diff --git a/.github/workflows/SwiftLint.yml b/.github/workflows/SwiftLint.yml new file mode 100644 index 000000000..1840516a1 --- /dev/null +++ b/.github/workflows/SwiftLint.yml @@ -0,0 +1,26 @@ +name: SwiftLint on Changed Files + +on: + pull_request: + paths: + - '**/*.swift' + +jobs: + swiftlint: + runs-on: macos-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Install SwiftLint + run: | + brew install swiftlint + + - name: Lint Modified Swift Files + run: | + # Fetch changes from the base branch of the pull request + git fetch origin ${{ github.base_ref }} + # Use git diff to find changed Swift files, then lint each file with SwiftLint + git diff --name-only origin/${{ github.base_ref }} | grep '\.swift$' | xargs swiftlint --config dydx/.swiftlint.yml --fix lint + diff --git a/.gitignore b/.gitignore index 16b699225..80fbddcaa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,25 @@ dydx/fastlane dydx/Pods/abacus/.gradle dydx/Pods/abacus/build +dydx/Pods/Abacus dydxV4/dydxV4/_Configurations/credentials.json dydxV4/dydxV4/_Configurations/GoogleService-Info-Staging.plist dydxV4/dydxV4/_Configurations/GoogleService-Info.plist -scripts/secrets \ No newline at end of file +scripts/secrets +scripts/percy_test.sh + +# fastlane specific +**/fastlane/report.xml + +# deliver temporary files +**/fastlane/Preview.html + +# scan temporary files +**/fastlane/test_output + +*.p12 +*.certSigningRequest +*.cer +*.mobileprovision +*.ipa +*.dSYM.* diff --git a/AmplitudeInjections/AmplitudeInjections.xcodeproj/project.pbxproj b/AmplitudeInjections/AmplitudeInjections.xcodeproj/project.pbxproj new file mode 100644 index 000000000..dea9e3efb --- /dev/null +++ b/AmplitudeInjections/AmplitudeInjections.xcodeproj/project.pbxproj @@ -0,0 +1,937 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 2E552127448AEBE948E414DC /* Pods_iOS_AmplitudeInjectionsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C6E1A9FAB1BD95234ECE499 /* Pods_iOS_AmplitudeInjectionsTests.framework */; }; + 6466E0C7280F65FC00B8791B /* AmplitudeInjections.docc in Sources */ = {isa = PBXBuildFile; fileRef = 6466E0C6280F65FC00B8791B /* AmplitudeInjections.docc */; }; + 6466E0CD280F65FC00B8791B /* AmplitudeInjections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6466E0C2280F65FC00B8791B /* AmplitudeInjections.framework */; }; + 6466E0D2280F65FC00B8791B /* AmplitudeInjectionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6466E0D1280F65FC00B8791B /* AmplitudeInjectionsTests.swift */; }; + 6466E0D3280F65FC00B8791B /* AmplitudeInjections.h in Headers */ = {isa = PBXBuildFile; fileRef = 6466E0C5280F65FC00B8791B /* AmplitudeInjections.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6466E184280F676600B8791B /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6466E179280F675300B8791B /* Utilities.framework */; platformFilter = ios; }; + 6466E18C280F776F00B8791B /* AmplitudeTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6466E18B280F776F00B8791B /* AmplitudeTracking.swift */; }; + 6466E1BB280F79F700B8791B /* PlatformParticles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6466E198280F79D800B8791B /* PlatformParticles.framework */; }; + 6466E1BE280F7A0000B8791B /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6466E1AE280F79D800B8791B /* ParticlesKit.framework */; platformFilter = ios; }; + 8F8DCEFC15BE98EB45B19BFC /* Pods_iOS_AmplitudeInjections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 073F828B22522EA43040A081 /* Pods_iOS_AmplitudeInjections.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 6466E0CE280F65FC00B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E0B9280F65FC00B8791B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6466E0C1280F65FC00B8791B; + remoteInfo = AmplitudeInjections; + }; + 6466E178280F675300B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E170280F675300B8791B /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 6466E17A280F675300B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E170280F675300B8791B /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 6466E17C280F675300B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E170280F675300B8791B /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 6466E17E280F675300B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E170280F675300B8791B /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 6466E180280F675300B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E170280F675300B8791B /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 6466E182280F675900B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E170280F675300B8791B /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 6466E197280F79D800B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E18D280F79D800B8791B /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB72921B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 6466E199280F79D800B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E18D280F79D800B8791B /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB7C621BB592E00BEF926; + remoteInfo = PlatformParticlesAppleWatch; + }; + 6466E19B280F79D800B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E18D280F79D800B8791B /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB4321BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTV; + }; + 6466E19D280F79D800B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E18D280F79D800B8791B /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CFF7C121D7D1F200DE7A79; + remoteInfo = MessageParticles; + }; + 6466E19F280F79D800B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E18D280F79D800B8791B /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB73221B0EE2D00391ADF; + remoteInfo = PlatformParticlesTests; + }; + 6466E1A1280F79D800B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E18D280F79D800B8791B /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB4B21BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTVTests; + }; + 6466E1A3280F79D800B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E18D280F79D800B8791B /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CFF7C921D7D1F300DE7A79; + remoteInfo = MessageParticlesTests; + }; + 6466E1AD280F79D800B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E1A5280F79D800B8791B /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FDA21B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 6466E1AF280F79D800B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E1A5280F79D800B8791B /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 319682B421B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 6466E1B1280F79D800B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E1A5280F79D800B8791B /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16121BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 6466E1B3280F79D800B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E1A5280F79D800B8791B /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FE321B0E9C4001775BA; + remoteInfo = ParticlesKitTests; + }; + 6466E1B5280F79D800B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E1A5280F79D800B8791B /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16921BA246A00BEF926; + remoteInfo = ParticlesKitAppleTVTests; + }; + 6466E1B7280F79E100B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E18D280F79D800B8791B /* PlatformParticles.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31ACB72821B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 6466E1B9280F79EA00B8791B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6466E1A5280F79D800B8791B /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 311C0FD921B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 073F828B22522EA43040A081 /* Pods_iOS_AmplitudeInjections.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_AmplitudeInjections.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1C6E1A9FAB1BD95234ECE499 /* Pods_iOS_AmplitudeInjectionsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_AmplitudeInjectionsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 419DB5F69656134F2104F7DB /* Pods-iOS-AmplitudeInjections.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-AmplitudeInjections.debug.xcconfig"; path = "Target Support Files/Pods-iOS-AmplitudeInjections/Pods-iOS-AmplitudeInjections.debug.xcconfig"; sourceTree = ""; }; + 6466E0C2280F65FC00B8791B /* AmplitudeInjections.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AmplitudeInjections.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6466E0C5280F65FC00B8791B /* AmplitudeInjections.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AmplitudeInjections.h; sourceTree = ""; }; + 6466E0C6280F65FC00B8791B /* AmplitudeInjections.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = AmplitudeInjections.docc; sourceTree = ""; }; + 6466E0CC280F65FC00B8791B /* AmplitudeInjectionsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AmplitudeInjectionsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 6466E0D1280F65FC00B8791B /* AmplitudeInjectionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmplitudeInjectionsTests.swift; sourceTree = ""; }; + 6466E170280F675300B8791B /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 6466E18B280F776F00B8791B /* AmplitudeTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmplitudeTracking.swift; sourceTree = ""; }; + 6466E18D280F79D800B8791B /* PlatformParticles.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PlatformParticles.xcodeproj; path = ../PlatformParticles/PlatformParticles.xcodeproj; sourceTree = ""; }; + 6466E1A5280F79D800B8791B /* ParticlesKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParticlesKit.xcodeproj; path = ../ParticlesKit/ParticlesKit.xcodeproj; sourceTree = ""; }; + A51231A2804523D59E2156DA /* Pods-iOS-AmplitudeInjections.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-AmplitudeInjections.release.xcconfig"; path = "Target Support Files/Pods-iOS-AmplitudeInjections/Pods-iOS-AmplitudeInjections.release.xcconfig"; sourceTree = ""; }; + AC592F9389429E65880A30DD /* Pods-iOS-AmplitudeInjectionsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-AmplitudeInjectionsTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-AmplitudeInjectionsTests/Pods-iOS-AmplitudeInjectionsTests.debug.xcconfig"; sourceTree = ""; }; + C0446A52CF1E7E532E741771 /* Pods-iOS-AmplitudeInjectionsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-AmplitudeInjectionsTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-AmplitudeInjectionsTests/Pods-iOS-AmplitudeInjectionsTests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6466E0BF280F65FC00B8791B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6466E1BE280F7A0000B8791B /* ParticlesKit.framework in Frameworks */, + 6466E1BB280F79F700B8791B /* PlatformParticles.framework in Frameworks */, + 6466E184280F676600B8791B /* Utilities.framework in Frameworks */, + 8F8DCEFC15BE98EB45B19BFC /* Pods_iOS_AmplitudeInjections.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6466E0C9280F65FC00B8791B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6466E0CD280F65FC00B8791B /* AmplitudeInjections.framework in Frameworks */, + 2E552127448AEBE948E414DC /* Pods_iOS_AmplitudeInjectionsTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02684E8128BD410A0007CEFF /* Dependencies */ = { + isa = PBXGroup; + children = ( + 6466E18D280F79D800B8791B /* PlatformParticles.xcodeproj */, + 6466E1A5280F79D800B8791B /* ParticlesKit.xcodeproj */, + 6466E170280F675300B8791B /* Utilities.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 6466E0B8280F65FC00B8791B = { + isa = PBXGroup; + children = ( + 02684E8128BD410A0007CEFF /* Dependencies */, + 6466E0C4280F65FC00B8791B /* AmplitudeInjections */, + 6466E0D0280F65FC00B8791B /* AmplitudeInjectionsTests */, + 6466E0C3280F65FC00B8791B /* Products */, + C977C944E2F31E5896495D48 /* Pods */, + 6801B17583B9D91E7D950811 /* Frameworks */, + ); + sourceTree = ""; + }; + 6466E0C3280F65FC00B8791B /* Products */ = { + isa = PBXGroup; + children = ( + 6466E0C2280F65FC00B8791B /* AmplitudeInjections.framework */, + 6466E0CC280F65FC00B8791B /* AmplitudeInjectionsTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 6466E0C4280F65FC00B8791B /* AmplitudeInjections */ = { + isa = PBXGroup; + children = ( + 6466E187280F760000B8791B /* _Configure */, + 6466E188280F760B00B8791B /* _Tracking */, + 6466E0C6280F65FC00B8791B /* AmplitudeInjections.docc */, + 6466E0C5280F65FC00B8791B /* AmplitudeInjections.h */, + ); + path = AmplitudeInjections; + sourceTree = ""; + }; + 6466E0D0280F65FC00B8791B /* AmplitudeInjectionsTests */ = { + isa = PBXGroup; + children = ( + 6466E0D1280F65FC00B8791B /* AmplitudeInjectionsTests.swift */, + ); + path = AmplitudeInjectionsTests; + sourceTree = ""; + }; + 6466E171280F675300B8791B /* Products */ = { + isa = PBXGroup; + children = ( + 6466E179280F675300B8791B /* Utilities.framework */, + 6466E17B280F675300B8791B /* Utilities.framework */, + 6466E17D280F675300B8791B /* Utilities.framework */, + 6466E17F280F675300B8791B /* UtilitiesTests.xctest */, + 6466E181280F675300B8791B /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 6466E187280F760000B8791B /* _Configure */ = { + isa = PBXGroup; + children = ( + ); + path = _Configure; + sourceTree = ""; + }; + 6466E188280F760B00B8791B /* _Tracking */ = { + isa = PBXGroup; + children = ( + 6466E18B280F776F00B8791B /* AmplitudeTracking.swift */, + ); + path = _Tracking; + sourceTree = ""; + }; + 6466E18E280F79D800B8791B /* Products */ = { + isa = PBXGroup; + children = ( + 6466E198280F79D800B8791B /* PlatformParticles.framework */, + 6466E19A280F79D800B8791B /* PlatformParticles.framework */, + 6466E19C280F79D800B8791B /* PlatformParticles.framework */, + 6466E19E280F79D800B8791B /* MessageParticles.framework */, + 6466E1A0280F79D800B8791B /* PlatformParticlesTests.xctest */, + 6466E1A2280F79D800B8791B /* PlatformParticlesAppleTVTests.xctest */, + 6466E1A4280F79D800B8791B /* MessageParticlesTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 6466E1A6280F79D800B8791B /* Products */ = { + isa = PBXGroup; + children = ( + 6466E1AE280F79D800B8791B /* ParticlesKit.framework */, + 6466E1B0280F79D800B8791B /* ParticlesKit.framework */, + 6466E1B2280F79D800B8791B /* ParticlesKit.framework */, + 6466E1B4280F79D800B8791B /* ParticlesKitTests.xctest */, + 6466E1B6280F79D800B8791B /* ParticlesKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 6801B17583B9D91E7D950811 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 073F828B22522EA43040A081 /* Pods_iOS_AmplitudeInjections.framework */, + 1C6E1A9FAB1BD95234ECE499 /* Pods_iOS_AmplitudeInjectionsTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + C977C944E2F31E5896495D48 /* Pods */ = { + isa = PBXGroup; + children = ( + 419DB5F69656134F2104F7DB /* Pods-iOS-AmplitudeInjections.debug.xcconfig */, + A51231A2804523D59E2156DA /* Pods-iOS-AmplitudeInjections.release.xcconfig */, + AC592F9389429E65880A30DD /* Pods-iOS-AmplitudeInjectionsTests.debug.xcconfig */, + C0446A52CF1E7E532E741771 /* Pods-iOS-AmplitudeInjectionsTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 6466E0BD280F65FC00B8791B /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 6466E0D3280F65FC00B8791B /* AmplitudeInjections.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 6466E0C1280F65FC00B8791B /* AmplitudeInjections */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6466E0D6280F65FC00B8791B /* Build configuration list for PBXNativeTarget "AmplitudeInjections" */; + buildPhases = ( + CF131249E735CD8F27C6747F /* [CP] Check Pods Manifest.lock */, + 6466E0BD280F65FC00B8791B /* Headers */, + 6466E0BE280F65FC00B8791B /* Sources */, + 6466E0BF280F65FC00B8791B /* Frameworks */, + 6466E0C0280F65FC00B8791B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 6466E1BA280F79EA00B8791B /* PBXTargetDependency */, + 6466E1B8280F79E100B8791B /* PBXTargetDependency */, + 6466E183280F675900B8791B /* PBXTargetDependency */, + ); + name = AmplitudeInjections; + productName = AmplitudeInjections; + productReference = 6466E0C2280F65FC00B8791B /* AmplitudeInjections.framework */; + productType = "com.apple.product-type.framework"; + }; + 6466E0CB280F65FC00B8791B /* AmplitudeInjectionsTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6466E0D9280F65FC00B8791B /* Build configuration list for PBXNativeTarget "AmplitudeInjectionsTests" */; + buildPhases = ( + 187655C88A544A2C914E780B /* [CP] Check Pods Manifest.lock */, + 6466E0C8280F65FC00B8791B /* Sources */, + 6466E0C9280F65FC00B8791B /* Frameworks */, + 6466E0CA280F65FC00B8791B /* Resources */, + B4994CF9A975E15036B1B0B3 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 6466E0CF280F65FC00B8791B /* PBXTargetDependency */, + ); + name = AmplitudeInjectionsTests; + productName = AmplitudeInjectionsTests; + productReference = 6466E0CC280F65FC00B8791B /* AmplitudeInjectionsTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6466E0B9280F65FC00B8791B /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1330; + LastUpgradeCheck = 1330; + TargetAttributes = { + 6466E0C1280F65FC00B8791B = { + CreatedOnToolsVersion = 13.3; + }; + 6466E0CB280F65FC00B8791B = { + CreatedOnToolsVersion = 13.3; + }; + }; + }; + buildConfigurationList = 6466E0BC280F65FC00B8791B /* Build configuration list for PBXProject "AmplitudeInjections" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 6466E0B8280F65FC00B8791B; + productRefGroup = 6466E0C3280F65FC00B8791B /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 6466E1A6280F79D800B8791B /* Products */; + ProjectRef = 6466E1A5280F79D800B8791B /* ParticlesKit.xcodeproj */; + }, + { + ProductGroup = 6466E18E280F79D800B8791B /* Products */; + ProjectRef = 6466E18D280F79D800B8791B /* PlatformParticles.xcodeproj */; + }, + { + ProductGroup = 6466E171280F675300B8791B /* Products */; + ProjectRef = 6466E170280F675300B8791B /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 6466E0C1280F65FC00B8791B /* AmplitudeInjections */, + 6466E0CB280F65FC00B8791B /* AmplitudeInjectionsTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 6466E179280F675300B8791B /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 6466E178280F675300B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E17B280F675300B8791B /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 6466E17A280F675300B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E17D280F675300B8791B /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 6466E17C280F675300B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E17F280F675300B8791B /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 6466E17E280F675300B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E181280F675300B8791B /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 6466E180280F675300B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E198280F79D800B8791B /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 6466E197280F79D800B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E19A280F79D800B8791B /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 6466E199280F79D800B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E19C280F79D800B8791B /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 6466E19B280F79D800B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E19E280F79D800B8791B /* MessageParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MessageParticles.framework; + remoteRef = 6466E19D280F79D800B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E1A0280F79D800B8791B /* PlatformParticlesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformParticlesTests.xctest; + remoteRef = 6466E19F280F79D800B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E1A2280F79D800B8791B /* PlatformParticlesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformParticlesAppleTVTests.xctest; + remoteRef = 6466E1A1280F79D800B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E1A4280F79D800B8791B /* MessageParticlesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = MessageParticlesTests.xctest; + remoteRef = 6466E1A3280F79D800B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E1AE280F79D800B8791B /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 6466E1AD280F79D800B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E1B0280F79D800B8791B /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 6466E1AF280F79D800B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E1B2280F79D800B8791B /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 6466E1B1280F79D800B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E1B4280F79D800B8791B /* ParticlesKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitTests.xctest; + remoteRef = 6466E1B3280F79D800B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 6466E1B6280F79D800B8791B /* ParticlesKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitAppleTVTests.xctest; + remoteRef = 6466E1B5280F79D800B8791B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 6466E0C0280F65FC00B8791B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6466E0CA280F65FC00B8791B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 187655C88A544A2C914E780B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-AmplitudeInjectionsTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B4994CF9A975E15036B1B0B3 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-AmplitudeInjectionsTests/Pods-iOS-AmplitudeInjectionsTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-AmplitudeInjectionsTests/Pods-iOS-AmplitudeInjectionsTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-AmplitudeInjectionsTests/Pods-iOS-AmplitudeInjectionsTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + CF131249E735CD8F27C6747F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-AmplitudeInjections-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6466E0BE280F65FC00B8791B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6466E0C7280F65FC00B8791B /* AmplitudeInjections.docc in Sources */, + 6466E18C280F776F00B8791B /* AmplitudeTracking.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6466E0C8280F65FC00B8791B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6466E0D2280F65FC00B8791B /* AmplitudeInjectionsTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 6466E0CF280F65FC00B8791B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6466E0C1280F65FC00B8791B /* AmplitudeInjections */; + targetProxy = 6466E0CE280F65FC00B8791B /* PBXContainerItemProxy */; + }; + 6466E183280F675900B8791B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 6466E182280F675900B8791B /* PBXContainerItemProxy */; + }; + 6466E1B8280F79E100B8791B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PlatformParticles; + targetProxy = 6466E1B7280F79E100B8791B /* PBXContainerItemProxy */; + }; + 6466E1BA280F79EA00B8791B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKit; + targetProxy = 6466E1B9280F79EA00B8791B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 6466E0D4280F65FC00B8791B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 6466E0D5280F65FC00B8791B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 6466E0D7280F65FC00B8791B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 419DB5F69656134F2104F7DB /* Pods-iOS-AmplitudeInjections.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.AmplitudeInjections; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6466E0D8280F65FC00B8791B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A51231A2804523D59E2156DA /* Pods-iOS-AmplitudeInjections.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.AmplitudeInjections; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 6466E0DA280F65FC00B8791B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AC592F9389429E65880A30DD /* Pods-iOS-AmplitudeInjectionsTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.AmplitudeInjectionsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6466E0DB280F65FC00B8791B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C0446A52CF1E7E532E741771 /* Pods-iOS-AmplitudeInjectionsTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.AmplitudeInjectionsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6466E0BC280F65FC00B8791B /* Build configuration list for PBXProject "AmplitudeInjections" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6466E0D4280F65FC00B8791B /* Debug */, + 6466E0D5280F65FC00B8791B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6466E0D6280F65FC00B8791B /* Build configuration list for PBXNativeTarget "AmplitudeInjections" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6466E0D7280F65FC00B8791B /* Debug */, + 6466E0D8280F65FC00B8791B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6466E0D9280F65FC00B8791B /* Build configuration list for PBXNativeTarget "AmplitudeInjectionsTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6466E0DA280F65FC00B8791B /* Debug */, + 6466E0DB280F65FC00B8791B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 6466E0B9280F65FC00B8791B /* Project object */; +} diff --git a/AmplitudeInjections/AmplitudeInjections/AmplitudeInjections.docc/AmplitudeInjections.md b/AmplitudeInjections/AmplitudeInjections/AmplitudeInjections.docc/AmplitudeInjections.md new file mode 100755 index 000000000..d00ff735e --- /dev/null +++ b/AmplitudeInjections/AmplitudeInjections/AmplitudeInjections.docc/AmplitudeInjections.md @@ -0,0 +1,13 @@ +# ``AmplitudeInjections`` + +Summary + +## Overview + +Text + +## Topics + +### Group + +- ``Symbol`` \ No newline at end of file diff --git a/AmplitudeInjections/AmplitudeInjections/AmplitudeInjections.h b/AmplitudeInjections/AmplitudeInjections/AmplitudeInjections.h new file mode 100644 index 000000000..ee09b00a8 --- /dev/null +++ b/AmplitudeInjections/AmplitudeInjections/AmplitudeInjections.h @@ -0,0 +1,18 @@ +// +// AmplitudeInjections.h +// AmplitudeInjections +// +// Created by John Huang on 4/19/22. +// + +#import + +//! Project version number for AmplitudeInjections. +FOUNDATION_EXPORT double AmplitudeInjectionsVersionNumber; + +//! Project version string for AmplitudeInjections. +FOUNDATION_EXPORT const unsigned char AmplitudeInjectionsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/AmplitudeInjections/AmplitudeInjections/_Tracking/AmplitudeTracking.swift b/AmplitudeInjections/AmplitudeInjections/_Tracking/AmplitudeTracking.swift new file mode 100644 index 000000000..57b2ab7ca --- /dev/null +++ b/AmplitudeInjections/AmplitudeInjections/_Tracking/AmplitudeTracking.swift @@ -0,0 +1,52 @@ +// +// AmplitudeTracking.swift +// AmplitudeInjections +// +// Created by John Huang on 4/19/22. +// + +import AmplitudeSwift +import PlatformParticles +import Utilities + +open class AmplitudeTracking: TransformerTracker { + + private let amplitude: Amplitude + + public init(_ apiKey: String) { + self.amplitude = Amplitude.init(configuration: .init(apiKey: apiKey)) + super.init() + } + + override open func log(event: String, data: [String: Any]?, revenue: NSNumber?) { + if !excluded { + var data = data + if let revenue = revenue { + if data == nil { + data = [String: Any]() + } + data?["$revenue"] = revenue + } + Console.shared.log("analytics log | Amplitude: logging event \(event) with data: \((data ?? [:]).description)") + let event = BaseEvent(eventType: event, eventProperties: data) + amplitude.track(event: event) + } + } + + override public func setUserId(_ userId: String?) { + Console.shared.log("analytics log | Amplitude: User ID set to: `\(userId ?? "nil")`") + amplitude.setUserId(userId: userId) + } + + // https://amplitude.com/docs/sdks/analytics/ios/ios-swift-sdk#identify + override public func setValue(_ value: Any?, forUserProperty userProperty: String) { + Console.shared.log("analytics log | Amplitude: User Property `\(userProperty)` set to: \(value ?? "nil")") + let identify = Identify() + if value != nil { + identify.set(property: userProperty, value: value) + } else { + identify.unset(property: userProperty) + } + amplitude.identify(identify: identify) + } +} diff --git a/AmplitudeInjections/AmplitudeInjectionsTests/AmplitudeInjectionsTests.swift b/AmplitudeInjections/AmplitudeInjectionsTests/AmplitudeInjectionsTests.swift new file mode 100644 index 000000000..c8bcde6aa --- /dev/null +++ b/AmplitudeInjections/AmplitudeInjectionsTests/AmplitudeInjectionsTests.swift @@ -0,0 +1,36 @@ +// +// AmplitudeInjectionsTests.swift +// AmplitudeInjectionsTests +// +// Created by John Huang on 4/19/22. +// + +import XCTest +@testable import AmplitudeInjections + +class AmplitudeInjectionsTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/AppsFlyerStaticInjections/AppsFlyerStaticInjections.xcodeproj/project.pbxproj b/AppsFlyerStaticInjections/AppsFlyerStaticInjections.xcodeproj/project.pbxproj new file mode 100644 index 000000000..fb9ecbaae --- /dev/null +++ b/AppsFlyerStaticInjections/AppsFlyerStaticInjections.xcodeproj/project.pbxproj @@ -0,0 +1,730 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 314B5D0A23DCC9F400139EB3 /* AppsFlyerStaticInjections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5D0923DCC9F400139EB3 /* AppsFlyerStaticInjections.swift */; }; + 314B5E1923DCCD3800139EB3 /* AppsFlyerTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5E1523DCCD3700139EB3 /* AppsFlyerTracking.swift */; }; + 314B5E1A23DCCD3800139EB3 /* AppsFlyerAttributor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5E1723DCCD3700139EB3 /* AppsFlyerAttributor.swift */; }; + 314B5E1B23DCCD3800139EB3 /* AppsFlyerInjections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5E1823DCCD3700139EB3 /* AppsFlyerInjections.swift */; }; + 6047E9B193F06297554A2333 /* Pods_iOS_AppsFlyerStaticInjections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C5D4FCC37316A5EE11EC461 /* Pods_iOS_AppsFlyerStaticInjections.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 314B716023DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B715623DD117600139EB3 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB72921B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 314B716223DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B715623DD117600139EB3 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB7C621BB592E00BEF926; + remoteInfo = PlatformParticlesAppleWatch; + }; + 314B716423DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B715623DD117600139EB3 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB4321BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTV; + }; + 314B716623DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B715623DD117600139EB3 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CFF7C121D7D1F200DE7A79; + remoteInfo = MessageParticles; + }; + 314B716823DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B715623DD117600139EB3 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB73221B0EE2D00391ADF; + remoteInfo = PlatformParticlesTests; + }; + 314B716A23DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B715623DD117600139EB3 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB4B21BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTVTests; + }; + 314B716C23DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B715623DD117600139EB3 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CFF7C921D7D1F300DE7A79; + remoteInfo = MessageParticlesTests; + }; + 314B717623DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B716E23DD117600139EB3 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FDA21B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 314B717823DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B716E23DD117600139EB3 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 319682B421B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 314B717A23DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B716E23DD117600139EB3 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16121BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 314B717C23DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B716E23DD117600139EB3 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FE321B0E9C4001775BA; + remoteInfo = ParticlesKitTests; + }; + 314B717E23DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B716E23DD117600139EB3 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16921BA246A00BEF926; + remoteInfo = ParticlesKitAppleTVTests; + }; + 314B718823DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B718023DD117600139EB3 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 314B718A23DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B718023DD117600139EB3 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 314B718C23DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B718023DD117600139EB3 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 314B718E23DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B718023DD117600139EB3 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 314B719023DD117600139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B718023DD117600139EB3 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 314B719223DD118C00139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B715623DD117600139EB3 /* PlatformParticles.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31ACB72821B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 314B719423DD118C00139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B716E23DD117600139EB3 /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 311C0FD921B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 314B719623DD118C00139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 314B718023DD117600139EB3 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 314B5D0423DCC9F400139EB3 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0BB984C1BE09B434B6BD5859 /* Pods-iOS-AppsFlyerStaticInjections.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-AppsFlyerStaticInjections.release.xcconfig"; path = "Target Support Files/Pods-iOS-AppsFlyerStaticInjections/Pods-iOS-AppsFlyerStaticInjections.release.xcconfig"; sourceTree = ""; }; + 314B5D0623DCC9F400139EB3 /* libAppsFlyerStaticInjections.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAppsFlyerStaticInjections.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 314B5D0923DCC9F400139EB3 /* AppsFlyerStaticInjections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppsFlyerStaticInjections.swift; sourceTree = ""; }; + 314B5E1523DCCD3700139EB3 /* AppsFlyerTracking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppsFlyerTracking.swift; sourceTree = ""; }; + 314B5E1723DCCD3700139EB3 /* AppsFlyerAttributor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppsFlyerAttributor.swift; sourceTree = ""; }; + 314B5E1823DCCD3700139EB3 /* AppsFlyerInjections.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppsFlyerInjections.swift; sourceTree = ""; }; + 314B715623DD117600139EB3 /* PlatformParticles.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PlatformParticles.xcodeproj; path = ../PlatformParticles/PlatformParticles.xcodeproj; sourceTree = ""; }; + 314B716E23DD117600139EB3 /* ParticlesKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParticlesKit.xcodeproj; path = ../ParticlesKit/ParticlesKit.xcodeproj; sourceTree = ""; }; + 314B718023DD117600139EB3 /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 7C5D4FCC37316A5EE11EC461 /* Pods_iOS_AppsFlyerStaticInjections.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_AppsFlyerStaticInjections.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 83655BBB6129CA84FF33CBD6 /* Pods-iOS-AppsFlyerStaticInjections.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-AppsFlyerStaticInjections.debug.xcconfig"; path = "Target Support Files/Pods-iOS-AppsFlyerStaticInjections/Pods-iOS-AppsFlyerStaticInjections.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 314B5D0323DCC9F400139EB3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6047E9B193F06297554A2333 /* Pods_iOS_AppsFlyerStaticInjections.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02684E9028BD41130007CEFF /* Dependencies */ = { + isa = PBXGroup; + children = ( + 314B715623DD117600139EB3 /* PlatformParticles.xcodeproj */, + 314B716E23DD117600139EB3 /* ParticlesKit.xcodeproj */, + 314B718023DD117600139EB3 /* Utilities.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 07CB1D119C1DCEE9D719C2BE /* Pods */ = { + isa = PBXGroup; + children = ( + 83655BBB6129CA84FF33CBD6 /* Pods-iOS-AppsFlyerStaticInjections.debug.xcconfig */, + 0BB984C1BE09B434B6BD5859 /* Pods-iOS-AppsFlyerStaticInjections.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; + 314B5CFD23DCC9F400139EB3 = { + isa = PBXGroup; + children = ( + 02684E9028BD41130007CEFF /* Dependencies */, + 314B5D0823DCC9F400139EB3 /* AppsFlyerStaticInjections */, + 314B5D0723DCC9F400139EB3 /* Products */, + 07CB1D119C1DCEE9D719C2BE /* Pods */, + DB847C407D47D5FF02BEB549 /* Frameworks */, + ); + sourceTree = ""; + }; + 314B5D0723DCC9F400139EB3 /* Products */ = { + isa = PBXGroup; + children = ( + 314B5D0623DCC9F400139EB3 /* libAppsFlyerStaticInjections.a */, + ); + name = Products; + sourceTree = ""; + }; + 314B5D0823DCC9F400139EB3 /* AppsFlyerStaticInjections */ = { + isa = PBXGroup; + children = ( + 314B5E1623DCCD3700139EB3 /* _Configure */, + 314B5E1423DCCD3700139EB3 /* _Tracking */, + 314B5D0923DCC9F400139EB3 /* AppsFlyerStaticInjections.swift */, + ); + path = AppsFlyerStaticInjections; + sourceTree = ""; + }; + 314B5E1423DCCD3700139EB3 /* _Tracking */ = { + isa = PBXGroup; + children = ( + 314B5E1523DCCD3700139EB3 /* AppsFlyerTracking.swift */, + ); + path = _Tracking; + sourceTree = ""; + }; + 314B5E1623DCCD3700139EB3 /* _Configure */ = { + isa = PBXGroup; + children = ( + 314B5E1723DCCD3700139EB3 /* AppsFlyerAttributor.swift */, + 314B5E1823DCCD3700139EB3 /* AppsFlyerInjections.swift */, + ); + path = _Configure; + sourceTree = ""; + }; + 314B715723DD117600139EB3 /* Products */ = { + isa = PBXGroup; + children = ( + 314B716123DD117600139EB3 /* PlatformParticles.framework */, + 314B716323DD117600139EB3 /* PlatformParticles.framework */, + 314B716523DD117600139EB3 /* PlatformParticles.framework */, + 314B716723DD117600139EB3 /* MessageParticles.framework */, + 314B716923DD117600139EB3 /* PlatformParticlesTests.xctest */, + 314B716B23DD117600139EB3 /* PlatformParticlesAppleTVTests.xctest */, + 314B716D23DD117600139EB3 /* MessageParticlesTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 314B716F23DD117600139EB3 /* Products */ = { + isa = PBXGroup; + children = ( + 314B717723DD117600139EB3 /* ParticlesKit.framework */, + 314B717923DD117600139EB3 /* ParticlesKit.framework */, + 314B717B23DD117600139EB3 /* ParticlesKit.framework */, + 314B717D23DD117600139EB3 /* ParticlesKitTests.xctest */, + 314B717F23DD117600139EB3 /* ParticlesKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 314B718123DD117600139EB3 /* Products */ = { + isa = PBXGroup; + children = ( + 314B718923DD117600139EB3 /* Utilities.framework */, + 314B718B23DD117600139EB3 /* Utilities.framework */, + 314B718D23DD117600139EB3 /* Utilities.framework */, + 314B718F23DD117600139EB3 /* UtilitiesTests.xctest */, + 314B719123DD117600139EB3 /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + DB847C407D47D5FF02BEB549 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7C5D4FCC37316A5EE11EC461 /* Pods_iOS_AppsFlyerStaticInjections.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 314B5D0523DCC9F400139EB3 /* AppsFlyerStaticInjections */ = { + isa = PBXNativeTarget; + buildConfigurationList = 314B5D0D23DCC9F400139EB3 /* Build configuration list for PBXNativeTarget "AppsFlyerStaticInjections" */; + buildPhases = ( + 1E0A8731C460807DBC643A57 /* [CP] Check Pods Manifest.lock */, + 314B5D0223DCC9F400139EB3 /* Sources */, + 314B5D0323DCC9F400139EB3 /* Frameworks */, + 314B5D0423DCC9F400139EB3 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 314B719323DD118C00139EB3 /* PBXTargetDependency */, + 314B719523DD118C00139EB3 /* PBXTargetDependency */, + 314B719723DD118C00139EB3 /* PBXTargetDependency */, + ); + name = AppsFlyerStaticInjections; + productName = AppsFlyerStaticInjections; + productReference = 314B5D0623DCC9F400139EB3 /* libAppsFlyerStaticInjections.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 314B5CFE23DCC9F400139EB3 /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Latest; + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "dYdX Trading Inc"; + TargetAttributes = { + 314B5D0523DCC9F400139EB3 = { + CreatedOnToolsVersion = 11.3.1; + }; + }; + }; + buildConfigurationList = 314B5D0123DCC9F400139EB3 /* Build configuration list for PBXProject "AppsFlyerStaticInjections" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 314B5CFD23DCC9F400139EB3; + productRefGroup = 314B5D0723DCC9F400139EB3 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 314B716F23DD117600139EB3 /* Products */; + ProjectRef = 314B716E23DD117600139EB3 /* ParticlesKit.xcodeproj */; + }, + { + ProductGroup = 314B715723DD117600139EB3 /* Products */; + ProjectRef = 314B715623DD117600139EB3 /* PlatformParticles.xcodeproj */; + }, + { + ProductGroup = 314B718123DD117600139EB3 /* Products */; + ProjectRef = 314B718023DD117600139EB3 /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 314B5D0523DCC9F400139EB3 /* AppsFlyerStaticInjections */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 314B716123DD117600139EB3 /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 314B716023DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B716323DD117600139EB3 /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 314B716223DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B716523DD117600139EB3 /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 314B716423DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B716723DD117600139EB3 /* MessageParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MessageParticles.framework; + remoteRef = 314B716623DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B716923DD117600139EB3 /* PlatformParticlesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformParticlesTests.xctest; + remoteRef = 314B716823DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B716B23DD117600139EB3 /* PlatformParticlesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformParticlesAppleTVTests.xctest; + remoteRef = 314B716A23DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B716D23DD117600139EB3 /* MessageParticlesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = MessageParticlesTests.xctest; + remoteRef = 314B716C23DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B717723DD117600139EB3 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 314B717623DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B717923DD117600139EB3 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 314B717823DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B717B23DD117600139EB3 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 314B717A23DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B717D23DD117600139EB3 /* ParticlesKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitTests.xctest; + remoteRef = 314B717C23DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B717F23DD117600139EB3 /* ParticlesKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitAppleTVTests.xctest; + remoteRef = 314B717E23DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B718923DD117600139EB3 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 314B718823DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B718B23DD117600139EB3 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 314B718A23DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B718D23DD117600139EB3 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 314B718C23DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B718F23DD117600139EB3 /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 314B718E23DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 314B719123DD117600139EB3 /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 314B719023DD117600139EB3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1E0A8731C460807DBC643A57 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-AppsFlyerStaticInjections-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 314B5D0223DCC9F400139EB3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B5E1B23DCCD3800139EB3 /* AppsFlyerInjections.swift in Sources */, + 314B5D0A23DCC9F400139EB3 /* AppsFlyerStaticInjections.swift in Sources */, + 314B5E1A23DCCD3800139EB3 /* AppsFlyerAttributor.swift in Sources */, + 314B5E1923DCCD3800139EB3 /* AppsFlyerTracking.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 314B719323DD118C00139EB3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PlatformParticles; + targetProxy = 314B719223DD118C00139EB3 /* PBXContainerItemProxy */; + }; + 314B719523DD118C00139EB3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKit; + targetProxy = 314B719423DD118C00139EB3 /* PBXContainerItemProxy */; + }; + 314B719723DD118C00139EB3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 314B719623DD118C00139EB3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 314B5D0B23DCC9F400139EB3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 314B5D0C23DCC9F400139EB3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 314B5D0E23DCC9F400139EB3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 83655BBB6129CA84FF33CBD6 /* Pods-iOS-AppsFlyerStaticInjections.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = XKF9424552; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 314B5D0F23DCC9F400139EB3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0BB984C1BE09B434B6BD5859 /* Pods-iOS-AppsFlyerStaticInjections.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = XKF9424552; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 314B5D0123DCC9F400139EB3 /* Build configuration list for PBXProject "AppsFlyerStaticInjections" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 314B5D0B23DCC9F400139EB3 /* Debug */, + 314B5D0C23DCC9F400139EB3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 314B5D0D23DCC9F400139EB3 /* Build configuration list for PBXNativeTarget "AppsFlyerStaticInjections" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 314B5D0E23DCC9F400139EB3 /* Debug */, + 314B5D0F23DCC9F400139EB3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 314B5CFE23DCC9F400139EB3 /* Project object */; +} diff --git a/AppsFlyerStaticInjections/AppsFlyerStaticInjections.xcodeproj/xcshareddata/xcschemes/AppsFlyerStaticInjections.xcscheme b/AppsFlyerStaticInjections/AppsFlyerStaticInjections.xcodeproj/xcshareddata/xcschemes/AppsFlyerStaticInjections.xcscheme new file mode 100644 index 000000000..30fc894c1 --- /dev/null +++ b/AppsFlyerStaticInjections/AppsFlyerStaticInjections.xcodeproj/xcshareddata/xcschemes/AppsFlyerStaticInjections.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppsFlyerStaticInjections/AppsFlyerStaticInjections/AppsFlyerStaticInjections.swift b/AppsFlyerStaticInjections/AppsFlyerStaticInjections/AppsFlyerStaticInjections.swift new file mode 100644 index 000000000..4a3a82753 --- /dev/null +++ b/AppsFlyerStaticInjections/AppsFlyerStaticInjections/AppsFlyerStaticInjections.swift @@ -0,0 +1,11 @@ +// +// AppsFlyerStaticInjections.swift +// AppsFlyerStaticInjections +// +// Created by Qiang Huang on 1/25/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +class AppsFlyerStaticInjections { + +} diff --git a/AppsFlyerStaticInjections/AppsFlyerStaticInjections/_Configure/AppsFlyerAttributor.swift b/AppsFlyerStaticInjections/AppsFlyerStaticInjections/_Configure/AppsFlyerAttributor.swift new file mode 100644 index 000000000..f0dc3c1e2 --- /dev/null +++ b/AppsFlyerStaticInjections/AppsFlyerStaticInjections/_Configure/AppsFlyerAttributor.swift @@ -0,0 +1,43 @@ +// +// AppsFlyerInjections.swift +// AppsFlyerInjections +// +// Created by Qiang Huang on 7/24/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import AppsFlyerLib +import PlatformParticles +import Utilities + +public final class AppsFlyerAttributor: NSObject, AttributionProtocol { + public func launch() { + AppsFlyerLib.shared().start { data, error in + if let error = error { + Console.shared.log(error) + } + if let data = data { + Console.shared.log(data) + } + } + } + + // Reports app open from a Universal Link for iOS 9 or later + public func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) { + AppsFlyerLib.shared().continue(userActivity, restorationHandler: restorationHandler) + } + + // Reports app open from deep link from apps which do not support Universal Links (Twitter) and for iOS8 and below + public func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) { + AppsFlyerLib.shared().handleOpen(url, sourceApplication: sourceApplication, withAnnotation: annotation) + } + + // Reports app open from deep link for iOS 10 or later + public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) { + AppsFlyerLib.shared().handleOpen(url, options: options) + } + + public func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) { + AppsFlyerLib.shared().continue(userActivity, restorationHandler: nil) + } +} diff --git a/AppsFlyerStaticInjections/AppsFlyerStaticInjections/_Configure/AppsFlyerInjections.swift b/AppsFlyerStaticInjections/AppsFlyerStaticInjections/_Configure/AppsFlyerInjections.swift new file mode 100644 index 000000000..a01f5352c --- /dev/null +++ b/AppsFlyerStaticInjections/AppsFlyerStaticInjections/_Configure/AppsFlyerInjections.swift @@ -0,0 +1,75 @@ +// +// AppsFlyerInjections.swift +// AppsFlyerInjections +// +// Created by Qiang Huang on 7/24/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import AppsFlyerLib +import Utilities + +public final class AppsFlyerRunner: NSObject, SingletonProtocol { + public static var shared: AppsFlyerRunner = { + AppsFlyerRunner() + }() + + public var devKey: String? { + didSet { + if devKey != oldValue { + setup() + } + } + } + + public var appId: String? { + didSet { + if appId != oldValue { + setup() + } + } + } + + private var tracker: AppsFlyerLib? { + didSet { + if tracker !== oldValue { + oldValue?.delegate = nil + tracker?.delegate = self + #if DEBUG + tracker?.isDebug = true + #endif + } + } + } + + private func setup() { + if let devKey = devKey, let appId = appId { + AppsFlyerLib.shared().appsFlyerDevKey = devKey + AppsFlyerLib.shared().appleAppID = appId + tracker = AppsFlyerLib.shared() + } + } +} + +extension AppsFlyerRunner: AppsFlyerLibDelegate { + public func onConversionDataSuccess(_ conversionInfo: [AnyHashable: Any]) { + } + + public func onConversionDataFail(_ error: Error) { + } + + public func onConversionDataReceived(_ installData: [AnyHashable: Any]) { + // Handle Conversion Data (Deferred Deep Link) + } + + public func onConversionDataRequestFailure(_ error: Error?) { + // print("\(error)") + } + + public func onAppOpenAttribution(_ attributionData: [AnyHashable: Any]) { + // Handle Deep Link Data + } + + public func onAppOpenAttributionFailure(_ error: Error) { + } +} diff --git a/AppsFlyerStaticInjections/AppsFlyerStaticInjections/_Tracking/AppsFlyerTracking.swift b/AppsFlyerStaticInjections/AppsFlyerStaticInjections/_Tracking/AppsFlyerTracking.swift new file mode 100644 index 000000000..4dafaef0a --- /dev/null +++ b/AppsFlyerStaticInjections/AppsFlyerStaticInjections/_Tracking/AppsFlyerTracking.swift @@ -0,0 +1,37 @@ +// +// AppsFlyerInjections.swift +// AppsFlyerInjections +// +// Created by Qiang Huang on 7/24/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import AppsFlyerLib +import PlatformParticles +import Utilities + +public class AppsFlyerTracking: TransformerTracker { + override public func log(event: String, data: [String: Any]?, revenue: NSNumber?) { + if !excluded { + var data = data + if let revenue = revenue { + if data == nil { + data = [String: Any]() + } + data?["af_revenue"] = revenue + } + AppsFlyerLib.shared().logEvent(event, withValues: data) + } + } + + public override func setUserId(_ userId: String?) { + AppsFlyerLib.shared().customerUserID = userId + } + + public override func setValue(_ value: Any?, forUserProperty userProperty: String) { + if AppsFlyerLib.shared().customData == nil { + AppsFlyerLib.shared().customData = [String: Any]() + } + AppsFlyerLib.shared().customData?[userProperty] = value + } +} diff --git a/CameraParticles/CameraParticles.xcodeproj/project.pbxproj b/CameraParticles/CameraParticles.xcodeproj/project.pbxproj new file mode 100644 index 000000000..9c8477ab3 --- /dev/null +++ b/CameraParticles/CameraParticles.xcodeproj/project.pbxproj @@ -0,0 +1,1221 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 3162B4BA26E13069000209E1 /* QRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162B4B926E13069000209E1 /* QRCodeViewController.swift */; }; + 3162B4D426E1307F000209E1 /* QRCodeCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162B4D326E1307F000209E1 /* QRCodeCapture.swift */; }; + 319ACD12249404A800DB66F1 /* CameraParticles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 319ACD08249404A800DB66F1 /* CameraParticles.framework */; }; + 319ACD17249404A800DB66F1 /* CameraParticlesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319ACD16249404A800DB66F1 /* CameraParticlesTests.swift */; }; + 319ACD19249404A800DB66F1 /* CameraParticles.h in Headers */ = {isa = PBXBuildFile; fileRef = 319ACD0B249404A800DB66F1 /* CameraParticles.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 319ACD24249404CA00DB66F1 /* CameraViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319ACD23249404CA00DB66F1 /* CameraViewController.swift */; }; + 319ACD6B2494058E00DB66F1 /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 319ACD562494055D00DB66F1 /* ParticlesKit.framework */; }; + 319ACD6D2494058E00DB66F1 /* PlatformParticles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 319ACD5F2494056100DB66F1 /* PlatformParticles.framework */; }; + 319ACD6F2494058E00DB66F1 /* UIToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 319ACD372494055C00DB66F1 /* UIToolkits.framework */; }; + 319ACD712494058E00DB66F1 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 319ACD2C2494055C00DB66F1 /* Utilities.framework */; }; + 319ACD7524940EDD00DB66F1 /* VideoProcessingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319ACD7424940EDD00DB66F1 /* VideoProcessingViewController.swift */; }; + 319ACD78249427C300DB66F1 /* CameraCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319ACD77249427C300DB66F1 /* CameraCapture.swift */; }; + 319ACD7A2494390300DB66F1 /* VidepProcessingCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319ACD792494390300DB66F1 /* VidepProcessingCapture.swift */; }; + 319ACEC62496927200DB66F1 /* VideoScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319ACEC52496927200DB66F1 /* VideoScanner.swift */; }; + 319ACEC82496966F00DB66F1 /* VideoTextScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319ACEC72496966F00DB66F1 /* VideoTextScanner.swift */; }; + 319ACFD22497D5C700DB66F1 /* TextVideoProcessingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319ACFD12497D5C700DB66F1 /* TextVideoProcessingViewController.swift */; }; + 319ACFE42497D96E00DB66F1 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 319ACFDF2497D95D00DB66F1 /* RoutingKit.framework */; }; + 418FB83A9B23494A11A0E041 /* Pods_iOS_CameraParticles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C29F15670AE171B45DAC9CA5 /* Pods_iOS_CameraParticles.framework */; }; + FB26021F0F8600AFCA6D10F0 /* Pods_iOS_CameraParticlesTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3188F04E36EB46EF6183232 /* Pods_iOS_CameraParticlesTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 319ACD13249404A800DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACCFF249404A800DB66F1 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 319ACD07249404A800DB66F1; + remoteInfo = CameraParticles; + }; + 319ACD2B2494055C00DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD262494055C00DB66F1 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 319ACD2D2494055C00DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD262494055C00DB66F1 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 319ACD362494055C00DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD2F2494055C00DB66F1 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65ADD216BC9E1008ABEE9; + remoteInfo = UIToolkits; + }; + 319ACD382494055C00DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD2F2494055C00DB66F1 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310C3A0B21D9623E0081E56D; + remoteInfo = UIAppToolkits; + }; + 319ACD3A2494055C00DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD2F2494055C00DB66F1 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AE6216BC9E1008ABEE9; + remoteInfo = UIToolkitsTests; + }; + 319ACD3C2494055C00DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD2F2494055C00DB66F1 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310C3A1321D9623F0081E56D; + remoteInfo = UIAppToolkitsTests; + }; + 319ACD552494055D00DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD502494055D00DB66F1 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FDA21B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 319ACD572494055D00DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD502494055D00DB66F1 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FE321B0E9C4001775BA; + remoteInfo = ParticlesKitTests; + }; + 319ACD5E2494056100DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD592494056100DB66F1 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB72921B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 319ACD602494056100DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD592494056100DB66F1 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB73221B0EE2D00391ADF; + remoteInfo = PlatformParticlesTests; + }; + 319ACD622494057200DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD592494056100DB66F1 /* PlatformParticles.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31ACB72821B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 319ACD642494057200DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD502494055D00DB66F1 /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 311C0FD921B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 319ACD662494057200DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD262494055C00DB66F1 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 319ACD682494057200DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD2F2494055C00DB66F1 /* UIToolkits.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65ADC216BC9E1008ABEE9; + remoteInfo = UIToolkits; + }; + 319ACFDE2497D95D00DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACFD92497D95D00DB66F1 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B01216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 319ACFE02497D95D00DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACFD92497D95D00DB66F1 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B0A216BCA10008ABEE9; + remoteInfo = RoutingKitTests; + }; + 319ACFE22497D96600DB66F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACFD92497D95D00DB66F1 /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65B00216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 31AB94D926DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD502494055D00DB66F1 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 319682B421B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 31AB94DB26DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD502494055D00DB66F1 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16121BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 31AB94DD26DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD502494055D00DB66F1 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16921BA246A00BEF926; + remoteInfo = ParticlesKitAppleTVTests; + }; + 31AB94E526DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD592494056100DB66F1 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB7C621BB592E00BEF926; + remoteInfo = PlatformParticlesAppleWatch; + }; + 31AB94E726DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD592494056100DB66F1 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB4321BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTV; + }; + 31AB94E926DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD592494056100DB66F1 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CFF7C121D7D1F200DE7A79; + remoteInfo = MessageParticles; + }; + 31AB94EB26DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD592494056100DB66F1 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB4B21BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTVTests; + }; + 31AB94ED26DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD592494056100DB66F1 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CFF7C921D7D1F300DE7A79; + remoteInfo = MessageParticlesTests; + }; + 31AB94F326DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACFD92497D95D00DB66F1 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196825B21B7947600AE0F28; + remoteInfo = RoutingKitAppleWatch; + }; + 31AB94F526DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACFD92497D95D00DB66F1 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22321BA26D700BEF926; + remoteInfo = RoutingKitAppleTV; + }; + 31AB94F726DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACFD92497D95D00DB66F1 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22B21BA26D800BEF926; + remoteInfo = RoutingKitAppleTVTests; + }; + 31AB94FF26DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD2F2494055C00DB66F1 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB85D21BB730700BEF926; + remoteInfo = UIToolkitsAppleWatch; + }; + 31AB950126DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD2F2494055C00DB66F1 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB0421BB79AD00BEF926; + remoteInfo = UIToolkitsAppleTV; + }; + 31AB950326DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD2F2494055C00DB66F1 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB0C21BB79AD00BEF926; + remoteInfo = UIToolkitsAppleTVTests; + }; + 31AB950926DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD262494055C00DB66F1 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 31AB950B26DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD262494055C00DB66F1 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 31AB950D26DEE84B00DB42B9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 319ACD262494055C00DB66F1 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 2D120953B7734D9A8E644E5D /* Pods-iOS-CameraParticlesTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-CameraParticlesTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-CameraParticlesTests/Pods-iOS-CameraParticlesTests.release.xcconfig"; sourceTree = ""; }; + 3162B4B926E13069000209E1 /* QRCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeViewController.swift; sourceTree = ""; }; + 3162B4D326E1307F000209E1 /* QRCodeCapture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeCapture.swift; sourceTree = ""; }; + 319ACD08249404A800DB66F1 /* CameraParticles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CameraParticles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 319ACD0B249404A800DB66F1 /* CameraParticles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraParticles.h; sourceTree = ""; }; + 319ACD0C249404A800DB66F1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 319ACD11249404A800DB66F1 /* CameraParticlesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CameraParticlesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 319ACD16249404A800DB66F1 /* CameraParticlesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraParticlesTests.swift; sourceTree = ""; }; + 319ACD18249404A800DB66F1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 319ACD23249404CA00DB66F1 /* CameraViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewController.swift; sourceTree = ""; }; + 319ACD262494055C00DB66F1 /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 319ACD2F2494055C00DB66F1 /* UIToolkits.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = UIToolkits.xcodeproj; path = ../UIToolkits/UIToolkits.xcodeproj; sourceTree = ""; }; + 319ACD502494055D00DB66F1 /* ParticlesKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParticlesKit.xcodeproj; path = ../ParticlesKit/ParticlesKit.xcodeproj; sourceTree = ""; }; + 319ACD592494056100DB66F1 /* PlatformParticles.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PlatformParticles.xcodeproj; path = ../PlatformParticles/PlatformParticles.xcodeproj; sourceTree = ""; }; + 319ACD7424940EDD00DB66F1 /* VideoProcessingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoProcessingViewController.swift; sourceTree = ""; }; + 319ACD77249427C300DB66F1 /* CameraCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraCapture.swift; sourceTree = ""; }; + 319ACD792494390300DB66F1 /* VidepProcessingCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VidepProcessingCapture.swift; sourceTree = ""; }; + 319ACEC52496927200DB66F1 /* VideoScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoScanner.swift; sourceTree = ""; }; + 319ACEC72496966F00DB66F1 /* VideoTextScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoTextScanner.swift; sourceTree = ""; }; + 319ACFD12497D5C700DB66F1 /* TextVideoProcessingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextVideoProcessingViewController.swift; sourceTree = ""; }; + 319ACFD92497D95D00DB66F1 /* RoutingKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RoutingKit.xcodeproj; path = ../RoutingKit/RoutingKit.xcodeproj; sourceTree = ""; }; + 994AFEE6943DB72D744DA418 /* Pods-iOS-CameraParticles.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-CameraParticles.release.xcconfig"; path = "Target Support Files/Pods-iOS-CameraParticles/Pods-iOS-CameraParticles.release.xcconfig"; sourceTree = ""; }; + BCB7953AE47B2AB20BC5F4A2 /* Pods-iOS-CameraParticlesTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-CameraParticlesTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-CameraParticlesTests/Pods-iOS-CameraParticlesTests.debug.xcconfig"; sourceTree = ""; }; + C29F15670AE171B45DAC9CA5 /* Pods_iOS_CameraParticles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_CameraParticles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D3188F04E36EB46EF6183232 /* Pods_iOS_CameraParticlesTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_CameraParticlesTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EF62150590C26FFFB47E26E9 /* Pods-iOS-CameraParticles.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-CameraParticles.debug.xcconfig"; path = "Target Support Files/Pods-iOS-CameraParticles/Pods-iOS-CameraParticles.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 319ACD05249404A800DB66F1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 319ACD6B2494058E00DB66F1 /* ParticlesKit.framework in Frameworks */, + 319ACD6F2494058E00DB66F1 /* UIToolkits.framework in Frameworks */, + 319ACD6D2494058E00DB66F1 /* PlatformParticles.framework in Frameworks */, + 319ACD712494058E00DB66F1 /* Utilities.framework in Frameworks */, + 319ACFE42497D96E00DB66F1 /* RoutingKit.framework in Frameworks */, + 418FB83A9B23494A11A0E041 /* Pods_iOS_CameraParticles.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 319ACD0E249404A800DB66F1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 319ACD12249404A800DB66F1 /* CameraParticles.framework in Frameworks */, + FB26021F0F8600AFCA6D10F0 /* Pods_iOS_CameraParticlesTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02684EAD28BD412A0007CEFF /* Dependencies */ = { + isa = PBXGroup; + children = ( + 319ACD592494056100DB66F1 /* PlatformParticles.xcodeproj */, + 319ACD502494055D00DB66F1 /* ParticlesKit.xcodeproj */, + 319ACFD92497D95D00DB66F1 /* RoutingKit.xcodeproj */, + 319ACD2F2494055C00DB66F1 /* UIToolkits.xcodeproj */, + 319ACD262494055C00DB66F1 /* Utilities.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 319ACCFE249404A800DB66F1 = { + isa = PBXGroup; + children = ( + 02684EAD28BD412A0007CEFF /* Dependencies */, + 319ACD0A249404A800DB66F1 /* CameraParticles */, + 319ACD15249404A800DB66F1 /* CameraParticlesTests */, + 319ACD09249404A800DB66F1 /* Products */, + 7D7AD4A8FB391B42A65AC91F /* Pods */, + 6E0E876D914719AFB6ABC596 /* Frameworks */, + ); + sourceTree = ""; + }; + 319ACD09249404A800DB66F1 /* Products */ = { + isa = PBXGroup; + children = ( + 319ACD08249404A800DB66F1 /* CameraParticles.framework */, + 319ACD11249404A800DB66F1 /* CameraParticlesTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 319ACD0A249404A800DB66F1 /* CameraParticles */ = { + isa = PBXGroup; + children = ( + 319ACD0B249404A800DB66F1 /* CameraParticles.h */, + 319ACD0C249404A800DB66F1 /* Info.plist */, + 319ACD76249427AC00DB66F1 /* _Capture */, + 319ACEBE2496925300DB66F1 /* _Scanner */, + 319ACD22249404B500DB66F1 /* _ViewControllers */, + ); + path = CameraParticles; + sourceTree = ""; + }; + 319ACD15249404A800DB66F1 /* CameraParticlesTests */ = { + isa = PBXGroup; + children = ( + 319ACD16249404A800DB66F1 /* CameraParticlesTests.swift */, + 319ACD18249404A800DB66F1 /* Info.plist */, + ); + path = CameraParticlesTests; + sourceTree = ""; + }; + 319ACD22249404B500DB66F1 /* _ViewControllers */ = { + isa = PBXGroup; + children = ( + 319ACD23249404CA00DB66F1 /* CameraViewController.swift */, + 319ACD7424940EDD00DB66F1 /* VideoProcessingViewController.swift */, + 319ACFD12497D5C700DB66F1 /* TextVideoProcessingViewController.swift */, + 3162B4B926E13069000209E1 /* QRCodeViewController.swift */, + ); + path = _ViewControllers; + sourceTree = ""; + }; + 319ACD272494055C00DB66F1 /* Products */ = { + isa = PBXGroup; + children = ( + 319ACD2C2494055C00DB66F1 /* Utilities.framework */, + 31AB950A26DEE84B00DB42B9 /* Utilities.framework */, + 31AB950C26DEE84B00DB42B9 /* Utilities.framework */, + 319ACD2E2494055C00DB66F1 /* UtilitiesTests.xctest */, + 31AB950E26DEE84B00DB42B9 /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 319ACD302494055C00DB66F1 /* Products */ = { + isa = PBXGroup; + children = ( + 319ACD372494055C00DB66F1 /* UIToolkits.framework */, + 31AB950026DEE84B00DB42B9 /* UIToolkits.framework */, + 31AB950226DEE84B00DB42B9 /* UIToolkits.framework */, + 319ACD392494055C00DB66F1 /* UIAppToolkits.framework */, + 319ACD3B2494055C00DB66F1 /* UIToolkitsTests.xctest */, + 31AB950426DEE84B00DB42B9 /* UIToolkitsAppleTVTests.xctest */, + 319ACD3D2494055C00DB66F1 /* UIAppToolkitsTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 319ACD512494055D00DB66F1 /* Products */ = { + isa = PBXGroup; + children = ( + 319ACD562494055D00DB66F1 /* ParticlesKit.framework */, + 31AB94DA26DEE84B00DB42B9 /* ParticlesKit.framework */, + 31AB94DC26DEE84B00DB42B9 /* ParticlesKit.framework */, + 319ACD582494055D00DB66F1 /* ParticlesKitTests.xctest */, + 31AB94DE26DEE84B00DB42B9 /* ParticlesKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 319ACD5A2494056100DB66F1 /* Products */ = { + isa = PBXGroup; + children = ( + 319ACD5F2494056100DB66F1 /* PlatformParticles.framework */, + 31AB94E626DEE84B00DB42B9 /* PlatformParticles.framework */, + 31AB94E826DEE84B00DB42B9 /* PlatformParticles.framework */, + 31AB94EA26DEE84B00DB42B9 /* MessageParticles.framework */, + 319ACD612494056100DB66F1 /* PlatformParticlesTests.xctest */, + 31AB94EC26DEE84B00DB42B9 /* PlatformParticlesAppleTVTests.xctest */, + 31AB94EE26DEE84B00DB42B9 /* MessageParticlesTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 319ACD76249427AC00DB66F1 /* _Capture */ = { + isa = PBXGroup; + children = ( + 319ACD77249427C300DB66F1 /* CameraCapture.swift */, + 3162B4D326E1307F000209E1 /* QRCodeCapture.swift */, + 319ACD792494390300DB66F1 /* VidepProcessingCapture.swift */, + ); + path = _Capture; + sourceTree = ""; + }; + 319ACEBE2496925300DB66F1 /* _Scanner */ = { + isa = PBXGroup; + children = ( + 319ACEC52496927200DB66F1 /* VideoScanner.swift */, + 319ACEC72496966F00DB66F1 /* VideoTextScanner.swift */, + ); + path = _Scanner; + sourceTree = ""; + }; + 319ACFDA2497D95D00DB66F1 /* Products */ = { + isa = PBXGroup; + children = ( + 319ACFDF2497D95D00DB66F1 /* RoutingKit.framework */, + 31AB94F426DEE84B00DB42B9 /* RoutingKit.framework */, + 31AB94F626DEE84B00DB42B9 /* RoutingKit.framework */, + 319ACFE12497D95D00DB66F1 /* RoutingKitTests.xctest */, + 31AB94F826DEE84B00DB42B9 /* RoutingKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 6E0E876D914719AFB6ABC596 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C29F15670AE171B45DAC9CA5 /* Pods_iOS_CameraParticles.framework */, + D3188F04E36EB46EF6183232 /* Pods_iOS_CameraParticlesTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 7D7AD4A8FB391B42A65AC91F /* Pods */ = { + isa = PBXGroup; + children = ( + EF62150590C26FFFB47E26E9 /* Pods-iOS-CameraParticles.debug.xcconfig */, + 994AFEE6943DB72D744DA418 /* Pods-iOS-CameraParticles.release.xcconfig */, + BCB7953AE47B2AB20BC5F4A2 /* Pods-iOS-CameraParticlesTests.debug.xcconfig */, + 2D120953B7734D9A8E644E5D /* Pods-iOS-CameraParticlesTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 319ACD03249404A800DB66F1 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 319ACD19249404A800DB66F1 /* CameraParticles.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 319ACD07249404A800DB66F1 /* CameraParticles */ = { + isa = PBXNativeTarget; + buildConfigurationList = 319ACD1C249404A800DB66F1 /* Build configuration list for PBXNativeTarget "CameraParticles" */; + buildPhases = ( + 537D376FE9A40981E485AF47 /* [CP] Check Pods Manifest.lock */, + 319ACD03249404A800DB66F1 /* Headers */, + 319ACD04249404A800DB66F1 /* Sources */, + 319ACD05249404A800DB66F1 /* Frameworks */, + 319ACD06249404A800DB66F1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 319ACFE32497D96600DB66F1 /* PBXTargetDependency */, + 319ACD632494057200DB66F1 /* PBXTargetDependency */, + 319ACD652494057200DB66F1 /* PBXTargetDependency */, + 319ACD672494057200DB66F1 /* PBXTargetDependency */, + 319ACD692494057200DB66F1 /* PBXTargetDependency */, + ); + name = CameraParticles; + productName = CameraParticles; + productReference = 319ACD08249404A800DB66F1 /* CameraParticles.framework */; + productType = "com.apple.product-type.framework"; + }; + 319ACD10249404A800DB66F1 /* CameraParticlesTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 319ACD1F249404A800DB66F1 /* Build configuration list for PBXNativeTarget "CameraParticlesTests" */; + buildPhases = ( + 9415E6F380500F2C3588AA76 /* [CP] Check Pods Manifest.lock */, + 319ACD0D249404A800DB66F1 /* Sources */, + 319ACD0E249404A800DB66F1 /* Frameworks */, + 319ACD0F249404A800DB66F1 /* Resources */, + 99B1D6594E694E7CA111A1E0 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 319ACD14249404A800DB66F1 /* PBXTargetDependency */, + ); + name = CameraParticlesTests; + productName = CameraParticlesTests; + productReference = 319ACD11249404A800DB66F1 /* CameraParticlesTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 319ACCFF249404A800DB66F1 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "dYdX Trading Inc"; + TargetAttributes = { + 319ACD07249404A800DB66F1 = { + CreatedOnToolsVersion = 11.5; + LastSwiftMigration = 1150; + }; + 319ACD10249404A800DB66F1 = { + CreatedOnToolsVersion = 11.5; + }; + }; + }; + buildConfigurationList = 319ACD02249404A800DB66F1 /* Build configuration list for PBXProject "CameraParticles" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 319ACCFE249404A800DB66F1; + productRefGroup = 319ACD09249404A800DB66F1 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 319ACD512494055D00DB66F1 /* Products */; + ProjectRef = 319ACD502494055D00DB66F1 /* ParticlesKit.xcodeproj */; + }, + { + ProductGroup = 319ACD5A2494056100DB66F1 /* Products */; + ProjectRef = 319ACD592494056100DB66F1 /* PlatformParticles.xcodeproj */; + }, + { + ProductGroup = 319ACFDA2497D95D00DB66F1 /* Products */; + ProjectRef = 319ACFD92497D95D00DB66F1 /* RoutingKit.xcodeproj */; + }, + { + ProductGroup = 319ACD302494055C00DB66F1 /* Products */; + ProjectRef = 319ACD2F2494055C00DB66F1 /* UIToolkits.xcodeproj */; + }, + { + ProductGroup = 319ACD272494055C00DB66F1 /* Products */; + ProjectRef = 319ACD262494055C00DB66F1 /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 319ACD07249404A800DB66F1 /* CameraParticles */, + 319ACD10249404A800DB66F1 /* CameraParticlesTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 319ACD2C2494055C00DB66F1 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 319ACD2B2494055C00DB66F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319ACD2E2494055C00DB66F1 /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 319ACD2D2494055C00DB66F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319ACD372494055C00DB66F1 /* UIToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIToolkits.framework; + remoteRef = 319ACD362494055C00DB66F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319ACD392494055C00DB66F1 /* UIAppToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIAppToolkits.framework; + remoteRef = 319ACD382494055C00DB66F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319ACD3B2494055C00DB66F1 /* UIToolkitsTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UIToolkitsTests.xctest; + remoteRef = 319ACD3A2494055C00DB66F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319ACD3D2494055C00DB66F1 /* UIAppToolkitsTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UIAppToolkitsTests.xctest; + remoteRef = 319ACD3C2494055C00DB66F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319ACD562494055D00DB66F1 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 319ACD552494055D00DB66F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319ACD582494055D00DB66F1 /* ParticlesKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitTests.xctest; + remoteRef = 319ACD572494055D00DB66F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319ACD5F2494056100DB66F1 /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 319ACD5E2494056100DB66F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319ACD612494056100DB66F1 /* PlatformParticlesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformParticlesTests.xctest; + remoteRef = 319ACD602494056100DB66F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319ACFDF2497D95D00DB66F1 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 319ACFDE2497D95D00DB66F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319ACFE12497D95D00DB66F1 /* RoutingKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitTests.xctest; + remoteRef = 319ACFE02497D95D00DB66F1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB94DA26DEE84B00DB42B9 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31AB94D926DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB94DC26DEE84B00DB42B9 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31AB94DB26DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB94DE26DEE84B00DB42B9 /* ParticlesKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitAppleTVTests.xctest; + remoteRef = 31AB94DD26DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB94E626DEE84B00DB42B9 /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 31AB94E526DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB94E826DEE84B00DB42B9 /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 31AB94E726DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB94EA26DEE84B00DB42B9 /* MessageParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MessageParticles.framework; + remoteRef = 31AB94E926DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB94EC26DEE84B00DB42B9 /* PlatformParticlesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformParticlesAppleTVTests.xctest; + remoteRef = 31AB94EB26DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB94EE26DEE84B00DB42B9 /* MessageParticlesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = MessageParticlesTests.xctest; + remoteRef = 31AB94ED26DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB94F426DEE84B00DB42B9 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 31AB94F326DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB94F626DEE84B00DB42B9 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 31AB94F526DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB94F826DEE84B00DB42B9 /* RoutingKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitAppleTVTests.xctest; + remoteRef = 31AB94F726DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB950026DEE84B00DB42B9 /* UIToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIToolkits.framework; + remoteRef = 31AB94FF26DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB950226DEE84B00DB42B9 /* UIToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIToolkits.framework; + remoteRef = 31AB950126DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB950426DEE84B00DB42B9 /* UIToolkitsAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UIToolkitsAppleTVTests.xctest; + remoteRef = 31AB950326DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB950A26DEE84B00DB42B9 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 31AB950926DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB950C26DEE84B00DB42B9 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 31AB950B26DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31AB950E26DEE84B00DB42B9 /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 31AB950D26DEE84B00DB42B9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 319ACD06249404A800DB66F1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 319ACD0F249404A800DB66F1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 537D376FE9A40981E485AF47 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-CameraParticles-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9415E6F380500F2C3588AA76 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-CameraParticlesTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 99B1D6594E694E7CA111A1E0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-CameraParticlesTests/Pods-iOS-CameraParticlesTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-CameraParticlesTests/Pods-iOS-CameraParticlesTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-CameraParticlesTests/Pods-iOS-CameraParticlesTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 319ACD04249404A800DB66F1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 319ACD7A2494390300DB66F1 /* VidepProcessingCapture.swift in Sources */, + 319ACEC82496966F00DB66F1 /* VideoTextScanner.swift in Sources */, + 3162B4BA26E13069000209E1 /* QRCodeViewController.swift in Sources */, + 3162B4D426E1307F000209E1 /* QRCodeCapture.swift in Sources */, + 319ACD24249404CA00DB66F1 /* CameraViewController.swift in Sources */, + 319ACD78249427C300DB66F1 /* CameraCapture.swift in Sources */, + 319ACEC62496927200DB66F1 /* VideoScanner.swift in Sources */, + 319ACFD22497D5C700DB66F1 /* TextVideoProcessingViewController.swift in Sources */, + 319ACD7524940EDD00DB66F1 /* VideoProcessingViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 319ACD0D249404A800DB66F1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 319ACD17249404A800DB66F1 /* CameraParticlesTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 319ACD14249404A800DB66F1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 319ACD07249404A800DB66F1 /* CameraParticles */; + targetProxy = 319ACD13249404A800DB66F1 /* PBXContainerItemProxy */; + }; + 319ACD632494057200DB66F1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PlatformParticles; + targetProxy = 319ACD622494057200DB66F1 /* PBXContainerItemProxy */; + }; + 319ACD652494057200DB66F1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKit; + targetProxy = 319ACD642494057200DB66F1 /* PBXContainerItemProxy */; + }; + 319ACD672494057200DB66F1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 319ACD662494057200DB66F1 /* PBXContainerItemProxy */; + }; + 319ACD692494057200DB66F1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UIToolkits; + targetProxy = 319ACD682494057200DB66F1 /* PBXContainerItemProxy */; + }; + 319ACFE32497D96600DB66F1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKit; + targetProxy = 319ACFE22497D96600DB66F1 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 319ACD1A249404A800DB66F1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 319ACD1B249404A800DB66F1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 319ACD1D249404A800DB66F1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EF62150590C26FFFB47E26E9 /* Pods-iOS-CameraParticles.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = CameraParticles/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.CameraParticles; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 319ACD1E249404A800DB66F1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 994AFEE6943DB72D744DA418 /* Pods-iOS-CameraParticles.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = CameraParticles/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.CameraParticles; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 319ACD20249404A800DB66F1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BCB7953AE47B2AB20BC5F4A2 /* Pods-iOS-CameraParticlesTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = CameraParticlesTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.CameraParticlesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 319ACD21249404A800DB66F1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2D120953B7734D9A8E644E5D /* Pods-iOS-CameraParticlesTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = CameraParticlesTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.CameraParticlesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 319ACD02249404A800DB66F1 /* Build configuration list for PBXProject "CameraParticles" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 319ACD1A249404A800DB66F1 /* Debug */, + 319ACD1B249404A800DB66F1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 319ACD1C249404A800DB66F1 /* Build configuration list for PBXNativeTarget "CameraParticles" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 319ACD1D249404A800DB66F1 /* Debug */, + 319ACD1E249404A800DB66F1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 319ACD1F249404A800DB66F1 /* Build configuration list for PBXNativeTarget "CameraParticlesTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 319ACD20249404A800DB66F1 /* Debug */, + 319ACD21249404A800DB66F1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 319ACCFF249404A800DB66F1 /* Project object */; +} diff --git a/CameraParticles/CameraParticles.xcodeproj/xcshareddata/xcschemes/CameraParticles.xcscheme b/CameraParticles/CameraParticles.xcodeproj/xcshareddata/xcschemes/CameraParticles.xcscheme new file mode 100644 index 000000000..d2286565b --- /dev/null +++ b/CameraParticles/CameraParticles.xcodeproj/xcshareddata/xcschemes/CameraParticles.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CameraParticles/CameraParticles/CameraParticles.h b/CameraParticles/CameraParticles/CameraParticles.h new file mode 100644 index 000000000..2cb8a3362 --- /dev/null +++ b/CameraParticles/CameraParticles/CameraParticles.h @@ -0,0 +1,18 @@ +// +// CameraParticles.h +// CameraParticles +// +// Created by Qiang Huang on 6/12/20. +// + +#import + +//! Project version number for CameraParticles. +FOUNDATION_EXPORT double CameraParticlesVersionNumber; + +//! Project version string for CameraParticles. +FOUNDATION_EXPORT const unsigned char CameraParticlesVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/CameraParticles/CameraParticles/Info.plist b/CameraParticles/CameraParticles/Info.plist new file mode 100644 index 000000000..9bcb24442 --- /dev/null +++ b/CameraParticles/CameraParticles/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/CameraParticles/CameraParticles/_Capture/CameraCapture.swift b/CameraParticles/CameraParticles/_Capture/CameraCapture.swift new file mode 100644 index 000000000..a1b8efd5e --- /dev/null +++ b/CameraParticles/CameraParticles/_Capture/CameraCapture.swift @@ -0,0 +1,221 @@ +// +// CameraCapture.swift +// CameraParticles +// +// Created by Qiang Huang on 6/12/20. +// + +import AVFoundation +import Foundation + +@objc open class CameraCapture: NSObject { + public var defaultFront: Bool = false + public var defaultFlash: Bool = false + + private var lock: Int = 0 { + didSet { + if lock != oldValue { + if oldValue == 0 { + session.beginConfiguration() + } else if lock == 0 { + session.commitConfiguration() + } + } + } + } + + @objc open dynamic var running: Bool = false { + didSet { + if running != oldValue { + if running { + DispatchQueue.global().async { [weak self] in + self?.session.startRunning() + } + } else { + DispatchQueue.global().async { [weak self] in + self?.session.stopRunning() + } + } + } + } + } + + lazy var frontTag: String = { + "\(className()).front" + }() + + lazy var flashTag: String = { + "\(className()).flash" + }() + + @objc open dynamic var front: Bool { + get { + return parser.asBoolean(UserDefaults.standard.string(forKey: frontTag))?.boolValue ?? defaultFlash + } + set { + if front != newValue { + UserDefaults.standard.set(newValue ? "1" : "0", forKey: frontTag) + setupCamera() + } + } + } + + @objc open dynamic var light: Bool { + get { + return parser.asBoolean(UserDefaults.standard.string(forKey: flashTag))?.boolValue ?? defaultFlash + } + set { + if light != newValue { + UserDefaults.standard.set(newValue ? "1" : "0", forKey: flashTag) + setupLight() + } + } + } + + @objc open dynamic var hasAudio: Bool = false { + didSet { + if hasAudio != oldValue { + audio = hasAudio ? AVCaptureDevice.default(for: .audio) : nil + } + } + } + + @objc open dynamic var camera: AVCaptureDevice? { + didSet { + if camera !== oldValue { + if let camera = camera { + do { + videoInput = try AVCaptureDeviceInput(device: camera) + } catch _ { + } + DispatchQueue.main.async {[weak self] in + self?.setupLight() + } + } else { + videoInput = nil + } + } + } + } + + @objc open dynamic var audio: AVCaptureDevice? { + didSet { + if audio !== oldValue { + if let audio = audio { + do { + audioInput = try AVCaptureDeviceInput(device: audio) + } catch _ { + } + } else { + audioInput = nil + } + } + } + } + + @objc open dynamic var videoInput: AVCaptureDeviceInput? { + didSet { + if videoInput !== oldValue { + configure { [weak self] in + if let self = self { + if let oldInput = oldValue { + session.removeInput(oldInput) + } + if let videoInput = self.videoInput, session.canAddInput(videoInput) { + session.addInput(videoInput) + if previewLayer == nil { + self.session.sessionPreset = .hd1920x1080 + let previewLayer = AVCaptureVideoPreviewLayer(session: session) + previewLayer.videoGravity = .resizeAspectFill + previewLayer.connection?.videoOrientation = .portrait + self.previewLayer = previewLayer + } + } + } + } + } + } + } + + @objc open dynamic var audioInput: AVCaptureDeviceInput? { + didSet { + if audioInput !== oldValue { + configure { [weak self] in + if let self = self { + if let oldInput = oldValue { + session.removeInput(oldInput) + } + if let audioInput = self.audioInput, session.canAddInput(audioInput) { + session.addInput(audioInput) + } + } + } + } + } + } + + @objc open dynamic var output: AVCaptureOutput? { + didSet { + if output !== oldValue { + configure { [weak self] in + if let self = self { + if let oldOutput = oldValue { + session.removeOutput(oldOutput) + } + if let output = self.output, session.canAddOutput(output) { + session.addOutput(output) + } + if let connection = output?.connection(with: .video) { + connection.videoOrientation = .portrait + } + } + } + } + } + } + + @objc open dynamic var session: AVCaptureSession = AVCaptureSession() + + @objc open dynamic var previewLayer: AVCaptureVideoPreviewLayer? + + open func setup() { + configure { [weak self] in + if let self = self { + self.setupInput() + self.setupOutput() + } + } + } + + open func setupInput() { + setupCamera() + } + + open func setupCamera() { + camera = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: front ? .front : .back) ?? AVCaptureDevice.default(for: .video) + } + + open func setupLight() { + if let camera = camera, camera.isTorchAvailable { + do { + try camera.lockForConfiguration() + if light { + camera.torchMode = .on + try camera.setTorchModeOn(level: 0.9) + } else { + camera.torchMode = .off + } + } catch { + } + } + } + + open func setupOutput() { + } + + open func configure(doing: () -> Void) { + lock = lock + 1 + doing() + lock = lock - 1 + } +} diff --git a/CameraParticles/CameraParticles/_Capture/QRCodeCapture.swift b/CameraParticles/CameraParticles/_Capture/QRCodeCapture.swift new file mode 100644 index 000000000..c5c8c548a --- /dev/null +++ b/CameraParticles/CameraParticles/_Capture/QRCodeCapture.swift @@ -0,0 +1,32 @@ +// +// VidepProcessingCapture.swift +// CameraParticles +// +// Created by Qiang Huang on 6/12/20. +// + +import AVFoundation +import Foundation + +@objc open class QRCodeCapture: CameraCapture, AVCaptureMetadataOutputObjectsDelegate { + @objc public dynamic var qrcode: String? { + didSet { + if qrcode != oldValue { + AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) + } + } + } + + override open func setupOutput() { + let metadataOutput = AVCaptureMetadataOutput() + output = metadataOutput + metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) + metadataOutput.metadataObjectTypes = [.qr] + } + + public func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { + if let metadataObject = metadataObjects.first, let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject { + qrcode = readableObject.stringValue + } + } +} diff --git a/CameraParticles/CameraParticles/_Capture/VidepProcessingCapture.swift b/CameraParticles/CameraParticles/_Capture/VidepProcessingCapture.swift new file mode 100644 index 000000000..42d7e3e7d --- /dev/null +++ b/CameraParticles/CameraParticles/_Capture/VidepProcessingCapture.swift @@ -0,0 +1,39 @@ +// +// VidepProcessingCapture.swift +// CameraParticles +// +// Created by Qiang Huang on 6/12/20. +// + +import AVFoundation +import Foundation + +@objc open class VideoProcessingCapture: CameraCapture { + @objc open dynamic var videoProcessing: AVCaptureVideoDataOutputSampleBufferDelegate? { + didSet { + if videoProcessing !== oldValue { + setupProcessing() + } + } + } + + open override func setupOutput() { + let output = AVCaptureVideoDataOutput() + output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: NSNumber(value: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)] + output.alwaysDiscardsLateVideoFrames = true + self.output = output + + setupProcessing() + } + + open func setupProcessing() { + if let videoOutput = output as? AVCaptureVideoDataOutput { + if let videoProcessing = videoProcessing { + let queue = DispatchQueue(label: "video.processing") + videoOutput.setSampleBufferDelegate(videoProcessing, queue: queue) + } else { + videoOutput.setSampleBufferDelegate(nil, queue: nil) + } + } + } +} diff --git a/CameraParticles/CameraParticles/_Scanner/VideoScanner.swift b/CameraParticles/CameraParticles/_Scanner/VideoScanner.swift new file mode 100644 index 000000000..1d95dd425 --- /dev/null +++ b/CameraParticles/CameraParticles/_Scanner/VideoScanner.swift @@ -0,0 +1,60 @@ +// +// VideoScanner.swift +// CameraParticles +// +// Created by Qiang Huang on 6/14/20. +// + +import AVFoundation +import Foundation +import UIKit + +@objc open class VideoScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { + let context: CIContext = CIContext(options: nil) + + @objc open dynamic var scanning: Bool = false + + open func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + if scanning { + process(buffer: sampleBuffer) + } + } + + open func process(buffer: CMSampleBuffer) { + } + + open func process(image: CIImage) { + } + + public func ciImage(buffer: CMSampleBuffer?) -> CIImage? { + if let buffer = buffer, let cvBuffer = CMSampleBufferGetImageBuffer(buffer) { + return CIImage(cvPixelBuffer: cvBuffer) + } + return nil + } + + public func cgImage(ciImage: CIImage?) -> CGImage? { + if let ciImage = ciImage { + return context.createCGImage(ciImage, from: ciImage.extent) + } + return nil + } + + public func image(cgImage: CGImage?) -> UIImage? { + if let cgImage = cgImage { + return UIImage(cgImage: cgImage) + } + return nil + } + + public func image(ciImage: CIImage?) -> UIImage? { + if let ciImage = ciImage { + return UIImage(ciImage: ciImage, scale: 1.0, orientation: .up) + } + return nil + } + + public func image(buffer: CMSampleBuffer?) -> UIImage? { + return image(cgImage: cgImage(ciImage: ciImage(buffer: buffer))) + } +} diff --git a/CameraParticles/CameraParticles/_Scanner/VideoTextScanner.swift b/CameraParticles/CameraParticles/_Scanner/VideoTextScanner.swift new file mode 100644 index 000000000..2e5a42ffb --- /dev/null +++ b/CameraParticles/CameraParticles/_Scanner/VideoTextScanner.swift @@ -0,0 +1,57 @@ +// +// VideoTextScanner.swift +// CameraParticles +// +// Created by Qiang Huang on 6/14/20. +// + +import AVFoundation +import CoreImage +import UIToolkits +import Utilities + +@objc public protocol VideoTextScannerDelegate { + func receiving(_ scanner: VideoTextScanner, buffer: CMSampleBuffer) + func scanner(_ scanner: VideoTextScanner, buffer: CMSampleBuffer, strings: [String: Set]) +} + +@objc open class VideoTextScanner: VideoScanner { + public weak var delegate: VideoTextScannerDelegate? + public var scanner: ScannerProtocol? + + internal var scanDebouncer: Debouncer = { + let debouncer = Debouncer() + debouncer.fifo = true + return debouncer + }() + + open override func process(buffer: CMSampleBuffer) { + if let _ = delegate, let _ = scanner { + if let handler = scanDebouncer.debounce() { + handler.run({ [weak self] in + if let self = self { + self.delegate?.receiving(self, buffer: buffer) + self.scan(buffer: buffer) {[weak self] strings, _ in + if let self = self, let strings = strings { + self.delegate?.scanner(self, buffer: buffer, strings: strings) + } + } + self.scanDebouncer.current = nil + } + }, delay: 0) + } + } + } + + open func scan(buffer: CMSampleBuffer, completion: @escaping ScanCompletionBlock) { + scanner?.scan(buffer: buffer, completion: { [weak self] strings, error in + if let strings = self?.postProcess(strings: strings) { + completion(strings, error) + } + }) + } + + open func postProcess(strings: [String: Set]?) -> [String: Set]? { + return strings + } +} diff --git a/CameraParticles/CameraParticles/_ViewControllers/CameraViewController.swift b/CameraParticles/CameraParticles/_ViewControllers/CameraViewController.swift new file mode 100644 index 000000000..a2dbae590 --- /dev/null +++ b/CameraParticles/CameraParticles/_ViewControllers/CameraViewController.swift @@ -0,0 +1,178 @@ +// +// CameraViewController.swift +// CameraParticles +// +// Created by Qiang Huang on 6/12/20. +// + +import AVFoundation +import PlatformParticles +import RoutingKit +import UIToolkits +import Utilities + +@objc open class CameraViewController: TrackingViewController { + @IBOutlet open var cameraPermissionView: UIView? + @IBOutlet open var cameraPermissionLabel: UILabel? + + @IBOutlet open var cameraPermissionButton: ButtonProtocol? { + didSet { + if cameraPermissionButton !== oldValue { + oldValue?.removeTarget() + cameraPermissionButton?.addTarget(self, action: #selector(permit(_:))) + } + } + } + + open var cameraPermission: CameraPermission? { + didSet { + changeObservation(from: oldValue, to: cameraPermission, keyPath: #keyPath(CameraPermission.authorization)) { [weak self] _, _, _, _ in + self?.updateCameraPermission() + } + } + } + + open var cameraPermissionText: String? { + return "You need to enable camera" + } + + @IBOutlet open var cameraButton: ButtonProtocol? { + didSet { + if cameraButton !== oldValue { + oldValue?.removeTarget() + cameraButton?.addTarget(self, action: #selector(camera(_:))) + } + } + } + + @IBOutlet open var flashButton: ButtonProtocol? { + didSet { + if flashButton !== oldValue { + oldValue?.removeTarget() + flashButton?.addTarget(self, action: #selector(light(_:))) + } + } + } + + @IBOutlet open var exitButton: UIButton? { + didSet { + if exitButton !== oldValue { + oldValue?.removeTarget() + exitButton?.addTarget(self, action: #selector(dismiss(_:))) + } + } + } + + @IBOutlet open var preview: UIView? + @IBOutlet open var review: UIImageView? + + @objc open dynamic var capture: CameraCapture? { + didSet { + changeObservation(from: oldValue, to: capture, keyPath: #keyPath(CameraCapture.previewLayer)) { [weak self] _, _, _, _ in + if let self = self { + self.previewLayer = self.capture?.previewLayer + } + } + changeObservation(from: oldValue, to: capture, keyPath: #keyPath(CameraCapture.light)) { [weak self] _, _, _, _ in + if let self = self { + self.updateLight() + } + } + } + } + + open var previewLayer: AVCaptureVideoPreviewLayer? { + didSet { + if previewLayer !== oldValue { + oldValue?.removeFromSuperlayer() + if let preview = preview, let previewLayer = previewLayer { + previewLayer.frame = preview.bounds + preview.layer.insertSublayer(previewLayer, at: 0) + } + } + } + } + + override open func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + cameraPermission = CameraPermission.shared + navigationController?.navigationBar.transparent = true + } + + override open func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + capture?.running = false + capture = nil + } + + override open func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + setupCamera() + } + + private func setupCamera() { + if !UIDevice.current.isSimulator, cameraPermission?.authorization == .authorized { + if let capture = createCapture() { + capture.configure { [weak self] in + if let self = self { + self.setup(capture: capture) + self.capture = capture + } + } + capture.running = true + } + } + } + + open func createCapture() -> CameraCapture? { + return nil + } + + open func setup(capture: CameraCapture) { + capture.setup() + } + + @IBAction open func camera(_ sender: Any?) { + if let capture = capture { + capture.front = !capture.front + } + } + + @IBAction open func light(_ sender: Any?) { + if let capture = capture { + capture.light = !capture.light + } + } + + @IBAction open func permit(_ sender: Any?) { + Router.shared?.navigate(to: RoutingRequest(path: "/authorization/camera"), animated: true, completion: nil) + } + + open func updateCameraPermission() { + if let cameraPermission = cameraPermission { + switch cameraPermission.authorization { + case .authorized: + setupCamera() + cameraPermissionView?.visible = false + + + default: + cameraPermissionView?.bringToFront() + exitButton?.bringToFront() + cameraPermissionView?.visible = true + cameraPermissionLabel?.text = cameraPermissionText + } + } else { + cameraPermissionView?.visible = false + } + } + + open func updateLight() { + if capture?.light ?? false { + flashButton?.buttonImage = UIImage.named("flashlight.off.fill", bundles: Bundle.particles) + } else { + flashButton?.buttonImage = UIImage.named("flashlight.on.fill", bundles: Bundle.particles) + } + } +} diff --git a/CameraParticles/CameraParticles/_ViewControllers/QRCodeViewController.swift b/CameraParticles/CameraParticles/_ViewControllers/QRCodeViewController.swift new file mode 100644 index 000000000..e1bb3fdb1 --- /dev/null +++ b/CameraParticles/CameraParticles/_ViewControllers/QRCodeViewController.swift @@ -0,0 +1,35 @@ +// +// QRCodeViewController.swift +// QRCodeViewController +// +// Created by Qiang Huang on 9/2/21. +// Copyright © 2021 dYdX Trading, Inc. All rights reserved. +// + +@objc open class QRCodeViewController: CameraViewController { + @Published public var qrcode: String? + + override open var capture: CameraCapture? { + didSet { + changeObservation(from: oldValue, to: qrcodeCapture, keyPath: #keyPath(QRCodeCapture.qrcode)) { [weak self] _, _, _, _ in + self?.process(qrcode: self?.qrcodeCapture?.qrcode) + } + } + } + + public var qrcodeCapture: QRCodeCapture? { + return capture as? QRCodeCapture + } + + override open func createCapture() -> CameraCapture? { + if let capture = capture { + return capture + } else { + return QRCodeCapture() + } + } + + open func process(qrcode: String?) { + self.qrcode = qrcode + } +} diff --git a/CameraParticles/CameraParticles/_ViewControllers/TextVideoProcessingViewController.swift b/CameraParticles/CameraParticles/_ViewControllers/TextVideoProcessingViewController.swift new file mode 100644 index 000000000..443dca4d2 --- /dev/null +++ b/CameraParticles/CameraParticles/_ViewControllers/TextVideoProcessingViewController.swift @@ -0,0 +1,180 @@ +// +// OCRVideoProcessingViewController.swift +// CameraParticles +// +// Created by Qiang Huang on 6/15/20. +// + +import AVFoundation +import Foundation +import RoutingKit +import UIToolkits +import Utilities + +@objc open class TextVideoProcessingViewController: VideoProcessingViewController, VideoTextScannerDelegate { + @IBInspectable open var offColor: UIColor? + @IBInspectable open var onColor: UIColor? + @IBOutlet open var viewFinder: OverlayView? + @IBOutlet open var markers: [UIView]? + + var overlayColor: UIColor? { + didSet { + if overlayColor !== oldValue { + viewFinder?.overlayColor = overlayColor + if let markers = markers { + for marker in markers { + marker.backgroundColor = overlayColor + } + } + } + } + } + + @IBInspectable open var path: String? + + @IBInspectable open var scannerType: String? { + didSet { + if scannerType != oldValue { + } + } + } + + open var data: [String: Set] = [:] + + open var textScanner: VideoTextScanner? { + return videoProcessor as? VideoTextScanner + } + + override open var videoProcessor: VideoScanner? { + didSet { + if videoProcessor !== oldValue { + (capture as? VideoProcessingCapture)?.videoProcessing = videoProcessor + textScanner?.delegate = self + } + } + } + + override open var capture: CameraCapture? { + didSet { + if capture !== oldValue { + (capture as? VideoProcessingCapture)?.videoProcessing = videoProcessor + } + } + } + + override open func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + updateScanningStatus() + } + + override open func updateTo(scanningStatus: EScanningStatus) { + super.updateTo(scanningStatus: scanningStatus) + + switch scanningStatus { + case .idle: + break + + case .scanning: + data = [:] + play(sound: soundScanning()) + + case .processing: + play(sound: sound(types: data)) + complete(data: data) + break + + default: + break + } + updateScanningStatus() + } + + open func updateScanningStatus() { + overlayColor = color(scanningStatus: scanningStatus) + } + + open func color(scanningStatus: EScanningStatus) -> UIColor { + switch scanningStatus { + case .scanning: + return (onColor ?? UIColor(white: 0.0, alpha: 0.33)) + + default: + return (offColor ?? UIColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 0.33)) + } + } + + override open func createCapture() -> CameraCapture? { + let scanner = VideoTextScanner() + scanner.scanner = Scanners.shared.scanner(type: scannerType ?? "QRCode") + videoProcessor = scanner + let capture = VideoProcessingCapture() + return capture + } + + override open func setup(capture: CameraCapture) { + super.setup(capture: capture) + } + + public func receiving(_ scanner: VideoTextScanner, buffer: CMSampleBuffer) { + } + + open func scanner(_ scanner: VideoTextScanner, buffer: CMSampleBuffer, strings: [String: Set]) { + if scanningStatus == .scanning { + if add(strings: strings) { + dataAdded() + } + } + } + + open func dataAdded() { + scanningStatus = .processing + } + + public func add(strings: [String: Set]) -> Bool { + var hasNew = false + var scannedTypes: Set = [] + for type in strings.keys { + if let value = strings[type] { + var set: Set = data[type] ?? [] + for each in value { + if !set.contains(each) { + set.insert(each) + scannedTypes.insert(type) + hasNew = true + } + } + data[type] = set + } + } + return hasNew + } + + open func shouldFinish() -> Bool { + return false + } + + open func play(sound: UInt32) { + if sound != 0 { + AudioServicesPlayAlertSound(SystemSoundID(sound)) + } + } + + open func soundScanning() -> UInt32 { + return 0 + } + + open func sound(types: [String: Set]) -> UInt32 { + return 0 + } + + open func complete(data: [String: Set]) { + } + + override open func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { + if request?.path == path || path == nil { + scannerType = request?.params?["barcode"] as? String + return true + } + return false + } +} diff --git a/CameraParticles/CameraParticles/_ViewControllers/VideoProcessingViewController.swift b/CameraParticles/CameraParticles/_ViewControllers/VideoProcessingViewController.swift new file mode 100644 index 000000000..b4f6f10c2 --- /dev/null +++ b/CameraParticles/CameraParticles/_ViewControllers/VideoProcessingViewController.swift @@ -0,0 +1,46 @@ +// +// VideoProcessingViewController.swift +// CameraParticles +// +// Created by Qiang Huang on 6/12/20. +// + +import AVFoundation +import Foundation + +@objc public enum EScanningStatus: Int { + case idle + case scanning + case processing +} + +@objc open class VideoProcessingViewController: CameraViewController { + open var scanningStatus: EScanningStatus = .idle { + didSet { + if scanningStatus != oldValue { + updateTo(scanningStatus: scanningStatus) + } + } + } + + public var videoCapture: VideoProcessingCapture? { + return capture as? VideoProcessingCapture + } + + open var videoProcessor: VideoScanner? { + didSet { + if videoProcessor !== oldValue { + videoCapture?.videoProcessing = videoProcessor + videoProcessor?.scanning = (scanningStatus == .scanning) + } + } + } + + override open func createCapture() -> CameraCapture? { + return VideoProcessingCapture() + } + + open func updateTo(scanningStatus: EScanningStatus) { + videoProcessor?.scanning = (scanningStatus == .scanning) + } +} diff --git a/CameraParticles/CameraParticlesTests/CameraParticlesTests.swift b/CameraParticles/CameraParticlesTests/CameraParticlesTests.swift new file mode 100644 index 000000000..d5c18b5fe --- /dev/null +++ b/CameraParticles/CameraParticlesTests/CameraParticlesTests.swift @@ -0,0 +1,33 @@ +// +// CameraParticlesTests.swift +// CameraParticlesTests +// +// Created by Qiang Huang on 6/12/20. +// + +import XCTest +@testable import CameraParticles + +class CameraParticlesTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/CameraParticles/CameraParticlesTests/Info.plist b/CameraParticles/CameraParticlesTests/Info.plist new file mode 100644 index 000000000..64d65ca49 --- /dev/null +++ b/CameraParticles/CameraParticlesTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/FirebaseStaticInjections/FirebaseStaticInjections.xcodeproj/project.pbxproj b/FirebaseStaticInjections/FirebaseStaticInjections.xcodeproj/project.pbxproj new file mode 100644 index 000000000..5faba4689 --- /dev/null +++ b/FirebaseStaticInjections/FirebaseStaticInjections.xcodeproj/project.pbxproj @@ -0,0 +1,1059 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 314B5E4723DCCD6800139EB3 /* FirebaseRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5E3823DCCD6700139EB3 /* FirebaseRunner.swift */; }; + 314B5E4923DCCD6800139EB3 /* CrashlyticsErrorLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5E3C23DCCD6700139EB3 /* CrashlyticsErrorLogging.swift */; }; + 314B5E4A23DCCD6800139EB3 /* FirebaseTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5E3E23DCCD6700139EB3 /* FirebaseTracking.swift */; }; + 314B5E4B23DCCD6800139EB3 /* FirebaseNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5E4023DCCD6800139EB3 /* FirebaseNotification.swift */; }; + 31B3E52F22763ED5009E8FEF /* FirebaseStaticInjectionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B3E52E22763ED5009E8FEF /* FirebaseStaticInjectionsTests.swift */; }; + 31B3E53122763ED5009E8FEF /* libFirebaseStaticInjections.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31B3E3BD22763B19009E8FEF /* libFirebaseStaticInjections.a */; }; + 462A35FDB56C49B164E4A815 /* Pods_iOS_FirebaseStaticInjections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98711AA66CE5F7FF3B89CC71 /* Pods_iOS_FirebaseStaticInjections.framework */; }; + D6DEC39DD1C2D90EF8486C38 /* Pods_iOS_FirebaseStaticInjectionsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C415DDDB0A07C7C3D5505D2A /* Pods_iOS_FirebaseStaticInjectionsTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 31A85FAB22E3C5080055DA3F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31A85F9F22E3C5080055DA3F /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB72921B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 31A85FAD22E3C5080055DA3F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31A85F9F22E3C5080055DA3F /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB7C621BB592E00BEF926; + remoteInfo = PlatformParticlesAppleWatch; + }; + 31A85FAF22E3C5080055DA3F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31A85F9F22E3C5080055DA3F /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB4321BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTV; + }; + 31A85FB322E3C5080055DA3F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31A85F9F22E3C5080055DA3F /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CFF7C121D7D1F200DE7A79; + remoteInfo = MessageParticles; + }; + 31A85FB522E3C5080055DA3F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31A85F9F22E3C5080055DA3F /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB73221B0EE2D00391ADF; + remoteInfo = PlatformParticlesTests; + }; + 31A85FB722E3C5080055DA3F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31A85F9F22E3C5080055DA3F /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB4B21BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTVTests; + }; + 31A85FBB22E3C5080055DA3F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31A85F9F22E3C5080055DA3F /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CFF7C921D7D1F300DE7A79; + remoteInfo = MessageParticlesTests; + }; + 31A85FBD22E3C50F0055DA3F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31A85F9F22E3C5080055DA3F /* PlatformParticles.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31ACB72821B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 31B3E41722763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E40D22763B59009E8FEF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 31B3E41922763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E40D22763B59009E8FEF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 31B3E41B22763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E40D22763B59009E8FEF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 31B3E41F22763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E40D22763B59009E8FEF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 31B3E42122763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E40D22763B59009E8FEF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 31B3E42F22763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E42522763B59009E8FEF /* PlatformRouting.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CEE9B52170FA2700DC61DA; + remoteInfo = PlatformRouting; + }; + 31B3E43122763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E42522763B59009E8FEF /* PlatformRouting.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB75F21BB4CA300BEF926; + remoteInfo = PlatformRoutingAppleWatch; + }; + 31B3E43322763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E42522763B59009E8FEF /* PlatformRouting.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBBE521BB7B7D00BEF926; + remoteInfo = PlatformRoutingAppleTV; + }; + 31B3E43722763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E42522763B59009E8FEF /* PlatformRouting.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CEE9BE2170FA2700DC61DA; + remoteInfo = PlatformRoutingTests; + }; + 31B3E43922763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E42522763B59009E8FEF /* PlatformRouting.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBBED21BB7B7D00BEF926; + remoteInfo = PlatformRoutingAppleTVTests; + }; + 31B3E44722763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E43D22763B59009E8FEF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FDA21B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 31B3E44922763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E43D22763B59009E8FEF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 319682B421B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 31B3E44B22763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E43D22763B59009E8FEF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16121BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 31B3E44F22763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E43D22763B59009E8FEF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FE321B0E9C4001775BA; + remoteInfo = ParticlesKitTests; + }; + 31B3E45122763B59009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E43D22763B59009E8FEF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16921BA246A00BEF926; + remoteInfo = ParticlesKitAppleTVTests; + }; + 31B3E45522763B64009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E42522763B59009E8FEF /* PlatformRouting.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31CEE9B42170FA2700DC61DA; + remoteInfo = PlatformRouting; + }; + 31B3E45722763B64009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E40D22763B59009E8FEF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 31B3E45922763B64009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E43D22763B59009E8FEF /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 311C0FD921B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 31B3E53222763ED5009E8FEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31B3E3B522763B19009E8FEF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31B3E3BC22763B19009E8FEF; + remoteInfo = FirebaseStaticInjections; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 31B3E3BB22763B19009E8FEF /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2AC82E9412F0658CCD3B5F99 /* Pods-iOS-FirebaseStaticInjectionsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-FirebaseStaticInjectionsTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-FirebaseStaticInjectionsTests/Pods-iOS-FirebaseStaticInjectionsTests.debug.xcconfig"; sourceTree = ""; }; + 3101FAF82511444A00AC4010 /* GoogleLogin.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GoogleLogin.xib; sourceTree = ""; }; + 314B5E3823DCCD6700139EB3 /* FirebaseRunner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirebaseRunner.swift; sourceTree = ""; }; + 314B5E3C23DCCD6700139EB3 /* CrashlyticsErrorLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashlyticsErrorLogging.swift; sourceTree = ""; }; + 314B5E3E23DCCD6700139EB3 /* FirebaseTracking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirebaseTracking.swift; sourceTree = ""; }; + 314B5E4023DCCD6800139EB3 /* FirebaseNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirebaseNotification.swift; sourceTree = ""; }; + 314C365024C7C77100695F7E /* FirebaseLogin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FirebaseLogin.xib; sourceTree = ""; }; + 31A85F9F22E3C5080055DA3F /* PlatformParticles.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PlatformParticles.xcodeproj; path = ../PlatformParticles/PlatformParticles.xcodeproj; sourceTree = ""; }; + 31B3E3BD22763B19009E8FEF /* libFirebaseStaticInjections.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFirebaseStaticInjections.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 31B3E40D22763B59009E8FEF /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 31B3E42522763B59009E8FEF /* PlatformRouting.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PlatformRouting.xcodeproj; path = ../PlatformRouting/PlatformRouting.xcodeproj; sourceTree = ""; }; + 31B3E43D22763B59009E8FEF /* ParticlesKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParticlesKit.xcodeproj; path = ../ParticlesKit/ParticlesKit.xcodeproj; sourceTree = ""; }; + 31B3E52C22763ED5009E8FEF /* FirebaseStaticInjectionsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FirebaseStaticInjectionsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 31B3E52E22763ED5009E8FEF /* FirebaseStaticInjectionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseStaticInjectionsTests.swift; sourceTree = ""; }; + 31B3E53022763ED5009E8FEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7AF31C47E0D98E7411788154 /* Pods-iOS-FirebaseStaticInjectionsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-FirebaseStaticInjectionsTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-FirebaseStaticInjectionsTests/Pods-iOS-FirebaseStaticInjectionsTests.release.xcconfig"; sourceTree = ""; }; + 98711AA66CE5F7FF3B89CC71 /* Pods_iOS_FirebaseStaticInjections.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_FirebaseStaticInjections.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B53FEE6286BC8AE67AA8289D /* Pods-iOS-FirebaseStaticInjections.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-FirebaseStaticInjections.debug.xcconfig"; path = "Target Support Files/Pods-iOS-FirebaseStaticInjections/Pods-iOS-FirebaseStaticInjections.debug.xcconfig"; sourceTree = ""; }; + C415DDDB0A07C7C3D5505D2A /* Pods_iOS_FirebaseStaticInjectionsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_FirebaseStaticInjectionsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DF7BF46C45A090E041492199 /* Pods-iOS-FirebaseStaticInjections.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-FirebaseStaticInjections.release.xcconfig"; path = "Target Support Files/Pods-iOS-FirebaseStaticInjections/Pods-iOS-FirebaseStaticInjections.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 31B3E3BA22763B19009E8FEF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 462A35FDB56C49B164E4A815 /* Pods_iOS_FirebaseStaticInjections.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31B3E52922763ED5009E8FEF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31B3E53122763ED5009E8FEF /* libFirebaseStaticInjections.a in Frameworks */, + D6DEC39DD1C2D90EF8486C38 /* Pods_iOS_FirebaseStaticInjectionsTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02684EC628BD41320007CEFF /* Dependencies */ = { + isa = PBXGroup; + children = ( + 31A85F9F22E3C5080055DA3F /* PlatformParticles.xcodeproj */, + 31B3E43D22763B59009E8FEF /* ParticlesKit.xcodeproj */, + 31B3E42522763B59009E8FEF /* PlatformRouting.xcodeproj */, + 31B3E40D22763B59009E8FEF /* Utilities.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 314B5E3723DCCD6700139EB3 /* _Configure */ = { + isa = PBXGroup; + children = ( + 314B5E3823DCCD6700139EB3 /* FirebaseRunner.swift */, + ); + path = _Configure; + sourceTree = ""; + }; + 314B5E3B23DCCD6700139EB3 /* _ErrorLogging */ = { + isa = PBXGroup; + children = ( + 314B5E3C23DCCD6700139EB3 /* CrashlyticsErrorLogging.swift */, + ); + path = _ErrorLogging; + sourceTree = ""; + }; + 314B5E3D23DCCD6700139EB3 /* _Tracking */ = { + isa = PBXGroup; + children = ( + 314B5E3E23DCCD6700139EB3 /* FirebaseTracking.swift */, + ); + path = _Tracking; + sourceTree = ""; + }; + 314B5E3F23DCCD6800139EB3 /* _Notification */ = { + isa = PBXGroup; + children = ( + 314B5E4023DCCD6800139EB3 /* FirebaseNotification.swift */, + ); + path = _Notification; + sourceTree = ""; + }; + 314C363924C7C74800695F7E /* Resources */ = { + isa = PBXGroup; + children = ( + 314C364D24C7C75400695F7E /* _iOS */, + ); + path = Resources; + sourceTree = ""; + }; + 314C364D24C7C75400695F7E /* _iOS */ = { + isa = PBXGroup; + children = ( + 314C364E24C7C75C00695F7E /* _Routing */, + ); + path = _iOS; + sourceTree = ""; + }; + 314C364E24C7C75C00695F7E /* _Routing */ = { + isa = PBXGroup; + children = ( + 314C364F24C7C76100695F7E /* _Xib */, + ); + path = _Routing; + sourceTree = ""; + }; + 314C364F24C7C76100695F7E /* _Xib */ = { + isa = PBXGroup; + children = ( + 3101FAF82511444A00AC4010 /* GoogleLogin.xib */, + 314C365024C7C77100695F7E /* FirebaseLogin.xib */, + ); + path = _Xib; + sourceTree = ""; + }; + 31A85FA022E3C5080055DA3F /* Products */ = { + isa = PBXGroup; + children = ( + 31A85FAC22E3C5080055DA3F /* PlatformParticles.framework */, + 31A85FAE22E3C5080055DA3F /* PlatformParticles.framework */, + 31A85FB022E3C5080055DA3F /* PlatformParticles.framework */, + 31A85FB422E3C5080055DA3F /* MessageParticles.framework */, + 31A85FB622E3C5080055DA3F /* PlatformParticlesTests.xctest */, + 31A85FB822E3C5080055DA3F /* PlatformParticlesAppleTVTests.xctest */, + 31A85FBC22E3C5080055DA3F /* MessageParticlesTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31B3E3B422763B19009E8FEF = { + isa = PBXGroup; + children = ( + 02684EC628BD41320007CEFF /* Dependencies */, + 31B3E3BF22763B19009E8FEF /* FirebaseStaticInjections */, + 31B3E52D22763ED5009E8FEF /* FirebaseStaticInjectionsTests */, + 31B3E3BE22763B19009E8FEF /* Products */, + AF7F81BC40F041FCE5305B98 /* Pods */, + D212FF4652818B626E9FD6F1 /* Frameworks */, + ); + sourceTree = ""; + }; + 31B3E3BE22763B19009E8FEF /* Products */ = { + isa = PBXGroup; + children = ( + 31B3E3BD22763B19009E8FEF /* libFirebaseStaticInjections.a */, + 31B3E52C22763ED5009E8FEF /* FirebaseStaticInjectionsTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31B3E3BF22763B19009E8FEF /* FirebaseStaticInjections */ = { + isa = PBXGroup; + children = ( + 314B5E3723DCCD6700139EB3 /* _Configure */, + 314B5E3B23DCCD6700139EB3 /* _ErrorLogging */, + 314B5E3F23DCCD6800139EB3 /* _Notification */, + 314B5E3D23DCCD6700139EB3 /* _Tracking */, + 314C363924C7C74800695F7E /* Resources */, + ); + path = FirebaseStaticInjections; + sourceTree = ""; + }; + 31B3E40E22763B59009E8FEF /* Products */ = { + isa = PBXGroup; + children = ( + 31B3E41822763B59009E8FEF /* Utilities.framework */, + 31B3E41A22763B59009E8FEF /* Utilities.framework */, + 31B3E41C22763B59009E8FEF /* Utilities.framework */, + 31B3E42022763B59009E8FEF /* UtilitiesTests.xctest */, + 31B3E42222763B59009E8FEF /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31B3E42622763B59009E8FEF /* Products */ = { + isa = PBXGroup; + children = ( + 31B3E43022763B59009E8FEF /* PlatformRouting.framework */, + 31B3E43222763B59009E8FEF /* PlatformRouting.framework */, + 31B3E43422763B59009E8FEF /* PlatformRouting.framework */, + 31B3E43822763B59009E8FEF /* PlatformRoutingTests.xctest */, + 31B3E43A22763B59009E8FEF /* PlatformRoutingAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31B3E43E22763B59009E8FEF /* Products */ = { + isa = PBXGroup; + children = ( + 31B3E44822763B59009E8FEF /* ParticlesKit.framework */, + 31B3E44A22763B59009E8FEF /* ParticlesKit.framework */, + 31B3E44C22763B59009E8FEF /* ParticlesKit.framework */, + 31B3E45022763B59009E8FEF /* ParticlesKitTests.xctest */, + 31B3E45222763B59009E8FEF /* ParticlesKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31B3E52D22763ED5009E8FEF /* FirebaseStaticInjectionsTests */ = { + isa = PBXGroup; + children = ( + 31B3E52E22763ED5009E8FEF /* FirebaseStaticInjectionsTests.swift */, + 31B3E53022763ED5009E8FEF /* Info.plist */, + ); + path = FirebaseStaticInjectionsTests; + sourceTree = ""; + }; + AF7F81BC40F041FCE5305B98 /* Pods */ = { + isa = PBXGroup; + children = ( + B53FEE6286BC8AE67AA8289D /* Pods-iOS-FirebaseStaticInjections.debug.xcconfig */, + DF7BF46C45A090E041492199 /* Pods-iOS-FirebaseStaticInjections.release.xcconfig */, + 2AC82E9412F0658CCD3B5F99 /* Pods-iOS-FirebaseStaticInjectionsTests.debug.xcconfig */, + 7AF31C47E0D98E7411788154 /* Pods-iOS-FirebaseStaticInjectionsTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; + D212FF4652818B626E9FD6F1 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 98711AA66CE5F7FF3B89CC71 /* Pods_iOS_FirebaseStaticInjections.framework */, + C415DDDB0A07C7C3D5505D2A /* Pods_iOS_FirebaseStaticInjectionsTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 31B3E3BC22763B19009E8FEF /* FirebaseStaticInjections */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31B3E3C422763B19009E8FEF /* Build configuration list for PBXNativeTarget "FirebaseStaticInjections" */; + buildPhases = ( + 2392E21FBB5775A2A4C80A4C /* [CP] Check Pods Manifest.lock */, + 31B3E3B922763B19009E8FEF /* Sources */, + 31B3E3BA22763B19009E8FEF /* Frameworks */, + 31B3E3BB22763B19009E8FEF /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 31A85FBE22E3C50F0055DA3F /* PBXTargetDependency */, + 31B3E45622763B64009E8FEF /* PBXTargetDependency */, + 31B3E45822763B64009E8FEF /* PBXTargetDependency */, + 31B3E45A22763B64009E8FEF /* PBXTargetDependency */, + ); + name = FirebaseStaticInjections; + productName = FirebaseStaticInjections; + productReference = 31B3E3BD22763B19009E8FEF /* libFirebaseStaticInjections.a */; + productType = "com.apple.product-type.library.static"; + }; + 31B3E52B22763ED5009E8FEF /* FirebaseStaticInjectionsTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31B3E53422763ED5009E8FEF /* Build configuration list for PBXNativeTarget "FirebaseStaticInjectionsTests" */; + buildPhases = ( + D235A5D738179D856B66C201 /* [CP] Check Pods Manifest.lock */, + 31B3E52822763ED5009E8FEF /* Sources */, + 31B3E52922763ED5009E8FEF /* Frameworks */, + 31B3E52A22763ED5009E8FEF /* Resources */, + 8B2ACFD82C7387329C9B46A2 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 31B3E53322763ED5009E8FEF /* PBXTargetDependency */, + ); + name = FirebaseStaticInjectionsTests; + productName = FirebaseStaticInjectionsTests; + productReference = 31B3E52C22763ED5009E8FEF /* FirebaseStaticInjectionsTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 31B3E3B522763B19009E8FEF /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Original; + LastSwiftUpdateCheck = 1020; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "dYdX Trading Inc"; + TargetAttributes = { + 31B3E3BC22763B19009E8FEF = { + CreatedOnToolsVersion = 10.2.1; + }; + 31B3E52B22763ED5009E8FEF = { + CreatedOnToolsVersion = 10.2.1; + }; + }; + }; + buildConfigurationList = 31B3E3B822763B19009E8FEF /* Build configuration list for PBXProject "FirebaseStaticInjections" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 31B3E3B422763B19009E8FEF; + productRefGroup = 31B3E3BE22763B19009E8FEF /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 31B3E43E22763B59009E8FEF /* Products */; + ProjectRef = 31B3E43D22763B59009E8FEF /* ParticlesKit.xcodeproj */; + }, + { + ProductGroup = 31A85FA022E3C5080055DA3F /* Products */; + ProjectRef = 31A85F9F22E3C5080055DA3F /* PlatformParticles.xcodeproj */; + }, + { + ProductGroup = 31B3E42622763B59009E8FEF /* Products */; + ProjectRef = 31B3E42522763B59009E8FEF /* PlatformRouting.xcodeproj */; + }, + { + ProductGroup = 31B3E40E22763B59009E8FEF /* Products */; + ProjectRef = 31B3E40D22763B59009E8FEF /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 31B3E3BC22763B19009E8FEF /* FirebaseStaticInjections */, + 31B3E52B22763ED5009E8FEF /* FirebaseStaticInjectionsTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 31A85FAC22E3C5080055DA3F /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 31A85FAB22E3C5080055DA3F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31A85FAE22E3C5080055DA3F /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 31A85FAD22E3C5080055DA3F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31A85FB022E3C5080055DA3F /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 31A85FAF22E3C5080055DA3F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31A85FB422E3C5080055DA3F /* MessageParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MessageParticles.framework; + remoteRef = 31A85FB322E3C5080055DA3F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31A85FB622E3C5080055DA3F /* PlatformParticlesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformParticlesTests.xctest; + remoteRef = 31A85FB522E3C5080055DA3F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31A85FB822E3C5080055DA3F /* PlatformParticlesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformParticlesAppleTVTests.xctest; + remoteRef = 31A85FB722E3C5080055DA3F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31A85FBC22E3C5080055DA3F /* MessageParticlesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = MessageParticlesTests.xctest; + remoteRef = 31A85FBB22E3C5080055DA3F /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E41822763B59009E8FEF /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 31B3E41722763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E41A22763B59009E8FEF /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 31B3E41922763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E41C22763B59009E8FEF /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 31B3E41B22763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E42022763B59009E8FEF /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 31B3E41F22763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E42222763B59009E8FEF /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 31B3E42122763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E43022763B59009E8FEF /* PlatformRouting.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformRouting.framework; + remoteRef = 31B3E42F22763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E43222763B59009E8FEF /* PlatformRouting.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformRouting.framework; + remoteRef = 31B3E43122763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E43422763B59009E8FEF /* PlatformRouting.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformRouting.framework; + remoteRef = 31B3E43322763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E43822763B59009E8FEF /* PlatformRoutingTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformRoutingTests.xctest; + remoteRef = 31B3E43722763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E43A22763B59009E8FEF /* PlatformRoutingAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformRoutingAppleTVTests.xctest; + remoteRef = 31B3E43922763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E44822763B59009E8FEF /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31B3E44722763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E44A22763B59009E8FEF /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31B3E44922763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E44C22763B59009E8FEF /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31B3E44B22763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E45022763B59009E8FEF /* ParticlesKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitTests.xctest; + remoteRef = 31B3E44F22763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31B3E45222763B59009E8FEF /* ParticlesKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitAppleTVTests.xctest; + remoteRef = 31B3E45122763B59009E8FEF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 31B3E52A22763ED5009E8FEF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 2392E21FBB5775A2A4C80A4C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-FirebaseStaticInjections-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8B2ACFD82C7387329C9B46A2 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-FirebaseStaticInjectionsTests/Pods-iOS-FirebaseStaticInjectionsTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-FirebaseStaticInjectionsTests/Pods-iOS-FirebaseStaticInjectionsTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-FirebaseStaticInjectionsTests/Pods-iOS-FirebaseStaticInjectionsTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + D235A5D738179D856B66C201 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-FirebaseStaticInjectionsTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 31B3E3B922763B19009E8FEF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B5E4723DCCD6800139EB3 /* FirebaseRunner.swift in Sources */, + 314B5E4923DCCD6800139EB3 /* CrashlyticsErrorLogging.swift in Sources */, + 314B5E4A23DCCD6800139EB3 /* FirebaseTracking.swift in Sources */, + 314B5E4B23DCCD6800139EB3 /* FirebaseNotification.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31B3E52822763ED5009E8FEF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31B3E52F22763ED5009E8FEF /* FirebaseStaticInjectionsTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 31A85FBE22E3C50F0055DA3F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PlatformParticles; + targetProxy = 31A85FBD22E3C50F0055DA3F /* PBXContainerItemProxy */; + }; + 31B3E45622763B64009E8FEF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PlatformRouting; + targetProxy = 31B3E45522763B64009E8FEF /* PBXContainerItemProxy */; + }; + 31B3E45822763B64009E8FEF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 31B3E45722763B64009E8FEF /* PBXContainerItemProxy */; + }; + 31B3E45A22763B64009E8FEF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKit; + targetProxy = 31B3E45922763B64009E8FEF /* PBXContainerItemProxy */; + }; + 31B3E53322763ED5009E8FEF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31B3E3BC22763B19009E8FEF /* FirebaseStaticInjections */; + targetProxy = 31B3E53222763ED5009E8FEF /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 31B3E3C222763B19009E8FEF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 31B3E3C322763B19009E8FEF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 31B3E3C522763B19009E8FEF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B53FEE6286BC8AE67AA8289D /* Pods-iOS-FirebaseStaticInjections.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = XKF9424552; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31B3E3C622763B19009E8FEF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DF7BF46C45A090E041492199 /* Pods-iOS-FirebaseStaticInjections.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = XKF9424552; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 31B3E53522763ED5009E8FEF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2AC82E9412F0658CCD3B5F99 /* Pods-iOS-FirebaseStaticInjectionsTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = FirebaseStaticInjectionsTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.FirebaseStaticInjectionsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31B3E53622763ED5009E8FEF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AF31C47E0D98E7411788154 /* Pods-iOS-FirebaseStaticInjectionsTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = FirebaseStaticInjectionsTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.FirebaseStaticInjectionsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 31B3E3B822763B19009E8FEF /* Build configuration list for PBXProject "FirebaseStaticInjections" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31B3E3C222763B19009E8FEF /* Debug */, + 31B3E3C322763B19009E8FEF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31B3E3C422763B19009E8FEF /* Build configuration list for PBXNativeTarget "FirebaseStaticInjections" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31B3E3C522763B19009E8FEF /* Debug */, + 31B3E3C622763B19009E8FEF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31B3E53422763ED5009E8FEF /* Build configuration list for PBXNativeTarget "FirebaseStaticInjectionsTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31B3E53522763ED5009E8FEF /* Debug */, + 31B3E53622763ED5009E8FEF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 31B3E3B522763B19009E8FEF /* Project object */; +} diff --git a/FirebaseStaticInjections/FirebaseStaticInjections.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/FirebaseStaticInjections/FirebaseStaticInjections.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..94b2795e2 --- /dev/null +++ b/FirebaseStaticInjections/FirebaseStaticInjections.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,4 @@ + + + diff --git a/FirebaseStaticInjections/FirebaseStaticInjections.xcodeproj/xcshareddata/xcschemes/FirebaseStaticInjections.xcscheme b/FirebaseStaticInjections/FirebaseStaticInjections.xcodeproj/xcshareddata/xcschemes/FirebaseStaticInjections.xcscheme new file mode 100644 index 000000000..471601be5 --- /dev/null +++ b/FirebaseStaticInjections/FirebaseStaticInjections.xcodeproj/xcshareddata/xcschemes/FirebaseStaticInjections.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FirebaseStaticInjections/FirebaseStaticInjections/Resources/_iOS/_Routing/_Xib/FirebaseLogin.xib b/FirebaseStaticInjections/FirebaseStaticInjections/Resources/_iOS/_Routing/_Xib/FirebaseLogin.xib new file mode 100644 index 000000000..b3526b8db --- /dev/null +++ b/FirebaseStaticInjections/FirebaseStaticInjections/Resources/_iOS/_Routing/_Xib/FirebaseLogin.xib @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FirebaseStaticInjections/FirebaseStaticInjections/Resources/_iOS/_Routing/_Xib/GoogleLogin.xib b/FirebaseStaticInjections/FirebaseStaticInjections/Resources/_iOS/_Routing/_Xib/GoogleLogin.xib new file mode 100644 index 000000000..5c23a3beb --- /dev/null +++ b/FirebaseStaticInjections/FirebaseStaticInjections/Resources/_iOS/_Routing/_Xib/GoogleLogin.xib @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FirebaseStaticInjections/FirebaseStaticInjections/_Configure/FirebaseRunner.swift b/FirebaseStaticInjections/FirebaseStaticInjections/_Configure/FirebaseRunner.swift new file mode 100644 index 000000000..e43ce791b --- /dev/null +++ b/FirebaseStaticInjections/FirebaseStaticInjections/_Configure/FirebaseRunner.swift @@ -0,0 +1,41 @@ +// +// FirebaseRunner.swift +// FirebaseInjections +// +// Created by Qiang Huang on 12/20/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import FirebaseCore +import FirebaseCrashlytics +import Utilities + +public final class FirebaseRunner: NSObject, SingletonProtocol { + public static var optionsFile: String? { + didSet { + guard oldValue != optionsFile else { return } + shared = .init(optionsFile: optionsFile) + } + } + public let enabled: Bool + + public private (set) static var shared: FirebaseRunner = { + FirebaseRunner(optionsFile: optionsFile) + }() + + public init(optionsFile: String?) { + if let optionsFile = optionsFile, + let filePath = Bundle.main.path(forResource: optionsFile, ofType: "plist"), + let options = FirebaseOptions(contentsOfFile: filePath), + !options.googleAppID.isEmpty { + //do not configure firebase if using placeholder config file, otherwise app will crash due to startup runtime exception + FirebaseApp.configure(options: options) + Console.shared.log("analytics log | Firebase initialized") + FirebaseConfiguration.shared.setLoggerLevel(.min) + enabled = true + } else { + enabled = false + } + super.init() + } +} diff --git a/FirebaseStaticInjections/FirebaseStaticInjections/_ErrorLogging/CrashlyticsErrorLogging.swift b/FirebaseStaticInjections/FirebaseStaticInjections/_ErrorLogging/CrashlyticsErrorLogging.swift new file mode 100644 index 000000000..560a55b74 --- /dev/null +++ b/FirebaseStaticInjections/FirebaseStaticInjections/_ErrorLogging/CrashlyticsErrorLogging.swift @@ -0,0 +1,30 @@ +// +// CrashlyticsErrorLogging.swift +// FirebaseStaticInjections +// +// Created by Qiang Huang on 9/2/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import FirebaseCrashlytics +import ParticlesKit +import Utilities + +public class CrashlyticsErrorLogging: NSObject & ErrorLoggingProtocol { + public func log(_ error: Error?) { + if let error = error { + let errorText = "Error Logging \(error)" + Console.shared.log(errorText) + Crashlytics.crashlytics().record(error: error) + } + } + + public func e(tag: String, message: String) { + Crashlytics.crashlytics().record(error: NSError(domain: tag, code: 0, userInfo: [NSLocalizedDescriptionKey: message])) + } + + public func d(tag: String, message: String) { + // Do nothing + } + +} diff --git a/FirebaseStaticInjections/FirebaseStaticInjections/_Notification/FirebaseNotification.swift b/FirebaseStaticInjections/FirebaseStaticInjections/_Notification/FirebaseNotification.swift new file mode 100644 index 000000000..b02455387 --- /dev/null +++ b/FirebaseStaticInjections/FirebaseStaticInjections/_Notification/FirebaseNotification.swift @@ -0,0 +1,214 @@ +// +// FirebaseNotification.swift +// FirebaseInjections +// +// Created by John Huang on 12/31/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import FirebaseMessaging +import PlatformRouting +import RoutingKit +import UserNotifications +import Utilities + +public class FirebaseNotificationHandler: NotificationHandler { + public let tag: String + + override public var permission: EPrivacyPermission { + didSet { + if permission != oldValue { + updateAssociation() + } + } + } + + override public var token: String? { + didSet { + if token != oldValue { + updateAssociation() + if let token = token { + Console.shared.log("Firebase registration token: \(token)") + } + switch Installation.source { + case .debug, .testFlight: + DebugSettings.shared?.debug?["push_token"] = token + case .appStore, .jailBroken: + break + } + } + } + } + + public init(tag: String) { + self.tag = tag + super.init() + + _ = FirebaseRunner.shared + Messaging.messaging().delegate = self + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + self?.token = Messaging.messaging().fcmToken + } + } + + override public func request() { + UNUserNotificationCenter.current().delegate = self + + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] + UNUserNotificationCenter.current().requestAuthorization( + options: authOptions, + completionHandler: { _, _ in } + ) + UIApplication.shared.registerForRemoteNotifications() + } + + func updateAssociation() { + if authorization != nil { + switch permission { + case .authorized: + NotificationUserAssociation.shared?.deviceToken = token + default: + NotificationUserAssociation.shared?.deviceToken = nil + } + } + } + + override public func present(message: [AnyHashable: Any]) { + if let aps = message["aps"] as? [String: Any], let alert = aps["alert"] as? [String: Any] { + let title = parser.asString(alert["title"]) + let text = parser.asString(alert["body"]) + var actions: [ErrorAction]? + if let _ = link(message: message) { + let action = ErrorAction(text: DataLocalizer.localize(path: "APP.GENERAL.GO", params: nil)) { + _ = self.receive(message: message) + } + actions = [action] + } + if let actions = actions { + ErrorInfo.shared?.info(title: title, message: text, type: .info, error: nil, actions: actions) + } else { + ErrorInfo.shared?.info(title: title, message: text, type: .info, error: nil) + } + } + } + + override public func receive(message: [AnyHashable: Any]) -> Bool { + if let link = link(message: message) { + Router.shared?.navigate(to: URL(string: link), completion: nil) + return true + } + return false + } + + override public func didSetConfiguration(oldValue: NotificationConfiguration?) { + if configuration != oldValue { + updateConfiguration(previous: oldValue?.firebase, current: configuration?.firebase) + } + } + + private func link(message: [AnyHashable: Any]) -> String? { + if let data = parser.asDictionary(message["data"]), let custom = parser.asDictionary(data[tag]) { + return parser.asString(custom["link"]) + } + return nil + } + + fileprivate func updateConfiguration(previous: FirebaseNotificationConfiguration?, current: FirebaseNotificationConfiguration?) { + let previousTopics = previous?.subscribedTopics ?? [] + let currentTopics = current?.subscribedTopics ?? [] + + let newTopics = currentTopics.subtracting(previousTopics) + for topic in newTopics { + Messaging.messaging().subscribe(toTopic: topic) { error in + if let error = error { + Console.shared.log("Firebase topic subscription failed: \(String(describing: error))") + } + } + } + + let deletedTopics = previousTopics.subtracting(currentTopics) + for topic in deletedTopics { + Messaging.messaging().unsubscribe(fromTopic: topic) { error in + if let error = error { + Console.shared.log("Firebase topic unsubscription failed: \(String(describing: error))") + } + } + } + } +} + +extension FirebaseNotificationHandler: MessagingDelegate { + public func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { + if let fcmToken = fcmToken { + Console.shared.log("Firebase registration token: \(fcmToken)") + + token = fcmToken + + let dataDict: [String: String] = ["token": fcmToken] + NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict) + + // TODO: If necessary send token to application server. + // Note: This callback is fired at each app startup and whenever a new token is generated. + + #if DEBUG +// InstanceID.instanceID().instanceID { result, error in +// if let error = error { +// Console.shared.log("Error fetching remote instance ID: \(error)") +// } else if let result = result { +// Console.shared.log("Remote instance ID token: \(result.token)") +// } +// let apns = Messaging.messaging().apnsToken +// if let apns = apns { +// let string = String(data: apns, encoding: .utf16) +// Console.shared.log("ASPN token: \(String(describing: string))") +// } +// } + #endif + } + } + +// public func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) { +// Console.shared.log("Received data message: \(remoteMessage.appData)") +// } +} + +extension FirebaseNotificationHandler: UNUserNotificationCenterDelegate { + public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + let userInfo = notification.request.content.userInfo + present(message: userInfo) + completionHandler([]) + } + + public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + let userInfo = response.notification.request.content.userInfo + _ = receive(message: userInfo) + completionHandler() + } +} + +extension FirebaseNotificationHandler: NotificationBridgeProtocol { + public func launched() { + } + + public func registered(deviceToken: Data) { + } + + public func failed(error: Error) { + } + + public func received(userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + Messaging.serviceExtension().exportDeliveryMetricsToBigQuery(withMessageInfo: userInfo) + if receive(message: userInfo) { + completionHandler(.newData) + } else { + completionHandler(.noData) + } + } + + public func receivedDeeplink(userInfo: [AnyHashable : Any]) -> URL? { + if let link = link(message: userInfo) { + return URL(string: link) + } + return nil + } +} diff --git a/FirebaseStaticInjections/FirebaseStaticInjections/_Tracking/FirebaseTracking.swift b/FirebaseStaticInjections/FirebaseStaticInjections/_Tracking/FirebaseTracking.swift new file mode 100644 index 000000000..2f46313ae --- /dev/null +++ b/FirebaseStaticInjections/FirebaseStaticInjections/_Tracking/FirebaseTracking.swift @@ -0,0 +1,78 @@ +// +// FilebaseAnalytics.swift +// TrackingKit +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import FirebaseAnalytics +import FirebaseCore +import PlatformParticles +import Utilities + +public class FirebaseTracking: TransformerTracker { + private func parseAnyToString(_ value: Any?) -> String? { + guard let value = value else { + return nil + } + switch value { + case let stringValue as String: + return stringValue + case let intValue as Int: + return String(intValue) + case let doubleValue as Double: + return String(doubleValue) + case let boolValue as Bool: + return String(boolValue) + case let arrayValue as [Any]: + if let jsonString = parseAsJsonString(arrayValue) { + return jsonString + } + case let dictValue as [String: Any]: + if let jsonString = parseAsJsonString(dictValue) { + return jsonString + } + default: + return "\(value)" + } + return "\(value)" + } + + private func parseAsJsonString(_ value: Any) -> String? { + if let jsonData = try? JSONSerialization.data(withJSONObject: value, options: .prettyPrinted), + let jsonString = String(data: jsonData, encoding: .utf8) { + return jsonString + } + return nil + } + + override public init() { + super.init() + FirebaseConfiguration.shared.setLoggerLevel(.min) + Analytics.setUserProperty(String(format: "%.4f", UIDevice.current.systemVersionAsFloat), forName: "os_version") + } + + override public func setUserId(_ userId: String?) { + Console.shared.log("analytics log | Firebase: User ID set to: `\(userId ?? "nil")`") + Analytics.setUserID(userId) + } + + override public func setValue(_ value: Any?, forUserProperty userProperty: String) { + Console.shared.log("analytics log | Firebase: User Property `\(userProperty)` set to: \(value ?? "nil")") + // firebase max supported length is 36, this is best effort + if let valueString = parseAnyToString(value) { + Analytics.setUserProperty(String(valueString.prefix(36)), forName: userProperty) + } else { + Analytics.setUserProperty(nil, forName: userProperty) + } + } + + override public func log(event: String, data: [String: Any]?, revenue: NSNumber?) { + if !excluded { + DispatchQueue.global().async { + Analytics.logEvent(event, parameters: data) + } + } + } +} diff --git a/FirebaseStaticInjections/FirebaseStaticInjectionsTests/FirebaseStaticInjectionsTests.swift b/FirebaseStaticInjections/FirebaseStaticInjectionsTests/FirebaseStaticInjectionsTests.swift new file mode 100644 index 000000000..9407c2f38 --- /dev/null +++ b/FirebaseStaticInjections/FirebaseStaticInjectionsTests/FirebaseStaticInjectionsTests.swift @@ -0,0 +1,33 @@ +// +// FirebaseStaticInjectionsTests.swift +// FirebaseStaticInjectionsTests +// +// Created by Qiang Huang on 4/28/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import XCTest + +class FirebaseStaticInjectionsTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/FirebaseStaticInjections/FirebaseStaticInjectionsTests/Info.plist b/FirebaseStaticInjections/FirebaseStaticInjectionsTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/FirebaseStaticInjections/FirebaseStaticInjectionsTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/JedioKit/JedioKit.xcodeproj/project.pbxproj b/JedioKit/JedioKit.xcodeproj/project.pbxproj new file mode 100644 index 000000000..feff40afb --- /dev/null +++ b/JedioKit/JedioKit.xcodeproj/project.pbxproj @@ -0,0 +1,1546 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 313EBA9C21BB791800BEF926 /* JedioKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 313EBA9A21BB791800BEF926 /* JedioKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 313EBAAE21BB792600BEF926 /* JedioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBAA521BB792600BEF926 /* JedioKit.framework */; }; + 313EBAB321BB792600BEF926 /* JedioKitAppleTVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EBAB221BB792600BEF926 /* JedioKitAppleTVTests.swift */; }; + 313EBAB521BB792600BEF926 /* JedioKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 313EBAA721BB792600BEF926 /* JedioKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 313EBAFA21BB799A00BEF926 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBA6221BB78DF00BEF926 /* Utilities.framework */; }; + 313EBAFB21BB799A00BEF926 /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBA7221BB78DF00BEF926 /* ParticlesKit.framework */; }; + 313EBAFD21BB799A00BEF926 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBA8621BB78DF00BEF926 /* RoutingKit.framework */; }; + 313EBCFD21BB7DED00BEF926 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBA6421BB78DF00BEF926 /* Utilities.framework */; }; + 313EBCFE21BB7DED00BEF926 /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBA7421BB78DF00BEF926 /* ParticlesKit.framework */; }; + 313EBD0021BB7DED00BEF926 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBA8821BB78DF00BEF926 /* RoutingKit.framework */; }; + 314B5EF123DCCDEB00139EB3 /* PhoneFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDB23DCCDEB00139EB3 /* PhoneFieldValidator.swift */; }; + 314B5EF223DCCDEC00139EB3 /* PhoneFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDB23DCCDEB00139EB3 /* PhoneFieldValidator.swift */; }; + 314B5EF323DCCDEC00139EB3 /* PhoneFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDB23DCCDEB00139EB3 /* PhoneFieldValidator.swift */; }; + 314B5EF523DCCDEC00139EB3 /* NullFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDC23DCCDEB00139EB3 /* NullFieldValidator.swift */; }; + 314B5EF623DCCDEC00139EB3 /* NullFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDC23DCCDEB00139EB3 /* NullFieldValidator.swift */; }; + 314B5EF723DCCDEC00139EB3 /* NullFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDC23DCCDEB00139EB3 /* NullFieldValidator.swift */; }; + 314B5EF923DCCDEC00139EB3 /* PasswordFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDD23DCCDEB00139EB3 /* PasswordFieldValidator.swift */; }; + 314B5EFA23DCCDEC00139EB3 /* PasswordFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDD23DCCDEB00139EB3 /* PasswordFieldValidator.swift */; }; + 314B5EFB23DCCDEC00139EB3 /* PasswordFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDD23DCCDEB00139EB3 /* PasswordFieldValidator.swift */; }; + 314B5EFD23DCCDEC00139EB3 /* FieldValidatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDE23DCCDEB00139EB3 /* FieldValidatorProtocol.swift */; }; + 314B5EFE23DCCDEC00139EB3 /* FieldValidatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDE23DCCDEB00139EB3 /* FieldValidatorProtocol.swift */; }; + 314B5EFF23DCCDEC00139EB3 /* FieldValidatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDE23DCCDEB00139EB3 /* FieldValidatorProtocol.swift */; }; + 314B5F0123DCCDEC00139EB3 /* EmailFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDF23DCCDEB00139EB3 /* EmailFieldValidator.swift */; }; + 314B5F0223DCCDEC00139EB3 /* EmailFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDF23DCCDEB00139EB3 /* EmailFieldValidator.swift */; }; + 314B5F0323DCCDEC00139EB3 /* EmailFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EDF23DCCDEB00139EB3 /* EmailFieldValidator.swift */; }; + 314B5F0523DCCDEC00139EB3 /* FieldInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE223DCCDEB00139EB3 /* FieldInput.swift */; }; + 314B5F0623DCCDEC00139EB3 /* FieldInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE223DCCDEB00139EB3 /* FieldInput.swift */; }; + 314B5F0723DCCDEC00139EB3 /* FieldInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE223DCCDEB00139EB3 /* FieldInput.swift */; }; + 314B5F0923DCCDEC00139EB3 /* FieldDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE323DCCDEB00139EB3 /* FieldDefinition.swift */; }; + 314B5F0A23DCCDEC00139EB3 /* FieldDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE323DCCDEB00139EB3 /* FieldDefinition.swift */; }; + 314B5F0B23DCCDEC00139EB3 /* FieldDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE323DCCDEB00139EB3 /* FieldDefinition.swift */; }; + 314B5F0D23DCCDEC00139EB3 /* FieldDefinitionGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE423DCCDEB00139EB3 /* FieldDefinitionGroup.swift */; }; + 314B5F0E23DCCDEC00139EB3 /* FieldDefinitionGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE423DCCDEB00139EB3 /* FieldDefinitionGroup.swift */; }; + 314B5F0F23DCCDEC00139EB3 /* FieldDefinitionGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE423DCCDEB00139EB3 /* FieldDefinitionGroup.swift */; }; + 314B5F1123DCCDEC00139EB3 /* FieldOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE623DCCDEB00139EB3 /* FieldOutput.swift */; }; + 314B5F1223DCCDEC00139EB3 /* FieldOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE623DCCDEB00139EB3 /* FieldOutput.swift */; }; + 314B5F1323DCCDEC00139EB3 /* FieldOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE623DCCDEB00139EB3 /* FieldOutput.swift */; }; + 314B5F1523DCCDEC00139EB3 /* FieldLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE723DCCDEB00139EB3 /* FieldLoader.swift */; }; + 314B5F1623DCCDEC00139EB3 /* FieldLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE723DCCDEB00139EB3 /* FieldLoader.swift */; }; + 314B5F1723DCCDEC00139EB3 /* FieldLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE723DCCDEB00139EB3 /* FieldLoader.swift */; }; + 314B5F1923DCCDEC00139EB3 /* FieldProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE823DCCDEB00139EB3 /* FieldProtocol.swift */; }; + 314B5F1A23DCCDEC00139EB3 /* FieldProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE823DCCDEB00139EB3 /* FieldProtocol.swift */; }; + 314B5F1B23DCCDEC00139EB3 /* FieldProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EE823DCCDEB00139EB3 /* FieldProtocol.swift */; }; + 314B5F1D23DCCDEC00139EB3 /* FieldOutput+Xib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEA23DCCDEB00139EB3 /* FieldOutput+Xib.swift */; }; + 314B5F1E23DCCDEC00139EB3 /* FieldOutput+Xib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEA23DCCDEB00139EB3 /* FieldOutput+Xib.swift */; }; + 314B5F1F23DCCDEC00139EB3 /* FieldOutput+Xib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEA23DCCDEB00139EB3 /* FieldOutput+Xib.swift */; }; + 314B5F2123DCCDEC00139EB3 /* FieldDefinition+Xib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEB23DCCDEB00139EB3 /* FieldDefinition+Xib.swift */; }; + 314B5F2223DCCDEC00139EB3 /* FieldDefinition+Xib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEB23DCCDEB00139EB3 /* FieldDefinition+Xib.swift */; }; + 314B5F2323DCCDEC00139EB3 /* FieldDefinition+Xib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEB23DCCDEB00139EB3 /* FieldDefinition+Xib.swift */; }; + 314B5F2523DCCDEC00139EB3 /* FieldInput+Xib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEC23DCCDEB00139EB3 /* FieldInput+Xib.swift */; }; + 314B5F2623DCCDEC00139EB3 /* FieldInput+Xib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEC23DCCDEB00139EB3 /* FieldInput+Xib.swift */; }; + 314B5F2723DCCDEC00139EB3 /* FieldInput+Xib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEC23DCCDEB00139EB3 /* FieldInput+Xib.swift */; }; + 314B5F2923DCCDEC00139EB3 /* FieldsEntityInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEE23DCCDEB00139EB3 /* FieldsEntityInteractor.swift */; }; + 314B5F2A23DCCDEC00139EB3 /* FieldsEntityInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEE23DCCDEB00139EB3 /* FieldsEntityInteractor.swift */; }; + 314B5F2B23DCCDEC00139EB3 /* FieldsEntityInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEE23DCCDEB00139EB3 /* FieldsEntityInteractor.swift */; }; + 314B5F2D23DCCDEC00139EB3 /* FieldListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEF23DCCDEB00139EB3 /* FieldListInteractor.swift */; }; + 314B5F2E23DCCDEC00139EB3 /* FieldListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEF23DCCDEB00139EB3 /* FieldListInteractor.swift */; }; + 314B5F2F23DCCDEC00139EB3 /* FieldListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EEF23DCCDEB00139EB3 /* FieldListInteractor.swift */; }; + 314B5F3123DCCDEC00139EB3 /* ListInteractor+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EF023DCCDEB00139EB3 /* ListInteractor+Input.swift */; }; + 314B5F3223DCCDEC00139EB3 /* ListInteractor+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EF023DCCDEB00139EB3 /* ListInteractor+Input.swift */; }; + 314B5F3323DCCDEC00139EB3 /* ListInteractor+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5EF023DCCDEB00139EB3 /* ListInteractor+Input.swift */; }; + 31ACB85521B0F3F500391ADF /* JedioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB84B21B0F3F500391ADF /* JedioKit.framework */; }; + 31ACB85A21B0F3F500391ADF /* JedioKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ACB85921B0F3F500391ADF /* JedioKitTests.swift */; }; + 31ACB85C21B0F3F500391ADF /* JedioKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 31ACB84E21B0F3F500391ADF /* JedioKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 31ACB8A621B0F46100391ADF /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB88621B0F41B00391ADF /* Utilities.framework */; }; + 31ACB8A721B0F46100391ADF /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB87421B0F41B00391ADF /* ParticlesKit.framework */; }; + 31ACB8A921B0F46100391ADF /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB89821B0F41B00391ADF /* RoutingKit.framework */; }; + 4B6E9CF3843CB9A0D4E39253 /* Pods_iOS_JedioKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB3703C5C970B059CECDFFC0 /* Pods_iOS_JedioKitTests.framework */; }; + B812470D89D39155E00F81F0 /* Pods_iOS_JedioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7DE4EEC6ACB1AB4847FA59E5 /* Pods_iOS_JedioKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 313EBA6121BB78DF00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB88021B0F41B00391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 313EBA6321BB78DF00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB88021B0F41B00391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 313EBA6721BB78DF00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB88021B0F41B00391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 313EBA7121BB78DF00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB86E21B0F41B00391ADF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 319682B421B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 313EBA7321BB78DF00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB86E21B0F41B00391ADF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16121BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 313EBA7721BB78DF00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB86E21B0F41B00391ADF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16921BA246A00BEF926; + remoteInfo = ParticlesKitAppleTVTests; + }; + 313EBA8521BB78DF00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB89221B0F41B00391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196825B21B7947600AE0F28; + remoteInfo = RoutingKitAppleWatch; + }; + 313EBA8721BB78DF00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB89221B0F41B00391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22321BA26D700BEF926; + remoteInfo = RoutingKitAppleTV; + }; + 313EBA8B21BB78DF00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB89221B0F41B00391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22B21BA26D800BEF926; + remoteInfo = RoutingKitAppleTVTests; + }; + 313EBAAF21BB792600BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB84221B0F3F500391ADF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 313EBAA421BB792600BEF926; + remoteInfo = JedioKitAppleTV; + }; + 313EBAF021BB796900BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB88021B0F41B00391ADF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 3196823621B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 313EBAF221BB796900BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB86E21B0F41B00391ADF /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 319682B321B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 313EBAF421BB796900BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB89221B0F41B00391ADF /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 3196825A21B7947600AE0F28; + remoteInfo = RoutingKitAppleWatch; + }; + 313EBCA121BB7CD000BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB88021B0F41B00391ADF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313A536421B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 313EBCA321BB7CD000BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB86E21B0F41B00391ADF /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313EB16021BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 313EBCA521BB7CD000BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB89221B0F41B00391ADF /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313EB22221BA26D700BEF926; + remoteInfo = RoutingKitAppleTV; + }; + 31ACB85621B0F3F500391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB84221B0F3F500391ADF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31ACB84A21B0F3F500391ADF; + remoteInfo = JedioKit; + }; + 31ACB87321B0F41B00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB86E21B0F41B00391ADF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FDA21B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 31ACB87521B0F41B00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB86E21B0F41B00391ADF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FE321B0E9C4001775BA; + remoteInfo = ParticlesKitTests; + }; + 31ACB88521B0F41B00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB88021B0F41B00391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 31ACB88721B0F41B00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB88021B0F41B00391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 31ACB89721B0F41B00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB89221B0F41B00391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B01216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 31ACB89921B0F41B00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB89221B0F41B00391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B0A216BCA10008ABEE9; + remoteInfo = RoutingKitTests; + }; + 31ACB89B21B0F44700391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB88021B0F41B00391ADF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 31ACB89D21B0F44700391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB86E21B0F41B00391ADF /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 311C0FD921B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 31ACB89F21B0F44700391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB89221B0F41B00391ADF /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65B00216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 313EBA9821BB791800BEF926 /* JedioKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JedioKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EBA9A21BB791800BEF926 /* JedioKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JedioKit.h; sourceTree = ""; }; + 313EBA9B21BB791800BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EBAA521BB792600BEF926 /* JedioKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JedioKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EBAA721BB792600BEF926 /* JedioKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JedioKit.h; sourceTree = ""; }; + 313EBAA821BB792600BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EBAAD21BB792600BEF926 /* JedioKitAppleTVTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JedioKitAppleTVTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EBAB221BB792600BEF926 /* JedioKitAppleTVTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JedioKitAppleTVTests.swift; sourceTree = ""; }; + 313EBAB421BB792600BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 314B5EDB23DCCDEB00139EB3 /* PhoneFieldValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneFieldValidator.swift; sourceTree = ""; }; + 314B5EDC23DCCDEB00139EB3 /* NullFieldValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NullFieldValidator.swift; sourceTree = ""; }; + 314B5EDD23DCCDEB00139EB3 /* PasswordFieldValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordFieldValidator.swift; sourceTree = ""; }; + 314B5EDE23DCCDEB00139EB3 /* FieldValidatorProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldValidatorProtocol.swift; sourceTree = ""; }; + 314B5EDF23DCCDEB00139EB3 /* EmailFieldValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailFieldValidator.swift; sourceTree = ""; }; + 314B5EE223DCCDEB00139EB3 /* FieldInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldInput.swift; sourceTree = ""; }; + 314B5EE323DCCDEB00139EB3 /* FieldDefinition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldDefinition.swift; sourceTree = ""; }; + 314B5EE423DCCDEB00139EB3 /* FieldDefinitionGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldDefinitionGroup.swift; sourceTree = ""; }; + 314B5EE623DCCDEB00139EB3 /* FieldOutput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldOutput.swift; sourceTree = ""; }; + 314B5EE723DCCDEB00139EB3 /* FieldLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldLoader.swift; sourceTree = ""; }; + 314B5EE823DCCDEB00139EB3 /* FieldProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldProtocol.swift; sourceTree = ""; }; + 314B5EEA23DCCDEB00139EB3 /* FieldOutput+Xib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FieldOutput+Xib.swift"; sourceTree = ""; }; + 314B5EEB23DCCDEB00139EB3 /* FieldDefinition+Xib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FieldDefinition+Xib.swift"; sourceTree = ""; }; + 314B5EEC23DCCDEB00139EB3 /* FieldInput+Xib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FieldInput+Xib.swift"; sourceTree = ""; }; + 314B5EEE23DCCDEB00139EB3 /* FieldsEntityInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldsEntityInteractor.swift; sourceTree = ""; }; + 314B5EEF23DCCDEB00139EB3 /* FieldListInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldListInteractor.swift; sourceTree = ""; }; + 314B5EF023DCCDEB00139EB3 /* ListInteractor+Input.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ListInteractor+Input.swift"; sourceTree = ""; }; + 31ACB84B21B0F3F500391ADF /* JedioKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JedioKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31ACB84E21B0F3F500391ADF /* JedioKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JedioKit.h; sourceTree = ""; }; + 31ACB84F21B0F3F500391ADF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31ACB85421B0F3F500391ADF /* JedioKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JedioKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 31ACB85921B0F3F500391ADF /* JedioKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JedioKitTests.swift; sourceTree = ""; }; + 31ACB85B21B0F3F500391ADF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31ACB86E21B0F41B00391ADF /* ParticlesKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParticlesKit.xcodeproj; path = ../ParticlesKit/ParticlesKit.xcodeproj; sourceTree = ""; }; + 31ACB88021B0F41B00391ADF /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 31ACB89221B0F41B00391ADF /* RoutingKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RoutingKit.xcodeproj; path = ../RoutingKit/RoutingKit.xcodeproj; sourceTree = ""; }; + 5B918BE9A744104AEA35008A /* Pods-iOS-JedioKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-JedioKit.release.xcconfig"; path = "Target Support Files/Pods-iOS-JedioKit/Pods-iOS-JedioKit.release.xcconfig"; sourceTree = ""; }; + 7DE4EEC6ACB1AB4847FA59E5 /* Pods_iOS_JedioKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_JedioKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B5E7A2D21E0E6E3CCF07850E /* Pods-iOS-JedioKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-JedioKitTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-JedioKitTests/Pods-iOS-JedioKitTests.release.xcconfig"; sourceTree = ""; }; + BB3703C5C970B059CECDFFC0 /* Pods_iOS_JedioKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_JedioKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E300CFD413F55789DC9D3839 /* Pods-iOS-JedioKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-JedioKitTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-JedioKitTests/Pods-iOS-JedioKitTests.debug.xcconfig"; sourceTree = ""; }; + FEA24D0A29E1C1E2DD2351B6 /* Pods-iOS-JedioKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-JedioKit.debug.xcconfig"; path = "Target Support Files/Pods-iOS-JedioKit/Pods-iOS-JedioKit.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 313EBA9521BB791800BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBAFA21BB799A00BEF926 /* Utilities.framework in Frameworks */, + 313EBAFB21BB799A00BEF926 /* ParticlesKit.framework in Frameworks */, + 313EBAFD21BB799A00BEF926 /* RoutingKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBAA221BB792600BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBCFD21BB7DED00BEF926 /* Utilities.framework in Frameworks */, + 313EBCFE21BB7DED00BEF926 /* ParticlesKit.framework in Frameworks */, + 313EBD0021BB7DED00BEF926 /* RoutingKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBAAA21BB792600BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBAAE21BB792600BEF926 /* JedioKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB84821B0F3F500391ADF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31ACB8A621B0F46100391ADF /* Utilities.framework in Frameworks */, + 31ACB8A721B0F46100391ADF /* ParticlesKit.framework in Frameworks */, + 31ACB8A921B0F46100391ADF /* RoutingKit.framework in Frameworks */, + B812470D89D39155E00F81F0 /* Pods_iOS_JedioKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB85121B0F3F500391ADF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31ACB85521B0F3F500391ADF /* JedioKit.framework in Frameworks */, + 4B6E9CF3843CB9A0D4E39253 /* Pods_iOS_JedioKitTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0A60D7BCAC682B79D872665C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7DE4EEC6ACB1AB4847FA59E5 /* Pods_iOS_JedioKit.framework */, + BB3703C5C970B059CECDFFC0 /* Pods_iOS_JedioKitTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 265A0D8592051AE2755B0CA3 /* Pods */ = { + isa = PBXGroup; + children = ( + FEA24D0A29E1C1E2DD2351B6 /* Pods-iOS-JedioKit.debug.xcconfig */, + 5B918BE9A744104AEA35008A /* Pods-iOS-JedioKit.release.xcconfig */, + E300CFD413F55789DC9D3839 /* Pods-iOS-JedioKitTests.debug.xcconfig */, + B5E7A2D21E0E6E3CCF07850E /* Pods-iOS-JedioKitTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; + 313EBA9921BB791800BEF926 /* JedioKitAppleWatch */ = { + isa = PBXGroup; + children = ( + 313EBA9A21BB791800BEF926 /* JedioKit.h */, + 313EBA9B21BB791800BEF926 /* Info.plist */, + ); + path = JedioKitAppleWatch; + sourceTree = ""; + }; + 313EBAA621BB792600BEF926 /* JedioKitAppleTV */ = { + isa = PBXGroup; + children = ( + 313EBAA721BB792600BEF926 /* JedioKit.h */, + 313EBAA821BB792600BEF926 /* Info.plist */, + ); + path = JedioKitAppleTV; + sourceTree = ""; + }; + 313EBAB121BB792600BEF926 /* JedioKitAppleTVTests */ = { + isa = PBXGroup; + children = ( + 313EBAB221BB792600BEF926 /* JedioKitAppleTVTests.swift */, + 313EBAB421BB792600BEF926 /* Info.plist */, + ); + path = JedioKitAppleTVTests; + sourceTree = ""; + }; + 314B5EDA23DCCDEB00139EB3 /* _Validator */ = { + isa = PBXGroup; + children = ( + 314B5EDB23DCCDEB00139EB3 /* PhoneFieldValidator.swift */, + 314B5EDC23DCCDEB00139EB3 /* NullFieldValidator.swift */, + 314B5EDD23DCCDEB00139EB3 /* PasswordFieldValidator.swift */, + 314B5EDE23DCCDEB00139EB3 /* FieldValidatorProtocol.swift */, + 314B5EDF23DCCDEB00139EB3 /* EmailFieldValidator.swift */, + ); + path = _Validator; + sourceTree = ""; + }; + 314B5EE023DCCDEB00139EB3 /* _Field */ = { + isa = PBXGroup; + children = ( + 314B5EE123DCCDEB00139EB3 /* _Input */, + 314B5EE523DCCDEB00139EB3 /* _Output */, + 314B5EE323DCCDEB00139EB3 /* FieldDefinition.swift */, + 314B5EE423DCCDEB00139EB3 /* FieldDefinitionGroup.swift */, + 314B5EE723DCCDEB00139EB3 /* FieldLoader.swift */, + 314B5EE823DCCDEB00139EB3 /* FieldProtocol.swift */, + ); + path = _Field; + sourceTree = ""; + }; + 314B5EE123DCCDEB00139EB3 /* _Input */ = { + isa = PBXGroup; + children = ( + 314B5EE223DCCDEB00139EB3 /* FieldInput.swift */, + ); + path = _Input; + sourceTree = ""; + }; + 314B5EE523DCCDEB00139EB3 /* _Output */ = { + isa = PBXGroup; + children = ( + 314B5EE623DCCDEB00139EB3 /* FieldOutput.swift */, + ); + path = _Output; + sourceTree = ""; + }; + 314B5EE923DCCDEB00139EB3 /* _Xib */ = { + isa = PBXGroup; + children = ( + 314B5EEA23DCCDEB00139EB3 /* FieldOutput+Xib.swift */, + 314B5EEB23DCCDEB00139EB3 /* FieldDefinition+Xib.swift */, + 314B5EEC23DCCDEB00139EB3 /* FieldInput+Xib.swift */, + ); + path = _Xib; + sourceTree = ""; + }; + 314B5EED23DCCDEB00139EB3 /* _Interactor */ = { + isa = PBXGroup; + children = ( + 314B5EEE23DCCDEB00139EB3 /* FieldsEntityInteractor.swift */, + 314B5EEF23DCCDEB00139EB3 /* FieldListInteractor.swift */, + 314B5EF023DCCDEB00139EB3 /* ListInteractor+Input.swift */, + ); + path = _Interactor; + sourceTree = ""; + }; + 31ACB84121B0F3F500391ADF = { + isa = PBXGroup; + children = ( + 31ACB86E21B0F41B00391ADF /* ParticlesKit.xcodeproj */, + 31ACB89221B0F41B00391ADF /* RoutingKit.xcodeproj */, + 31ACB88021B0F41B00391ADF /* Utilities.xcodeproj */, + 31ACB84D21B0F3F500391ADF /* JedioKit */, + 313EBAA621BB792600BEF926 /* JedioKitAppleTV */, + 313EBAB121BB792600BEF926 /* JedioKitAppleTVTests */, + 313EBA9921BB791800BEF926 /* JedioKitAppleWatch */, + 31ACB85821B0F3F500391ADF /* JedioKitTests */, + 31ACB84C21B0F3F500391ADF /* Products */, + 265A0D8592051AE2755B0CA3 /* Pods */, + 0A60D7BCAC682B79D872665C /* Frameworks */, + ); + sourceTree = ""; + }; + 31ACB84C21B0F3F500391ADF /* Products */ = { + isa = PBXGroup; + children = ( + 31ACB84B21B0F3F500391ADF /* JedioKit.framework */, + 31ACB85421B0F3F500391ADF /* JedioKitTests.xctest */, + 313EBA9821BB791800BEF926 /* JedioKit.framework */, + 313EBAA521BB792600BEF926 /* JedioKit.framework */, + 313EBAAD21BB792600BEF926 /* JedioKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31ACB84D21B0F3F500391ADF /* JedioKit */ = { + isa = PBXGroup; + children = ( + 314B5EE023DCCDEB00139EB3 /* _Field */, + 314B5EED23DCCDEB00139EB3 /* _Interactor */, + 314B5EDA23DCCDEB00139EB3 /* _Validator */, + 314B5EE923DCCDEB00139EB3 /* _Xib */, + 31ACB84F21B0F3F500391ADF /* Info.plist */, + 31ACB84E21B0F3F500391ADF /* JedioKit.h */, + ); + path = JedioKit; + sourceTree = ""; + }; + 31ACB85821B0F3F500391ADF /* JedioKitTests */ = { + isa = PBXGroup; + children = ( + 31ACB85921B0F3F500391ADF /* JedioKitTests.swift */, + 31ACB85B21B0F3F500391ADF /* Info.plist */, + ); + path = JedioKitTests; + sourceTree = ""; + }; + 31ACB86F21B0F41B00391ADF /* Products */ = { + isa = PBXGroup; + children = ( + 31ACB87421B0F41B00391ADF /* ParticlesKit.framework */, + 313EBA7221BB78DF00BEF926 /* ParticlesKit.framework */, + 313EBA7421BB78DF00BEF926 /* ParticlesKit.framework */, + 31ACB87621B0F41B00391ADF /* ParticlesKitTests.xctest */, + 313EBA7821BB78DF00BEF926 /* ParticlesKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31ACB88121B0F41B00391ADF /* Products */ = { + isa = PBXGroup; + children = ( + 31ACB88621B0F41B00391ADF /* Utilities.framework */, + 313EBA6221BB78DF00BEF926 /* Utilities.framework */, + 313EBA6421BB78DF00BEF926 /* Utilities.framework */, + 31ACB88821B0F41B00391ADF /* UtilitiesTests.xctest */, + 313EBA6821BB78DF00BEF926 /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31ACB89321B0F41B00391ADF /* Products */ = { + isa = PBXGroup; + children = ( + 31ACB89821B0F41B00391ADF /* RoutingKit.framework */, + 313EBA8621BB78DF00BEF926 /* RoutingKit.framework */, + 313EBA8821BB78DF00BEF926 /* RoutingKit.framework */, + 31ACB89A21B0F41B00391ADF /* RoutingKitTests.xctest */, + 313EBA8C21BB78DF00BEF926 /* RoutingKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 313EBA9321BB791800BEF926 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBA9C21BB791800BEF926 /* JedioKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBAA021BB792600BEF926 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBAB521BB792600BEF926 /* JedioKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB84621B0F3F500391ADF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 31ACB85C21B0F3F500391ADF /* JedioKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 313EBA9721BB791800BEF926 /* JedioKitAppleWatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EBA9D21BB791800BEF926 /* Build configuration list for PBXNativeTarget "JedioKitAppleWatch" */; + buildPhases = ( + 313EBA9321BB791800BEF926 /* Headers */, + 313EBA9421BB791800BEF926 /* Sources */, + 313EBA9521BB791800BEF926 /* Frameworks */, + 313EBA9621BB791800BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EBAF121BB796900BEF926 /* PBXTargetDependency */, + 313EBAF321BB796900BEF926 /* PBXTargetDependency */, + 313EBAF521BB796900BEF926 /* PBXTargetDependency */, + ); + name = JedioKitAppleWatch; + productName = JedioKitAppleWatch; + productReference = 313EBA9821BB791800BEF926 /* JedioKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 313EBAA421BB792600BEF926 /* JedioKitAppleTV */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EBAB621BB792600BEF926 /* Build configuration list for PBXNativeTarget "JedioKitAppleTV" */; + buildPhases = ( + 313EBAA021BB792600BEF926 /* Headers */, + 313EBAA121BB792600BEF926 /* Sources */, + 313EBAA221BB792600BEF926 /* Frameworks */, + 313EBAA321BB792600BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EBCA221BB7CD000BEF926 /* PBXTargetDependency */, + 313EBCA421BB7CD000BEF926 /* PBXTargetDependency */, + 313EBCA621BB7CD000BEF926 /* PBXTargetDependency */, + ); + name = JedioKitAppleTV; + productName = JedioKitAppleTV; + productReference = 313EBAA521BB792600BEF926 /* JedioKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 313EBAAC21BB792600BEF926 /* JedioKitAppleTVTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EBAB921BB792600BEF926 /* Build configuration list for PBXNativeTarget "JedioKitAppleTVTests" */; + buildPhases = ( + 313EBAA921BB792600BEF926 /* Sources */, + 313EBAAA21BB792600BEF926 /* Frameworks */, + 313EBAAB21BB792600BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EBAB021BB792600BEF926 /* PBXTargetDependency */, + ); + name = JedioKitAppleTVTests; + productName = JedioKitAppleTVTests; + productReference = 313EBAAD21BB792600BEF926 /* JedioKitAppleTVTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 31ACB84A21B0F3F500391ADF /* JedioKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31ACB85F21B0F3F500391ADF /* Build configuration list for PBXNativeTarget "JedioKit" */; + buildPhases = ( + 6DFDA40381ECAD22EC0726BF /* [CP] Check Pods Manifest.lock */, + 31ACB84621B0F3F500391ADF /* Headers */, + 31ACB84721B0F3F500391ADF /* Sources */, + 31ACB84821B0F3F500391ADF /* Frameworks */, + 31ACB84921B0F3F500391ADF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 31ACB89C21B0F44700391ADF /* PBXTargetDependency */, + 31ACB89E21B0F44700391ADF /* PBXTargetDependency */, + 31ACB8A021B0F44700391ADF /* PBXTargetDependency */, + ); + name = JedioKit; + productName = JedioKit; + productReference = 31ACB84B21B0F3F500391ADF /* JedioKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 31ACB85321B0F3F500391ADF /* JedioKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31ACB86221B0F3F500391ADF /* Build configuration list for PBXNativeTarget "JedioKitTests" */; + buildPhases = ( + E19BA38655AAAC278B197123 /* [CP] Check Pods Manifest.lock */, + 31ACB85021B0F3F500391ADF /* Sources */, + 31ACB85121B0F3F500391ADF /* Frameworks */, + 31ACB85221B0F3F500391ADF /* Resources */, + 1D7AFA444B30200810187177 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 31ACB85721B0F3F500391ADF /* PBXTargetDependency */, + ); + name = JedioKitTests; + productName = JedioKitTests; + productReference = 31ACB85421B0F3F500391ADF /* JedioKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 31ACB84221B0F3F500391ADF /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "dYdX Trading Inc"; + TargetAttributes = { + 313EBA9721BB791800BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 313EBAA421BB792600BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 313EBAAC21BB792600BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 31ACB84A21B0F3F500391ADF = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1020; + }; + 31ACB85321B0F3F500391ADF = { + CreatedOnToolsVersion = 10.1; + }; + }; + }; + buildConfigurationList = 31ACB84521B0F3F500391ADF /* Build configuration list for PBXProject "JedioKit" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 31ACB84121B0F3F500391ADF; + productRefGroup = 31ACB84C21B0F3F500391ADF /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 31ACB86F21B0F41B00391ADF /* Products */; + ProjectRef = 31ACB86E21B0F41B00391ADF /* ParticlesKit.xcodeproj */; + }, + { + ProductGroup = 31ACB89321B0F41B00391ADF /* Products */; + ProjectRef = 31ACB89221B0F41B00391ADF /* RoutingKit.xcodeproj */; + }, + { + ProductGroup = 31ACB88121B0F41B00391ADF /* Products */; + ProjectRef = 31ACB88021B0F41B00391ADF /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 31ACB84A21B0F3F500391ADF /* JedioKit */, + 313EBA9721BB791800BEF926 /* JedioKitAppleWatch */, + 313EBAA421BB792600BEF926 /* JedioKitAppleTV */, + 31ACB85321B0F3F500391ADF /* JedioKitTests */, + 313EBAAC21BB792600BEF926 /* JedioKitAppleTVTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 313EBA6221BB78DF00BEF926 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 313EBA6121BB78DF00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBA6421BB78DF00BEF926 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 313EBA6321BB78DF00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBA6821BB78DF00BEF926 /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 313EBA6721BB78DF00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBA7221BB78DF00BEF926 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 313EBA7121BB78DF00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBA7421BB78DF00BEF926 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 313EBA7321BB78DF00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBA7821BB78DF00BEF926 /* ParticlesKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitAppleTVTests.xctest; + remoteRef = 313EBA7721BB78DF00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBA8621BB78DF00BEF926 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 313EBA8521BB78DF00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBA8821BB78DF00BEF926 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 313EBA8721BB78DF00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBA8C21BB78DF00BEF926 /* RoutingKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitAppleTVTests.xctest; + remoteRef = 313EBA8B21BB78DF00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB87421B0F41B00391ADF /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31ACB87321B0F41B00391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB87621B0F41B00391ADF /* ParticlesKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitTests.xctest; + remoteRef = 31ACB87521B0F41B00391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB88621B0F41B00391ADF /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 31ACB88521B0F41B00391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB88821B0F41B00391ADF /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 31ACB88721B0F41B00391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB89821B0F41B00391ADF /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 31ACB89721B0F41B00391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB89A21B0F41B00391ADF /* RoutingKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitTests.xctest; + remoteRef = 31ACB89921B0F41B00391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 313EBA9621BB791800BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBAA321BB792600BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBAAB21BB792600BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB84921B0F3F500391ADF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB85221B0F3F500391ADF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1D7AFA444B30200810187177 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-JedioKitTests/Pods-iOS-JedioKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-JedioKitTests/Pods-iOS-JedioKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-JedioKitTests/Pods-iOS-JedioKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 6DFDA40381ECAD22EC0726BF /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-JedioKit-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E19BA38655AAAC278B197123 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-JedioKitTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 313EBA9421BB791800BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B5F0A23DCCDEC00139EB3 /* FieldDefinition.swift in Sources */, + 314B5EF623DCCDEC00139EB3 /* NullFieldValidator.swift in Sources */, + 314B5EF223DCCDEC00139EB3 /* PhoneFieldValidator.swift in Sources */, + 314B5F1E23DCCDEC00139EB3 /* FieldOutput+Xib.swift in Sources */, + 314B5F3223DCCDEC00139EB3 /* ListInteractor+Input.swift in Sources */, + 314B5EFA23DCCDEC00139EB3 /* PasswordFieldValidator.swift in Sources */, + 314B5F0623DCCDEC00139EB3 /* FieldInput.swift in Sources */, + 314B5F2223DCCDEC00139EB3 /* FieldDefinition+Xib.swift in Sources */, + 314B5F1623DCCDEC00139EB3 /* FieldLoader.swift in Sources */, + 314B5F0223DCCDEC00139EB3 /* EmailFieldValidator.swift in Sources */, + 314B5F2623DCCDEC00139EB3 /* FieldInput+Xib.swift in Sources */, + 314B5F1A23DCCDEC00139EB3 /* FieldProtocol.swift in Sources */, + 314B5F1223DCCDEC00139EB3 /* FieldOutput.swift in Sources */, + 314B5EFE23DCCDEC00139EB3 /* FieldValidatorProtocol.swift in Sources */, + 314B5F2A23DCCDEC00139EB3 /* FieldsEntityInteractor.swift in Sources */, + 314B5F0E23DCCDEC00139EB3 /* FieldDefinitionGroup.swift in Sources */, + 314B5F2E23DCCDEC00139EB3 /* FieldListInteractor.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBAA121BB792600BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B5F0B23DCCDEC00139EB3 /* FieldDefinition.swift in Sources */, + 314B5EF723DCCDEC00139EB3 /* NullFieldValidator.swift in Sources */, + 314B5EF323DCCDEC00139EB3 /* PhoneFieldValidator.swift in Sources */, + 314B5F1F23DCCDEC00139EB3 /* FieldOutput+Xib.swift in Sources */, + 314B5F3323DCCDEC00139EB3 /* ListInteractor+Input.swift in Sources */, + 314B5EFB23DCCDEC00139EB3 /* PasswordFieldValidator.swift in Sources */, + 314B5F0723DCCDEC00139EB3 /* FieldInput.swift in Sources */, + 314B5F2323DCCDEC00139EB3 /* FieldDefinition+Xib.swift in Sources */, + 314B5F1723DCCDEC00139EB3 /* FieldLoader.swift in Sources */, + 314B5F0323DCCDEC00139EB3 /* EmailFieldValidator.swift in Sources */, + 314B5F2723DCCDEC00139EB3 /* FieldInput+Xib.swift in Sources */, + 314B5F1B23DCCDEC00139EB3 /* FieldProtocol.swift in Sources */, + 314B5F1323DCCDEC00139EB3 /* FieldOutput.swift in Sources */, + 314B5EFF23DCCDEC00139EB3 /* FieldValidatorProtocol.swift in Sources */, + 314B5F2B23DCCDEC00139EB3 /* FieldsEntityInteractor.swift in Sources */, + 314B5F0F23DCCDEC00139EB3 /* FieldDefinitionGroup.swift in Sources */, + 314B5F2F23DCCDEC00139EB3 /* FieldListInteractor.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBAA921BB792600BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBAB321BB792600BEF926 /* JedioKitAppleTVTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB84721B0F3F500391ADF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B5F0923DCCDEC00139EB3 /* FieldDefinition.swift in Sources */, + 314B5EF523DCCDEC00139EB3 /* NullFieldValidator.swift in Sources */, + 314B5EF123DCCDEB00139EB3 /* PhoneFieldValidator.swift in Sources */, + 314B5F1D23DCCDEC00139EB3 /* FieldOutput+Xib.swift in Sources */, + 314B5F3123DCCDEC00139EB3 /* ListInteractor+Input.swift in Sources */, + 314B5EF923DCCDEC00139EB3 /* PasswordFieldValidator.swift in Sources */, + 314B5F0523DCCDEC00139EB3 /* FieldInput.swift in Sources */, + 314B5F2123DCCDEC00139EB3 /* FieldDefinition+Xib.swift in Sources */, + 314B5F1523DCCDEC00139EB3 /* FieldLoader.swift in Sources */, + 314B5F0123DCCDEC00139EB3 /* EmailFieldValidator.swift in Sources */, + 314B5F2523DCCDEC00139EB3 /* FieldInput+Xib.swift in Sources */, + 314B5F1923DCCDEC00139EB3 /* FieldProtocol.swift in Sources */, + 314B5F1123DCCDEC00139EB3 /* FieldOutput.swift in Sources */, + 314B5EFD23DCCDEC00139EB3 /* FieldValidatorProtocol.swift in Sources */, + 314B5F2923DCCDEC00139EB3 /* FieldsEntityInteractor.swift in Sources */, + 314B5F0D23DCCDEC00139EB3 /* FieldDefinitionGroup.swift in Sources */, + 314B5F2D23DCCDEC00139EB3 /* FieldListInteractor.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB85021B0F3F500391ADF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31ACB85A21B0F3F500391ADF /* JedioKitTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 313EBAB021BB792600BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 313EBAA421BB792600BEF926 /* JedioKitAppleTV */; + targetProxy = 313EBAAF21BB792600BEF926 /* PBXContainerItemProxy */; + }; + 313EBAF121BB796900BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleWatch; + targetProxy = 313EBAF021BB796900BEF926 /* PBXContainerItemProxy */; + }; + 313EBAF321BB796900BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKitAppleWatch; + targetProxy = 313EBAF221BB796900BEF926 /* PBXContainerItemProxy */; + }; + 313EBAF521BB796900BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKitAppleWatch; + targetProxy = 313EBAF421BB796900BEF926 /* PBXContainerItemProxy */; + }; + 313EBCA221BB7CD000BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleTV; + targetProxy = 313EBCA121BB7CD000BEF926 /* PBXContainerItemProxy */; + }; + 313EBCA421BB7CD000BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKitAppleTV; + targetProxy = 313EBCA321BB7CD000BEF926 /* PBXContainerItemProxy */; + }; + 313EBCA621BB7CD000BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKitAppleTV; + targetProxy = 313EBCA521BB7CD000BEF926 /* PBXContainerItemProxy */; + }; + 31ACB85721B0F3F500391ADF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31ACB84A21B0F3F500391ADF /* JedioKit */; + targetProxy = 31ACB85621B0F3F500391ADF /* PBXContainerItemProxy */; + }; + 31ACB89C21B0F44700391ADF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 31ACB89B21B0F44700391ADF /* PBXContainerItemProxy */; + }; + 31ACB89E21B0F44700391ADF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKit; + targetProxy = 31ACB89D21B0F44700391ADF /* PBXContainerItemProxy */; + }; + 31ACB8A021B0F44700391ADF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKit; + targetProxy = 31ACB89F21B0F44700391ADF /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 313EBA9E21BB791800BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = JedioKitAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.JedioKitAppleWatch; + PRODUCT_NAME = JedioKit; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _watchOS"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Debug; + }; + 313EBA9F21BB791800BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = JedioKitAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.JedioKitAppleWatch; + PRODUCT_NAME = JedioKit; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _watchOS"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Release; + }; + 313EBAB721BB792600BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = JedioKitAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.JedioKitAppleTV; + PRODUCT_NAME = JedioKit; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _tvOS"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313EBAB821BB792600BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = JedioKitAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.JedioKitAppleTV; + PRODUCT_NAME = JedioKit; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _tvOS"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 313EBABA21BB792600BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = JedioKitAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.JedioKitAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313EBABB21BB792600BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = JedioKitAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.JedioKitAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 31ACB85D21B0F3F500391ADF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 31ACB85E21B0F3F500391ADF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 31ACB86021B0F3F500391ADF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FEA24D0A29E1C1E2DD2351B6 /* Pods-iOS-JedioKit.debug.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = JedioKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.JedioKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _iOS"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31ACB86121B0F3F500391ADF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5B918BE9A744104AEA35008A /* Pods-iOS-JedioKit.release.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = JedioKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.JedioKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _iOS"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 31ACB86321B0F3F500391ADF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E300CFD413F55789DC9D3839 /* Pods-iOS-JedioKitTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = JedioKitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.JedioKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31ACB86421B0F3F500391ADF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B5E7A2D21E0E6E3CCF07850E /* Pods-iOS-JedioKitTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = JedioKitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.JedioKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 313EBA9D21BB791800BEF926 /* Build configuration list for PBXNativeTarget "JedioKitAppleWatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EBA9E21BB791800BEF926 /* Debug */, + 313EBA9F21BB791800BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313EBAB621BB792600BEF926 /* Build configuration list for PBXNativeTarget "JedioKitAppleTV" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EBAB721BB792600BEF926 /* Debug */, + 313EBAB821BB792600BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313EBAB921BB792600BEF926 /* Build configuration list for PBXNativeTarget "JedioKitAppleTVTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EBABA21BB792600BEF926 /* Debug */, + 313EBABB21BB792600BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31ACB84521B0F3F500391ADF /* Build configuration list for PBXProject "JedioKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31ACB85D21B0F3F500391ADF /* Debug */, + 31ACB85E21B0F3F500391ADF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31ACB85F21B0F3F500391ADF /* Build configuration list for PBXNativeTarget "JedioKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31ACB86021B0F3F500391ADF /* Debug */, + 31ACB86121B0F3F500391ADF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31ACB86221B0F3F500391ADF /* Build configuration list for PBXNativeTarget "JedioKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31ACB86321B0F3F500391ADF /* Debug */, + 31ACB86421B0F3F500391ADF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 31ACB84221B0F3F500391ADF /* Project object */; +} diff --git a/JedioKit/JedioKit.xcodeproj/xcshareddata/xcschemes/JedioKit.xcscheme b/JedioKit/JedioKit.xcodeproj/xcshareddata/xcschemes/JedioKit.xcscheme new file mode 100644 index 000000000..8fe9a0b0a --- /dev/null +++ b/JedioKit/JedioKit.xcodeproj/xcshareddata/xcschemes/JedioKit.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/JedioKit/JedioKit.xcodeproj/xcshareddata/xcschemes/JedioKitTests.xcscheme b/JedioKit/JedioKit.xcodeproj/xcshareddata/xcschemes/JedioKitTests.xcscheme new file mode 100644 index 000000000..49695869d --- /dev/null +++ b/JedioKit/JedioKit.xcodeproj/xcshareddata/xcschemes/JedioKitTests.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/JedioKit/JedioKit/Info.plist b/JedioKit/JedioKit/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/JedioKit/JedioKit/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/JedioKit/JedioKit/JedioKit.h b/JedioKit/JedioKit/JedioKit.h new file mode 100644 index 000000000..2d50caf9d --- /dev/null +++ b/JedioKit/JedioKit/JedioKit.h @@ -0,0 +1,19 @@ +// +// JedioKit.h +// JedioKit +// +// Created by Qiang Huang on 11/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for JedioKit. +FOUNDATION_EXPORT double JedioKitVersionNumber; + +//! Project version string for JedioKit. +FOUNDATION_EXPORT const unsigned char JedioKitVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/JedioKit/JedioKit/_Field/FieldDefinition.swift b/JedioKit/JedioKit/_Field/FieldDefinition.swift new file mode 100644 index 000000000..c212e8748 --- /dev/null +++ b/JedioKit/JedioKit/_Field/FieldDefinition.swift @@ -0,0 +1,209 @@ +// +// FieldOutputDefinition.swift +// FieldInteractorLib +// +// Created by Qiang Huang on 10/15/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +@objc open class FieldDefinition: DictionaryEntity { + public func definition(for field: String) -> [String: Any]? { + return data?[field] as? [String: Any] + } + + public func set(definition: [String: Any]?, for field: String) { + data?[field] = definition + } + + override open var parser: Parser { + return Parser.standard + } +} + +public extension FieldDefinition { + var visible: Bool { + return parser.asBoolean(data?["visible"])?.boolValue ?? true + } + + var title: [String: Any]? { + return definition(for: "title") + } + + var subtitle: [String: Any]? { + return definition(for: "subtitle") + } + + var text: [String: Any]? { + return definition(for: "text") + } + + var subtext: [String: Any]? { + return definition(for: "subtext") + } + + var image: [String: Any]? { + return definition(for: "image") + } + + var link: [String: Any]? { + return definition(for: "link") + } + + var dependencies: [String: Any]? { + return definition(for: "dependencies") + } +} + +@objc open class FieldOutputDefinition: FieldDefinition { + public var checked: [String: Any]? { + return definition(for: "checked") + } + + public var strings: [String: Any]? { + return definition(for: "strings") + } + + public var images: [String: Any]? { + return definition(for: "images") + } + + public var items: [FieldOutputDefinition]? + + override open func parse(dictionary: [String: Any]) { + super.parse(dictionary: dictionary) + if let items = parser.asArray(dictionary["items"]) as? [[String: Any]] { + var children = [FieldOutputDefinition]() + for item in items { + let child = FieldOutputDefinition() + child.parse(dictionary: item) + children.append(child) + } + self.items = children + } + } +} + +public enum FieldType { + case text + case strings + case int + case float + case bool + case percent + case image + case images + case signature +} + +public enum FieldValidateType: String { + case email + case password + case phone + case url + case creditcard +} + +@objc open class FieldInputDefinition: FieldDefinition { + @objc public dynamic var field: [String: Any]? { + get { + return definition(for: "field") + } + set { + set(definition: newValue, for: "field") + } + } + + public var fieldType: FieldType { + switch parser.asString(field?["type"]) { + case "text": + return .text + + case "strings": + return .strings + + case "int": + return .int + + case "float": + return .float + + case "bool": + return .bool + + case "percent": + return .percent + + case "image": + return .image + + case "images": + return .images + + case "signature": + return .signature + + default: + return .text + } + } + + @objc public dynamic var defaultValue: Any? { + return field?["default"] + } + + @objc public dynamic var optional: Bool { + return parser.asBoolean(field?["optional"])?.boolValue ?? false + } + + public var validator: FieldValidateType? { + if let string = parser.asString(field?["validator"]) { + return FieldValidateType(rawValue: string) + } + return nil + } + + @objc public dynamic var options: [[String: Any]]? { + get { + return field?["options"] as? [[String: Any]] + } + set { + willChangeValue(forKey: "options") + field?["options"] = newValue + didChangeValue(forKey: "options") + } + } + + public var min: Float? { + return parser.asNumber(field?["min"])?.floatValue + } + + public var max: Float? { + return parser.asNumber(field?["max"])?.floatValue + } + + public func option(for value: Any) -> [String: Any]? { + return options?.first(where: { (option: [String: Any]) -> Bool in + if let itemValue = option["value"] { + if type(of: value) == type(of: itemValue) { + if value is String { + return value as? String == itemValue as? String + } else if value is Int { + return value as? Int == itemValue as? Int + } else { + return false + } + } + } + return false + }) ?? nil + } + + public func option(labeled label: String) -> [String: Any]? { + return options?.first(where: { (option: [String: Any]) -> Bool in + parser.asString(option["text"]) == label + }) ?? nil + } +} diff --git a/JedioKit/JedioKit/_Field/FieldDefinitionGroup.swift b/JedioKit/JedioKit/_Field/FieldDefinitionGroup.swift new file mode 100644 index 000000000..2786c0340 --- /dev/null +++ b/JedioKit/JedioKit/_Field/FieldDefinitionGroup.swift @@ -0,0 +1,108 @@ +// +// FieldDefinitionGroup.swift +// JedioKit +// +// Created by Qiang Huang on 4/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +@objc open class FieldDefinitionGroup: DictionaryEntity { + override open var parser: Parser { + return Parser.standard + } + + public var input: Bool { + return parser.asBoolean(data?["input"])?.boolValue ?? false + } + + public var title: String? { + return parser.asString(data?["title"]) + } + + public var definitions: [FieldDefinition]? + + public func definition(for field: String) -> [String: Any]? { + return data?[field] as? [String: Any] + } + + override open func parse(dictionary: [String: Any]) { + super.parse(dictionary: dictionary) + + if let definitionsData = parser.asArray(data?["fields"]) as? [[String: Any]] { + var definitions = [FieldDefinition]() + for itemDictionary in definitionsData { + let definition = fieldDefinition() + definition.parse(dictionary: itemDictionary) + if definition.visible { + definitions.append(definition) + } + } + self.definitions = definitions + } + } + + open func fieldDefinition() -> FieldDefinition { + return input ? FieldInputDefinition() : FieldOutputDefinition() + } + + open func field(entity: ModelObjectProtocol) -> FieldProtocol? { + return input ? FieldInput() : FieldOutput() + } + + open func transformToFieldData(entity: ModelObjectProtocol) -> [FieldProtocol]? { + if let definitions = self.definitions { + var fields = [FieldProtocol]() + for definition in definitions { + if let field = field(entity: entity) { + field.field = definition + field.entity = entity as? (NSObject & ModelObjectProtocol) + fields.append(field) + if let items = (field as? FieldOutputProtocol)?.items { + for item in items { + fields.append(item) + } + } + } + } + return fields + } + return nil + } + + open func shouldShow(entity: ModelObjectProtocol, field: FieldProtocol) -> Bool { + var shouldShow = true + if let dependencies = field.dependencies { + for arg0 in dependencies { + if shouldShow { + let (key, value) = arg0 + let string = parser.asString(value) + let valueString = parser.asString((entity as? NSObject)?.value(forKey: key)) + if let string = string { + let valueStrings = valueString?.components(separatedBy: ",") + shouldShow = valueStrings?.contains(string) ?? false + } else { + shouldShow = valueString == nil + } + } + } + } + if shouldShow { + if input { + return true + } else { + if let output = field as? FieldOutputProtocol { + if (output as? XibProviderProtocol)?.xib != nil { + return true + } + return output.hasData + } + return false + } + } else { + return false + } + } +} diff --git a/JedioKit/JedioKit/_Field/FieldLoader.swift b/JedioKit/JedioKit/_Field/FieldLoader.swift new file mode 100644 index 000000000..62a48207c --- /dev/null +++ b/JedioKit/JedioKit/_Field/FieldLoader.swift @@ -0,0 +1,42 @@ +// +// FieldLoader.swift +// JedioKit +// +// Created by Qiang Huang on 4/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +@objc open class FieldLoader: NSObject { + @IBInspectable @objc public dynamic var definitionFile: String? { + didSet { + if definitionFile != oldValue { + definitionGroups = load() + } + } + } + + @objc public dynamic var definitionGroups: [FieldDefinitionGroup]? + + open func load() -> [FieldDefinitionGroup]? { + let json = JsonLoader.load(bundles: Bundle.particles, fileName: definitionFile) + if let jsonDictionary = json as? [[String: Any]] { + var definitions = [FieldDefinitionGroup]() + for itemDictionary in jsonDictionary { + if let parsed = parser.asDictionary(itemDictionary) { + let definition = FieldDefinitionGroup() + definition.parse(dictionary: parsed) + definitions.append(definition) + } + } + return definitions + } + return nil + } + + override open var parser: Parser { + return Parser.standard + } +} diff --git a/JedioKit/JedioKit/_Field/FieldProtocol.swift b/JedioKit/JedioKit/_Field/FieldProtocol.swift new file mode 100644 index 000000000..916cffa09 --- /dev/null +++ b/JedioKit/JedioKit/_Field/FieldProtocol.swift @@ -0,0 +1,159 @@ +// +// FieldDataProtocol.swift +// FieldInteractorLib +// +// Created by Qiang Huang on 10/15/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +public protocol FieldProtocol: InteractorProtocol { + var field: FieldDefinition? { get set } + var title: String? { get } + var subtitle: String? { get } + var text: String? { get } + var subtext: String? { get } + var image: String? { get } + var dependencies: [String: Any]? { get } +} + +extension FieldProtocol where Self: NSObject { + public var title: String? { + return text(field?.title) + } + + public var subtitle: String? { + return text(field?.subtitle) + } + + public var text: String? { + return text(field?.text) + } + + public var subtext: String? { + return text(field?.subtext) + } + + public var image: String? { + return text(field?.image) + } + + public var dependencies: [String: Any]? { + return field?.dependencies + } + + internal func bool(_ definition: [String: Any]?) -> Bool? { + if let text = text(definition)?.lowercased() { + if text == "y" || text == "1" || text == "true" || text == "yes" { + return true + } else if text == "n" || text == "0" || text == "false" || text == "no" { + return false + } + } + return nil + } + + internal func hasData(_ definition: [String: Any]?, textOK: Bool = false) -> Bool { + if let entity = entity, let definition = definition { + if let _ = parser.asString(definition["text"]) { + return textOK + } else if let field = parser.asString(definition["field"]), let entity = entity as? NSObject, let _ = parser.asString(entity.value(forKey: field)) { + return true + } + } + return false + } + + internal func text(_ definition: [String: Any]?) -> String? { + if let value = textValue(definition), let definition = definition { + if let options = parser.asDictionary(definition["options"]), let text = parser.asString(options[value]) { + return text + } else { + return value + } + } + return nil + } + + internal func textValue(_ definition: [String: Any]?) -> String? { + if let entity = entity as? (NSObject & ModelObjectProtocol), let definition = definition { + if let text = parser.asString(definition["text"]) { + return text + } else if let field = parser.asString(definition["field"]) { + if let value = entity.value(forKey: field) { + switch parser.asString(definition["type"]) { + case "bool": + return parser.asString(value) + + case "int": + return parser.asNumber(value)?.stringValue + + case "float": + return parser.asNumber(value)?.stringValue + + case "percent": + if let float = parser.asNumber(value)?.floatValue { + let percent = float * 100 + return String(format: "%.2f%%", percent) + } else { + return nil + } + + case "text": + return parser.asString(value) + + default: + return parser.asString(value) + } + } + } + } + return nil + } + + internal func strings(_ definition: [String: Any]?) -> [String]? { + if let entity = entity as? (NSObject & ModelObjectProtocol), let definition = definition, let field = parser.asString(definition["field"]) { + return parser.asStrings(entity.value(forKey: field)) + } + return nil + } +} + +public protocol FieldOutputProtocol: FieldProtocol { + var fieldOutput: FieldOutputDefinition? { get } + + var title: String? { get } + var subtitle: String? { get } + + var text: String? { get } + var subtext: String? { get } + + var checked: Bool? { get } + var items: [FieldOutputProtocol]? { get } + + var hasData: Bool { get } +} + +extension FieldOutputProtocol { + public var fieldOutput: FieldOutputDefinition? { + return field as? FieldOutputDefinition + } +} + +public protocol FieldInputProtocol: FieldProtocol { + var fieldInput: FieldInputDefinition? { get } + + var string: String? { get set } + var value: Any? { get set } + var checked: Bool? { get set } + var int: Int? { get set } + var float: Float? { get set } +} + +extension FieldInputProtocol { + public var fieldInput: FieldInputDefinition? { + return field as? FieldInputDefinition + } +} diff --git a/JedioKit/JedioKit/_Field/_Input/FieldInput.swift b/JedioKit/JedioKit/_Field/_Input/FieldInput.swift new file mode 100644 index 000000000..79be33f5c --- /dev/null +++ b/JedioKit/JedioKit/_Field/_Input/FieldInput.swift @@ -0,0 +1,184 @@ +// +// FieldInteractor.swift +// FieldInteractorLib +// +// Created by Qiang Huang on 10/15/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import Utilities + +@objc public class FieldInput: NSObject, FieldInputProtocol { + #if _iOS || _tvOS + private static var emailValicator = { EmailFieldValidator() }() + private static var phoneValicator = { PhoneFieldValidator() }() + private static var passwordValidator = { PasswordFieldValidator() }() + #endif + private static var nullValidator = { NullFieldValidator() }() + + @objc public dynamic var field: FieldDefinition? { + didSet { + if field !== oldValue { + setDefault() + } + } + } + + @objc public dynamic var entity: ModelObjectProtocol? { + didSet { + if entity !== oldValue { + setDefault() + } + } + } + + public var input: FieldInputDefinition? { + return field as? FieldInputDefinition + } + + public var fieldName: String? { + return parser.asString(fieldInput?.field?["field"]) + } + + public var options: [[String: Any]]? { + return fieldInput?.options + } + + public var value: Any? { + get { + if let fieldName = fieldName, let entity = entity as? NSObject { + return parser.asString(entity.value(forKey: fieldName)) + } + return nil + } + set { + if let fieldName = fieldName { + (entity as? NSObject)?.setValue(newValue, forKey: fieldName) + } + (entity as? DirtyProtocol)?.dirty = true + } + } + + public var string: String? { + get { return parser.asString(value) } + set { value = newValue } + } + + public var checked: Bool? { + get { + if fieldInput?.fieldType == .bool { + return bool(fieldInput?.field) + } + return nil + } + set { + if fieldInput?.fieldType == .bool { + if let newValue = newValue { + if let option = fieldInput?.option(labeled: newValue ? "yes" : "no") { + value = option["value"] + } + } else { + value = nil + } + } + } + } + + public var int: Int? { + get { + if fieldInput?.fieldType == .int { + return parser.asNumber(value)?.intValue + } + return nil + } + set { + if fieldInput?.fieldType == .int { + value = newValue + } + } + } + + public var float: Float? { + get { + if fieldInput?.fieldType == .float { + return parser.asNumber(value)?.floatValue + } + return nil + } + set { + if fieldInput?.fieldType == .float { + value = newValue + } + } + } + + public var percent: Float? { + get { + if fieldInput?.fieldType == .percent { + return parser.asNumber(value)?.floatValue + } + return nil + } + set { + if fieldInput?.fieldType == .percent { + value = newValue + } + } + } + + @objc public dynamic var strings: [String]? { + get { return string?.components(separatedBy: ",") } + set { value = newValue?.joined(separator: ",") } + } + + private var validator: FieldValidatorProtocol? { + #if _iOS || _tvOS + switch input?.fieldType { + case .text?: + switch input?.validator { + case .email?: + return type(of: self).emailValicator + + case .password?: + return type(of: self).passwordValidator + + case .phone?: + return type(of: self).phoneValicator + + default: + break + } + default: + break + } + #endif + return type(of: self).nullValidator + } + + public func validate() -> Error? { + if field?.definition(for: "field") != nil { + let optional = input?.optional ?? false + let fieldName = field?.title?["text"] as? String ?? field?.subtext?["text"] as? String ?? "" + let fieldNameLocalized = DataLocalizer.shared?.localize(path: fieldName, params: nil) ?? fieldName + return validator?.validate(field: fieldNameLocalized, data: string, optional: optional) + } + return nil + } + + private func setDefault() { + if let defaultValue = input?.defaultValue, value == nil { + value = defaultValue + } + } +} + +extension FieldInput: RoutingOriginatorProtocol { + public func routingRequest() -> RoutingRequest? { + if let link = field?.link, let urlString = parser.asString(link["text"]), let url = URL(string: urlString) { + return RoutingRequest(scheme: url.scheme, host: url.host, path: url.path, params: url.params) + } + return nil + } +} diff --git a/JedioKit/JedioKit/_Field/_Output/FieldOutput.swift b/JedioKit/JedioKit/_Field/_Output/FieldOutput.swift new file mode 100644 index 000000000..4ef21f32e --- /dev/null +++ b/JedioKit/JedioKit/_Field/_Output/FieldOutput.swift @@ -0,0 +1,107 @@ +// +// FieldInteractor.swift +// FieldInteractorLib +// +// Created by Qiang Huang on 10/15/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import Utilities + +@objc public class FieldOutput: NSObject, FieldOutputProtocol, RoutingOriginatorProtocol { + @objc public dynamic var field: FieldDefinition? { + didSet { + if field !== oldValue { + updateChildren() + } + } + } + + @objc public dynamic var entity: ModelObjectProtocol? { + didSet { + if entity !== oldValue { + updateChildren() + } + } + } + + public var title: String? { + return text(fieldOutput?.title) + } + + public var subtitle: String? { + return text(fieldOutput?.subtitle) + } + + public var text: String? { + var text = self.text(fieldOutput?.text) + return text + } + + public var subtext: String? { + return text(fieldOutput?.subtext) + } + + public var checked: Bool? { + return bool(fieldOutput?.checked) + } + + @objc public dynamic var link: String? { + return text(fieldOutput?.link) + } + + @objc public dynamic var strings: [String]? { + return strings(fieldOutput?.strings) + } + + @objc public dynamic var images: [String]? { + return strings(fieldOutput?.images) + } + + public var items: [FieldOutputProtocol]? + + @objc public dynamic var hasData: Bool { + if entity != nil && fieldOutput != nil { + return hasData(fieldOutput?.title) || hasData(fieldOutput?.subtitle) || hasData(fieldOutput?.text) || hasData(fieldOutput?.subtext) || hasData(fieldOutput?.image) || hasData(fieldOutput?.checked) || hasData(fieldOutput?.strings) || hasData(fieldOutput?.images) || hasData(fieldOutput?.link, textOK: true) || (items?.count ?? 0) > 0 + } + return false + } + + public func updateChildren() { + if let definitions = fieldOutput?.items { + var fields: [FieldOutput] = [FieldOutput]() + for definition in definitions { + let field = FieldOutput() + field.entity = entity + field.field = definition + if field.hasData { + fields.append(field) + } + } + if fields.count > 0 { + items = fields + } else { + items = nil + } + } else { + items = nil + } + } + + public func routingRequest() -> RoutingRequest? { + if let link = link { + return RoutingRequest(url: link) + } + return nil + } + + public override func isEqual(_ object: Any?) -> Bool { + if let output2 = object as? FieldOutput { + return text == output2.text && title == output2.title && subtitle == output2.subtitle && checked == output2.checked && image == output2.image && strings == output2.strings && images == output2.images && link == output2.link + } else { + return false + } + } +} diff --git a/JedioKit/JedioKit/_Interactor/FieldListInteractor.swift b/JedioKit/JedioKit/_Interactor/FieldListInteractor.swift new file mode 100644 index 000000000..807813527 --- /dev/null +++ b/JedioKit/JedioKit/_Interactor/FieldListInteractor.swift @@ -0,0 +1,13 @@ +// +// FieldListInteractor.swift +// JedioKit +// +// Created by Qiang Huang on 9/2/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit + +open class FieldListInteractor: ListInteractor { + open var definitionGroup: FieldDefinitionGroup? +} diff --git a/JedioKit/JedioKit/_Interactor/FieldsEntityInteractor.swift b/JedioKit/JedioKit/_Interactor/FieldsEntityInteractor.swift new file mode 100644 index 000000000..8379d78b8 --- /dev/null +++ b/JedioKit/JedioKit/_Interactor/FieldsEntityInteractor.swift @@ -0,0 +1,188 @@ +// +// FieldsEntityListInteractor.swift +// JedioKit +// +// Created by Qiang Huang on 9/2/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +open class FieldsEntityInteractor: BaseInteractor, InteractorProtocol { + @IBOutlet open var fieldLoader: FieldLoader? { + didSet { + changeObservation(from: oldValue, to: fieldLoader, keyPath: #keyPath(FieldLoader.definitionFile)) { [weak self] _, _, _, _ in + if let self = self { + self.definitionGroups = self.fieldLoader?.definitionGroups + } + } + } + } + + @objc public dynamic var definitionGroups: [FieldDefinitionGroup]? { + didSet { + loadFields() + } + } + + @IBOutlet open var list: ListInteractor? { + didSet { + if list !== oldValue { + loadFields() + } + } + } + + open var entity: ModelObjectProtocol? { + didSet { + if entity !== oldValue { + if entity != nil { + loadFields() + } + let dictionaryEntity = entity as? DictionaryEntity + changeObservation(from: oldValue, to: dictionaryEntity, keyPath: #keyPath(DictionaryEntity.data)) { [weak self] _, _, _, _ in + self?.filter() + } + } + } + } + + open var groups: [FieldListInteractor]? { + didSet { + if groups != oldValue { + filter() + } + } + } + + private var filtered: [FieldListInteractor]? { + didSet { + if filtered != oldValue { + updateSections() + } + } + } + + open var data: [String: Any]? { + if let filtered = filtered { + var data = [String: Any]() + for section in filtered { + if let fields = section.list { + for field in fields { + if let field = field as? FieldInput, let key = field.fieldName, let value = field.value { + data[key] = value + } + } + } + } + return data + } + return nil + } + + open var keys: [String]? { + if let groups = groups { + var keys = [String]() + for group in groups { + if let list = group.list { + for item in list { + if let field = item as? FieldProtocol, let key = field.field?.definition(for: "field")?["field"] as? String { + keys.append(key) + } + } + } + } + return keys + } + return nil + } + + open func loadFields() { + if let definitionGroups = definitionGroups, let list = list, let entity = entity { + list.parent = entity + let groups = definitionGroups.compactMap({ (definitionGroup: FieldDefinitionGroup) -> FieldListInteractor? in + let group = FieldListInteractor() + group.title = definitionGroup.title + group.definitionGroup = definitionGroup + group.list = definitionGroup.transformToFieldData(entity: entity) + return (group.list?.count != 0) ? group : nil + }) + self.groups = groups + } + } + + public func filter() { + if let groups = groups { + var filteredList: [FieldListInteractor] = [] + for i in 0 ..< groups.count { + let group = groups[i] + var trimmed: FieldListInteractor? + if let filtered = filtered, i < filtered.count { + trimmed = filtered[i] + } else { + trimmed = FieldListInteractor() + } + if let trimmed = trimmed { + trimmed.title = group.title + trimmed.definitionGroup = group.definitionGroup + trimmed.list = filter(fields: group.list as? [FieldProtocol], definitions: group.definitionGroup) + filteredList.append(trimmed) + } + } + filtered = filteredList + } + } + + public func filter(fields: [FieldProtocol]?, definitions: FieldDefinitionGroup?) -> [FieldProtocol]? { + if let fields = fields, let definitions = definitions, let entity = entity { + return fields.filter { (field) -> Bool in + definitions.shouldShow(entity: entity, field: field) + } + } else { + return nil + } + } + + public func updateSections() { + if let filtered = filtered { + let notEmpty = filtered.compactMap { (section) -> FieldListInteractor? in + (section.list?.count ?? 0) != 0 ? section : nil + } + list?.sync(notEmpty) + } else { + list?.sync(nil) + } + } + + public func refresh() { + if let filtered = filtered, let groups = groups, filtered.count == groups.count { + for i in 0 ..< filtered.count { + let list = filtered[i] + let group = groups[i] + list.sync(filter(fields: group.list as? [FieldProtocol], definitions: list.definitionGroup)) + } + updateSections() + } + } + + open func validateInput() -> Error? { + if let filtered = filtered { + var error: Error? + _ = filtered.first { (list) -> Bool in + if let fields = list.list { + _ = fields.first { (field) -> Bool in + if let field = field as? FieldInput { + error = field.validate() + return error != nil + } + return false + } + } + return error != nil + } + return error + } + return nil + } +} diff --git a/JedioKit/JedioKit/_Interactor/ListInteractor+Input.swift b/JedioKit/JedioKit/_Interactor/ListInteractor+Input.swift new file mode 100644 index 000000000..47b880f8f --- /dev/null +++ b/JedioKit/JedioKit/_Interactor/ListInteractor+Input.swift @@ -0,0 +1,22 @@ +// +// FieldInputListInteractor.swift +// JedioKit +// +// Created by Qiang Huang on 7/7/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit + +extension ListInteractor { + public func validateInput() -> Error? { + let invalidField = list?.first(where: { (item) -> Bool in + if let fieldInput = item as? FieldInput { + return fieldInput.validate() == nil + } else { + return false + } + }) + return (invalidField as? FieldInput)?.validate() + } +} diff --git a/JedioKit/JedioKit/_Validator/EmailFieldValidator.swift b/JedioKit/JedioKit/_Validator/EmailFieldValidator.swift new file mode 100644 index 000000000..d48b11d33 --- /dev/null +++ b/JedioKit/JedioKit/_Validator/EmailFieldValidator.swift @@ -0,0 +1,42 @@ +// +// EmailFieldValidator.swift +// JedioKit +// +// Created by Qiang Huang on 7/7/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +#if _iOS || _tvOS + import Validator + + @objc public class EmailFieldValidator: NSObject, FieldValidatorProtocol { + public func validate(field: String?, data: Any?, optional: Bool) -> Error? { + if let string = data as? String { + let validationError = TextValidationError("Invalid email") + let emailRule = ValidationRulePattern(pattern: EmailValidationPattern.standard, error: validationError) + if string.validate(rule: emailRule) == .valid { + return nil + } else { + let className = self.className() + if let field = field?.lowercased() { + return NSError(domain: "\(className).email.invalid", code: 0, userInfo: ["message": "Please enter a valid \(field)."]) + } else { + return NSError(domain: "\(className).email.invalid", code: 0, userInfo: ["message": "Please enter a valid email."]) + } + } + } else { + if optional { + return nil + } else { + let className = self.className() + if let field = field?.lowercased() { + return NSError(domain: "\(className).email.missing", code: 0, userInfo: ["message": "Please enter \(field)."]) + } else { + return NSError(domain: "\(className).email.invalid", code: 0, userInfo: ["message": "Please enter an email."]) + } + } + } + } + } +#endif diff --git a/JedioKit/JedioKit/_Validator/FieldValidatorProtocol.swift b/JedioKit/JedioKit/_Validator/FieldValidatorProtocol.swift new file mode 100644 index 000000000..3fea6ef5b --- /dev/null +++ b/JedioKit/JedioKit/_Validator/FieldValidatorProtocol.swift @@ -0,0 +1,26 @@ +// +// FieldValidatorProtocol.swift +// JedioKit +// +// Created by Qiang Huang on 7/7/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +#if _iOS || _tvOS + import Validator +#endif + +@objc public protocol FieldValidatorProtocol { + func validate(field: String?, data: Any?, optional: Bool) -> Error? +} + +#if _iOS || _tvOS + public class TextValidationError: ValidationError { + public var message: String + + public init(_ message: String) { + self.message = message + } + } +#endif diff --git a/JedioKit/JedioKit/_Validator/NullFieldValidator.swift b/JedioKit/JedioKit/_Validator/NullFieldValidator.swift new file mode 100644 index 000000000..30c0075ba --- /dev/null +++ b/JedioKit/JedioKit/_Validator/NullFieldValidator.swift @@ -0,0 +1,28 @@ +// +// NullFieldValidator.swift +// JedioKit +// +// Created by Qiang Huang on 1/19/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +@objc public class NullFieldValidator: NSObject, FieldValidatorProtocol { + public func validate(field: String?, data: Any?, optional: Bool) -> Error? { + if let _ = data as? String { + return nil + } else { + if optional { + return nil + } else { + let className = self.className() + if let field = field?.lowercased() { + return NSError(domain: "\(className).data.missing", code: 0, userInfo: ["message": "Please enter \(field)."]) + } else { + return NSError(domain: "\(className).data.missing", code: 0, userInfo: ["message": "Please enter required data."]) + } + } + } + } +} diff --git a/JedioKit/JedioKit/_Validator/PasswordFieldValidator.swift b/JedioKit/JedioKit/_Validator/PasswordFieldValidator.swift new file mode 100644 index 000000000..f8fc9d4f4 --- /dev/null +++ b/JedioKit/JedioKit/_Validator/PasswordFieldValidator.swift @@ -0,0 +1,40 @@ +// +// PasswordFieldValidator.swift +// JedioKit +// +// Created by Qiang Huang on 7/7/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +#if _iOS || _tvOS + import Validator + + @objc public class PasswordFieldValidator: NSObject, FieldValidatorProtocol { + public func validate(field: String?, data: Any?, optional: Bool) -> Error? { + if let string = data as? String { + if string.count > 6 { + return nil + } else { + let className = self.className() + if let field = field?.lowercased() { + return NSError(domain: "\(className).password.tooshort", code: 0, userInfo: ["message": "\(field) is too short."]) + } else { + return NSError(domain: "\(className).password.tooshort", code: 0, userInfo: ["message": "Please enter a valid passport."]) + } + } + } else { + if optional { + return nil + } else { + let className = self.className() + if let field = field?.lowercased() { + return NSError(domain: "\(className).password.missing", code: 0, userInfo: ["message": "Please enter \(field)."]) + } else { + return NSError(domain: "\(className).password.missing", code: 0, userInfo: ["message": "Please enter a password."]) + } + } + } + } + } +#endif diff --git a/JedioKit/JedioKit/_Validator/PhoneFieldValidator.swift b/JedioKit/JedioKit/_Validator/PhoneFieldValidator.swift new file mode 100644 index 000000000..23fbeecd3 --- /dev/null +++ b/JedioKit/JedioKit/_Validator/PhoneFieldValidator.swift @@ -0,0 +1,46 @@ +// +// PhoneFieldValidator.swift +// JedioKit +// +// Created by John Huang on 7/7/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities +#if _iOS || _tvOS + import libPhoneNumber_iOS + + @objc public class PhoneFieldValidator: NSObject, FieldValidatorProtocol { + public func validate(field: String?, data: Any?, optional: Bool) -> Error? { + if let string = data as? String { + let phoneUtil = NBPhoneNumberUtil() + do { + let phoneNumber: NBPhoneNumber = try phoneUtil.parse(string, defaultRegion: "US") + let formattedString: String = try phoneUtil.format(phoneNumber, numberFormat: .E164) + + NSLog("[%@]", formattedString) + return nil + } catch let error as NSError { + Console.shared.log(error.localizedDescription) + } + let className = self.className() + if let field = field?.lowercased() { + return NSError(domain: "\(className).phone.invalid", code: 0, userInfo: ["message": "Please enter a valid \(field)."]) + } else { + return NSError(domain: "\(className).phone.invalid", code: 0, userInfo: ["message": "Please enter a valid phone number."]) + } + } else { + if optional { + return nil + } else { + let className = self.className() + if let field = field?.lowercased() { + return NSError(domain: "\(className).phone.missing", code: 0, userInfo: ["message": "Please enter \(field)."]) + } else { + return NSError(domain: "\(className).phone.missing", code: 0, userInfo: ["message": "Please enter a phone number."]) + } + } + } + } + } +#endif diff --git a/JedioKit/JedioKit/_Xib/FieldDefinition+Xib.swift b/JedioKit/JedioKit/_Xib/FieldDefinition+Xib.swift new file mode 100644 index 000000000..0694fb99d --- /dev/null +++ b/JedioKit/JedioKit/_Xib/FieldDefinition+Xib.swift @@ -0,0 +1,16 @@ +// +// XibFieldOutputDefinition.swift +// FieldPresenterLib +// +// Created by Qiang Huang on 10/16/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +extension FieldDefinition { + public var xib: String? { + return parser.asString(data?["xib"]) + } +} diff --git a/JedioKit/JedioKit/_Xib/FieldInput+Xib.swift b/JedioKit/JedioKit/_Xib/FieldInput+Xib.swift new file mode 100644 index 000000000..0211bd7e4 --- /dev/null +++ b/JedioKit/JedioKit/_Xib/FieldInput+Xib.swift @@ -0,0 +1,79 @@ +// +// FieldOutput+Xib.swift +// FieldPresenterLib +// +// Created by Qiang Huang on 10/16/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit + +extension FieldInput: XibProviderProtocol { + public var xib: String? { + if let fieldInput = fieldInput { + if let xib = fieldInput.xib { + return xib + } else if fieldInput.link != nil { + return "field_link" + } else { + let hasOptions = fieldInput.options != nil + switch fieldInput.fieldType { + case .text: + return hasOptions ? "field_input_grid_text" : "field_input_textfield_text" + + case .int: + if hasOptions { + return "field_input_grid_int" + } else if fieldInput.min != nil && fieldInput.max != nil { + return "field_input_slider_int" + } else { + return "field_input_textfield_int" + } + + case .float: + if fieldInput.min != nil && fieldInput.max != nil { + return "field_input_slider_float" + } else { + return "field_input_textfield_float" + } + + case .percent: + return "field_input_slider_percent" + + case .strings: + return "field_input_grid_strings" + + case .bool: + #if _iOS + return "field_input_switch" + #else + return "field_blank" + #endif + + case .image: + #if _iOS + return "field_button_image" + #else + return "field_blank" + #endif + + case .images: + #if _iOS + return "field_input_grid_images" + #else + return "field_blank" + #endif + + case .signature: + #if _iOS + return "field_input_button_signature" + #else + return "field_blank" + #endif + } + } + } else { + return "field_blank" + } + } +} diff --git a/JedioKit/JedioKit/_Xib/FieldOutput+Xib.swift b/JedioKit/JedioKit/_Xib/FieldOutput+Xib.swift new file mode 100644 index 000000000..4267a9ca3 --- /dev/null +++ b/JedioKit/JedioKit/_Xib/FieldOutput+Xib.swift @@ -0,0 +1,33 @@ +// +// FieldOutput+Xib.swift +// FieldPresenterLib +// +// Created by Qiang Huang on 10/16/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit + +extension FieldOutput: XibProviderProtocol { + public var xib: String? { + if let xib = fieldOutput?.xib { + return xib + } else { + if let _ = fieldOutput?.checked { + return "field_checkmark" + } else if let _ = fieldOutput?.image { + return "field_image" + } else if let _ = fieldOutput?.strings { + return "field_strings" + } else if let _ = fieldOutput?.images { + return "field_images" + } else if let _ = fieldOutput?.subtext { + return "field_text_long" + } else if let _ = fieldOutput?.title { + return "field_text" + } else { + return "field_text_long" + } + } + } +} diff --git a/JedioKit/JedioKitAppleTV/Info.plist b/JedioKit/JedioKitAppleTV/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/JedioKit/JedioKitAppleTV/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/JedioKit/JedioKitAppleTV/JedioKit.h b/JedioKit/JedioKitAppleTV/JedioKit.h new file mode 100644 index 000000000..a0854a4f5 --- /dev/null +++ b/JedioKit/JedioKitAppleTV/JedioKit.h @@ -0,0 +1,19 @@ +// +// JedioKitAppleTV.h +// JedioKitAppleTV +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for JedioKitAppleTV. +FOUNDATION_EXPORT double JedioKitAppleTVVersionNumber; + +//! Project version string for JedioKitAppleTV. +FOUNDATION_EXPORT const unsigned char JedioKitAppleTVVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/JedioKit/JedioKitAppleTVTests/Info.plist b/JedioKit/JedioKitAppleTVTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/JedioKit/JedioKitAppleTVTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/JedioKit/JedioKitAppleTVTests/JedioKitAppleTVTests.swift b/JedioKit/JedioKitAppleTVTests/JedioKitAppleTVTests.swift new file mode 100644 index 000000000..09a331c3b --- /dev/null +++ b/JedioKit/JedioKitAppleTVTests/JedioKitAppleTVTests.swift @@ -0,0 +1,32 @@ +// +// JedioKitAppleTVTests.swift +// JedioKitAppleTVTests +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +@testable import JedioKit +import XCTest + +class JedioKitAppleTVTests: XCTestCase { + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. + } + } +} diff --git a/JedioKit/JedioKitAppleWatch/Info.plist b/JedioKit/JedioKitAppleWatch/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/JedioKit/JedioKitAppleWatch/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/JedioKit/JedioKitAppleWatch/JedioKit.h b/JedioKit/JedioKitAppleWatch/JedioKit.h new file mode 100644 index 000000000..3ac96f582 --- /dev/null +++ b/JedioKit/JedioKitAppleWatch/JedioKit.h @@ -0,0 +1,19 @@ +// +// JedioKitAppleWatch.h +// JedioKitAppleWatch +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for JedioKitAppleWatch. +FOUNDATION_EXPORT double JedioKitAppleWatchVersionNumber; + +//! Project version string for JedioKitAppleWatch. +FOUNDATION_EXPORT const unsigned char JedioKitAppleWatchVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/JedioKit/JedioKitTests/Info.plist b/JedioKit/JedioKitTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/JedioKit/JedioKitTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/JedioKit/JedioKitTests/JedioKitTests.swift b/JedioKit/JedioKitTests/JedioKitTests.swift new file mode 100644 index 000000000..9edbf1a02 --- /dev/null +++ b/JedioKit/JedioKitTests/JedioKitTests.swift @@ -0,0 +1,34 @@ +// +// JedioKitTests.swift +// JedioKitTests +// +// Created by Qiang Huang on 11/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import XCTest +@testable import JedioKit + +class JedioKitTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..4fcadeb3d --- /dev/null +++ b/LICENSE @@ -0,0 +1,802 @@ +Copyright (C) dYdX Trading Inc. + +Subject to your compliance with applicable law and the v4 Terms of Use, available at dydx.exchange/legal, you are granted the right to use the Program or Licensed Work (defined below) under the terms of the GNU Affero General Public License as set forth below; provided, however, that if you violate any such applicable law in your use of the Program or Licensed Work, all of your rights and licenses to use (including any rights to reproduce, distribute, install or modify) the Program or Licensed Work will automatically and immediately terminate. + + +The “Program” or “Licensed Work” shall mean any of the following: dydxprotocol/cosmos-sdk, dydxprotocol/cometbft, dydxprotocol/v4-chain, dydxprotocol/v4-clients, dydxprotocol/v4-web, dydxprotocol/v4-abacus, dydxprotocol/v4-localization, dydxprotocol/v4-documentation, and any dYdX or dYdX Trading Inc. repository reflecting a copy of, or link to, this license. + + +The GNU Affero General Public License +Version 3, 19 November 2007 + + +Copyright (C) 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + + + Preamble + + + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + + The precise terms and conditions for copying, distribution and +modification follow. + + + + TERMS AND CONDITIONS + + + 0. Definitions. + + + "This License" refers to version 3 of the GNU Affero General Public License. + + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + + A "covered work" means either the unmodified Program or a work based +on the Program. + + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + + + 1. Source Code. + + + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + + + The Corresponding Source for a work in source code form is that +same work. + + + 2. Basic Permissions. + + + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; Section 10 +makes it unnecessary. + + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + + + 4. Conveying Verbatim Copies. + + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with Section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + + 5. Conveying Modified Source Versions. + + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of Section 4, provided that you also meet all of these conditions: + + + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under Section + 7. This requirement modifies the requirement in Section 4 to + "keep intact all notices". + + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable Section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + + + 6. Conveying Non-Source Forms. + + + + You may convey a covered work in object code form under the terms +of Sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with Subsection 6b. + + + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under Subsection 6d. + + + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + + + 7. Additional Terms. + + + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + + + a) Disclaiming warranty or limiting liability differently from the + terms of Sections 15 and 16 of this License; or + + + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of Section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + + + 8. Termination. + + + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of Section 11). + + + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under Section 10. + + + + 9. Acceptance Not Required for Having Copies. + + + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + + + 10. Automatic Licensing of Downstream Recipients. + + + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + + + 11. Patents. + + + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + + + 12. No Surrender of Others' Freedom. + + + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + + + 13. Remote Network Interaction; Use with the GNU General Public License. + + + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + + + 14. Revised Versions of this License. + + + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + + + 15. Disclaimer of Warranty. + + + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + + + 16. Limitation of Liability. + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + 17. Interpretation of Sections 15 and 16. + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + +For more information about this software, see https://dydx.exchange. + Copyright (C) dYdX Trading Inc. diff --git a/ParticlesCommonModels/ParticlesCommonModels.xcodeproj/project.pbxproj b/ParticlesCommonModels/ParticlesCommonModels.xcodeproj/project.pbxproj new file mode 100644 index 000000000..61f2f6087 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels.xcodeproj/project.pbxproj @@ -0,0 +1,1387 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 310CDE5C21C8C133009665CF /* ParticlesCommonModels.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310CDE5221C8C133009665CF /* ParticlesCommonModels.framework */; }; + 310CDE6121C8C133009665CF /* ParticlesCommonModelsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310CDE6021C8C133009665CF /* ParticlesCommonModelsTests.swift */; }; + 310CDE6321C8C133009665CF /* ParticlesCommonModels.h in Headers */ = {isa = PBXBuildFile; fileRef = 310CDE5521C8C133009665CF /* ParticlesCommonModels.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 310CDEBD21C8C1FC009665CF /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310CDEAB21C8C1D8009665CF /* Utilities.framework */; }; + 310CDEBE21C8C206009665CF /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310CDE9321C8C1D8009665CF /* ParticlesKit.framework */; }; + 310CDF1E21C8C3DC009665CF /* ParticlesCommonModels.h in Headers */ = {isa = PBXBuildFile; fileRef = 310CDF1C21C8C3DC009665CF /* ParticlesCommonModels.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 310CDF3021C8C3F1009665CF /* ParticlesCommonModels.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310CDF2721C8C3F0009665CF /* ParticlesCommonModels.framework */; }; + 310CDF3521C8C3F1009665CF /* ParticlesCommonModelsAppleTVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310CDF3421C8C3F1009665CF /* ParticlesCommonModelsAppleTVTests.swift */; }; + 310CDF3721C8C3F1009665CF /* ParticlesCommonModels.h in Headers */ = {isa = PBXBuildFile; fileRef = 310CDF2921C8C3F0009665CF /* ParticlesCommonModels.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 310CDF5E21C8C422009665CF /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310CDEAD21C8C1D8009665CF /* Utilities.framework */; }; + 310CDF5F21C8C429009665CF /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310CDE9521C8C1D8009665CF /* ParticlesKit.framework */; }; + 310CDF6421C8C43E009665CF /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310CDEAF21C8C1D8009665CF /* Utilities.framework */; }; + 310CDF6521C8C447009665CF /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310CDE9721C8C1D8009665CF /* ParticlesKit.framework */; }; + 314B607F23DCCE4600139EB3 /* ExternalAppInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B606C23DCCE4500139EB3 /* ExternalAppInteractor.swift */; }; + 314B608023DCCE4600139EB3 /* ExternalAppInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B606C23DCCE4500139EB3 /* ExternalAppInteractor.swift */; }; + 314B608123DCCE4600139EB3 /* ExternalAppInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B606C23DCCE4500139EB3 /* ExternalAppInteractor.swift */; }; + 314B608723DCCE4600139EB3 /* SimpleAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B606F23DCCE4500139EB3 /* SimpleAction.swift */; }; + 314B608823DCCE4600139EB3 /* SimpleAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B606F23DCCE4500139EB3 /* SimpleAction.swift */; }; + 314B608923DCCE4600139EB3 /* SimpleAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B606F23DCCE4500139EB3 /* SimpleAction.swift */; }; + 314B608B23DCCE4600139EB3 /* DislikedListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607123DCCE4500139EB3 /* DislikedListInteractor.swift */; }; + 314B608C23DCCE4600139EB3 /* DislikedListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607123DCCE4500139EB3 /* DislikedListInteractor.swift */; }; + 314B608D23DCCE4600139EB3 /* DislikedListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607123DCCE4500139EB3 /* DislikedListInteractor.swift */; }; + 314B608F23DCCE4600139EB3 /* LikedListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607223DCCE4500139EB3 /* LikedListInteractor.swift */; }; + 314B609023DCCE4600139EB3 /* LikedListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607223DCCE4500139EB3 /* LikedListInteractor.swift */; }; + 314B609123DCCE4600139EB3 /* LikedListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607223DCCE4500139EB3 /* LikedListInteractor.swift */; }; + 314B609323DCCE4600139EB3 /* LikedKeysInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607323DCCE4500139EB3 /* LikedKeysInteractor.swift */; }; + 314B609423DCCE4600139EB3 /* LikedKeysInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607323DCCE4500139EB3 /* LikedKeysInteractor.swift */; }; + 314B609523DCCE4600139EB3 /* LikedKeysInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607323DCCE4500139EB3 /* LikedKeysInteractor.swift */; }; + 314B609723DCCE4600139EB3 /* BrowsingListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607523DCCE4500139EB3 /* BrowsingListInteractor.swift */; }; + 314B609823DCCE4600139EB3 /* BrowsingListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607523DCCE4500139EB3 /* BrowsingListInteractor.swift */; }; + 314B609923DCCE4600139EB3 /* BrowsingListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607523DCCE4500139EB3 /* BrowsingListInteractor.swift */; }; + 314B609B23DCCE4600139EB3 /* LocalSavedSearchCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607623DCCE4500139EB3 /* LocalSavedSearchCacheInteractor.swift */; }; + 314B609C23DCCE4600139EB3 /* LocalSavedSearchCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607623DCCE4500139EB3 /* LocalSavedSearchCacheInteractor.swift */; }; + 314B609D23DCCE4600139EB3 /* LocalSavedSearchCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607623DCCE4500139EB3 /* LocalSavedSearchCacheInteractor.swift */; }; + 314B609F23DCCE4600139EB3 /* FilteredListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607723DCCE4500139EB3 /* FilteredListInteractor.swift */; }; + 314B60A023DCCE4600139EB3 /* FilteredListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607723DCCE4500139EB3 /* FilteredListInteractor.swift */; }; + 314B60A123DCCE4600139EB3 /* FilteredListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607723DCCE4500139EB3 /* FilteredListInteractor.swift */; }; + 314B60A323DCCE4600139EB3 /* Doer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607923DCCE4500139EB3 /* Doer.swift */; }; + 314B60A423DCCE4600139EB3 /* Doer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607923DCCE4500139EB3 /* Doer.swift */; }; + 314B60A523DCCE4600139EB3 /* Doer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607923DCCE4500139EB3 /* Doer.swift */; }; + 314B60A723DCCE4600139EB3 /* LikeDoer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607A23DCCE4500139EB3 /* LikeDoer.swift */; }; + 314B60A823DCCE4600139EB3 /* LikeDoer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607A23DCCE4500139EB3 /* LikeDoer.swift */; }; + 314B60A923DCCE4600139EB3 /* LikeDoer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B607A23DCCE4500139EB3 /* LikeDoer.swift */; }; + 316FD74826B324E200F74AAD /* SelfFilteredListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316FD74726B324E200F74AAD /* SelfFilteredListInteractor.swift */; }; + 31EAFDF825C6352300412F11 /* NavigationModelProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EAFDF725C6352300412F11 /* NavigationModelProtocols.swift */; }; + 31EAFE0925C635F900412F11 /* NavigationObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EAFE0825C635F900412F11 /* NavigationObject.swift */; }; + 31EAFE0A25C635F900412F11 /* NavigationObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EAFE0825C635F900412F11 /* NavigationObject.swift */; }; + 31EAFE0B25C635F900412F11 /* NavigationObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EAFE0825C635F900412F11 /* NavigationObject.swift */; }; + 31EAFE1125C635FE00412F11 /* NavigationModelProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EAFDF725C6352300412F11 /* NavigationModelProtocols.swift */; }; + 31EAFE1225C635FF00412F11 /* NavigationModelProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EAFDF725C6352300412F11 /* NavigationModelProtocols.swift */; }; + 31EAFE1925C638BD00412F11 /* NavigationInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EAFE1825C638BD00412F11 /* NavigationInteractor.swift */; }; + 31EAFE1A25C638BD00412F11 /* NavigationInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EAFE1825C638BD00412F11 /* NavigationInteractor.swift */; }; + 31EAFE1B25C638BD00412F11 /* NavigationInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EAFE1825C638BD00412F11 /* NavigationInteractor.swift */; }; + 46E7CFF8DC2DA9F219DA0E91 /* Pods_iOS_ParticlesCommonModels.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA6E48B566D1AE00FAF8D22D /* Pods_iOS_ParticlesCommonModels.framework */; }; + 64FBF0ADDBA6E433D9DB8596 /* Pods_iOS_ParticlesCommonModelsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0B93456290A80728DE3A3E2 /* Pods_iOS_ParticlesCommonModelsTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 310CDE5D21C8C133009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDE4921C8C132009665CF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 310CDE5121C8C133009665CF; + remoteInfo = ParticlesCommonModels; + }; + 310CDE9221C8C1D8009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDE8821C8C1D8009665CF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FDA21B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 310CDE9421C8C1D8009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDE8821C8C1D8009665CF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 319682B421B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 310CDE9621C8C1D8009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDE8821C8C1D8009665CF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16121BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 310CDE9A21C8C1D8009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDE8821C8C1D8009665CF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FE321B0E9C4001775BA; + remoteInfo = ParticlesKitTests; + }; + 310CDE9C21C8C1D8009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDE8821C8C1D8009665CF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16921BA246A00BEF926; + remoteInfo = ParticlesKitAppleTVTests; + }; + 310CDEAA21C8C1D8009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDEA021C8C1D8009665CF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 310CDEAC21C8C1D8009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDEA021C8C1D8009665CF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 310CDEAE21C8C1D8009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDEA021C8C1D8009665CF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 310CDEB221C8C1D8009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDEA021C8C1D8009665CF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 310CDEB421C8C1D8009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDEA021C8C1D8009665CF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 310CDEB821C8C1E4009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDE8821C8C1D8009665CF /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 311C0FD921B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 310CDEBA21C8C1E4009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDEA021C8C1D8009665CF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 310CDF3121C8C3F1009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDE4921C8C132009665CF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 310CDF2621C8C3F0009665CF; + remoteInfo = ParticlesCommonModelsAppleTV; + }; + 310CDF5A21C8C412009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDE8821C8C1D8009665CF /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 319682B321B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 310CDF5C21C8C412009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDEA021C8C1D8009665CF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 3196823621B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 310CDF6021C8C434009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDE8821C8C1D8009665CF /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313EB16021BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 310CDF6221C8C434009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 310CDEA021C8C1D8009665CF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313A536421B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 310CDE5221C8C133009665CF /* ParticlesCommonModels.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParticlesCommonModels.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 310CDE5521C8C133009665CF /* ParticlesCommonModels.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ParticlesCommonModels.h; sourceTree = ""; }; + 310CDE5621C8C133009665CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 310CDE5B21C8C133009665CF /* ParticlesCommonModelsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ParticlesCommonModelsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 310CDE6021C8C133009665CF /* ParticlesCommonModelsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesCommonModelsTests.swift; sourceTree = ""; }; + 310CDE6221C8C133009665CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 310CDE8821C8C1D8009665CF /* ParticlesKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParticlesKit.xcodeproj; path = ../ParticlesKit/ParticlesKit.xcodeproj; sourceTree = ""; }; + 310CDEA021C8C1D8009665CF /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 310CDF1A21C8C3DC009665CF /* ParticlesCommonModels.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParticlesCommonModels.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 310CDF1C21C8C3DC009665CF /* ParticlesCommonModels.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ParticlesCommonModels.h; sourceTree = ""; }; + 310CDF1D21C8C3DC009665CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 310CDF2721C8C3F0009665CF /* ParticlesCommonModels.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParticlesCommonModels.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 310CDF2921C8C3F0009665CF /* ParticlesCommonModels.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ParticlesCommonModels.h; sourceTree = ""; }; + 310CDF2A21C8C3F0009665CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 310CDF2F21C8C3F0009665CF /* ParticlesCommonModelsAppleTVTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ParticlesCommonModelsAppleTVTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 310CDF3421C8C3F1009665CF /* ParticlesCommonModelsAppleTVTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesCommonModelsAppleTVTests.swift; sourceTree = ""; }; + 310CDF3621C8C3F1009665CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 314B606C23DCCE4500139EB3 /* ExternalAppInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExternalAppInteractor.swift; sourceTree = ""; }; + 314B606F23DCCE4500139EB3 /* SimpleAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleAction.swift; sourceTree = ""; }; + 314B607123DCCE4500139EB3 /* DislikedListInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DislikedListInteractor.swift; sourceTree = ""; }; + 314B607223DCCE4500139EB3 /* LikedListInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LikedListInteractor.swift; sourceTree = ""; }; + 314B607323DCCE4500139EB3 /* LikedKeysInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LikedKeysInteractor.swift; sourceTree = ""; }; + 314B607523DCCE4500139EB3 /* BrowsingListInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowsingListInteractor.swift; sourceTree = ""; }; + 314B607623DCCE4500139EB3 /* LocalSavedSearchCacheInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalSavedSearchCacheInteractor.swift; sourceTree = ""; }; + 314B607723DCCE4500139EB3 /* FilteredListInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilteredListInteractor.swift; sourceTree = ""; }; + 314B607923DCCE4500139EB3 /* Doer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Doer.swift; sourceTree = ""; }; + 314B607A23DCCE4500139EB3 /* LikeDoer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LikeDoer.swift; sourceTree = ""; }; + 316FD74726B324E200F74AAD /* SelfFilteredListInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfFilteredListInteractor.swift; sourceTree = ""; }; + 31EAFDF725C6352300412F11 /* NavigationModelProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModelProtocols.swift; sourceTree = ""; }; + 31EAFE0825C635F900412F11 /* NavigationObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationObject.swift; sourceTree = ""; }; + 31EAFE1825C638BD00412F11 /* NavigationInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationInteractor.swift; sourceTree = ""; }; + 56FA4D1740881427D12DA216 /* Pods-iOS-ParticlesCommonModels.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-ParticlesCommonModels.debug.xcconfig"; path = "Target Support Files/Pods-iOS-ParticlesCommonModels/Pods-iOS-ParticlesCommonModels.debug.xcconfig"; sourceTree = ""; }; + 72A6BED7E4F7A2C1BADA516E /* Pods-iOS-ParticlesCommonModels.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-ParticlesCommonModels.release.xcconfig"; path = "Target Support Files/Pods-iOS-ParticlesCommonModels/Pods-iOS-ParticlesCommonModels.release.xcconfig"; sourceTree = ""; }; + 9F37F47726893B8A244DAC3F /* Pods-iOS-ParticlesCommonModelsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-ParticlesCommonModelsTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-ParticlesCommonModelsTests/Pods-iOS-ParticlesCommonModelsTests.release.xcconfig"; sourceTree = ""; }; + AA6E48B566D1AE00FAF8D22D /* Pods_iOS_ParticlesCommonModels.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_ParticlesCommonModels.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F0B93456290A80728DE3A3E2 /* Pods_iOS_ParticlesCommonModelsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_ParticlesCommonModelsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FF3247AC7DFD64B9A846E515 /* Pods-iOS-ParticlesCommonModelsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-ParticlesCommonModelsTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-ParticlesCommonModelsTests/Pods-iOS-ParticlesCommonModelsTests.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 310CDE4F21C8C133009665CF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 310CDEBE21C8C206009665CF /* ParticlesKit.framework in Frameworks */, + 310CDEBD21C8C1FC009665CF /* Utilities.framework in Frameworks */, + 46E7CFF8DC2DA9F219DA0E91 /* Pods_iOS_ParticlesCommonModels.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDE5821C8C133009665CF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 310CDE5C21C8C133009665CF /* ParticlesCommonModels.framework in Frameworks */, + 64FBF0ADDBA6E433D9DB8596 /* Pods_iOS_ParticlesCommonModelsTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDF1721C8C3DC009665CF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 310CDF5F21C8C429009665CF /* ParticlesKit.framework in Frameworks */, + 310CDF5E21C8C422009665CF /* Utilities.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDF2421C8C3F0009665CF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 310CDF6521C8C447009665CF /* ParticlesKit.framework in Frameworks */, + 310CDF6421C8C43E009665CF /* Utilities.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDF2C21C8C3F0009665CF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 310CDF3021C8C3F1009665CF /* ParticlesCommonModels.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02684EFE28BD415B0007CEFF /* Dependencies */ = { + isa = PBXGroup; + children = ( + 310CDE8821C8C1D8009665CF /* ParticlesKit.xcodeproj */, + 310CDEA021C8C1D8009665CF /* Utilities.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 0D8C55EA9512FD156ADCC74F /* Frameworks */ = { + isa = PBXGroup; + children = ( + AA6E48B566D1AE00FAF8D22D /* Pods_iOS_ParticlesCommonModels.framework */, + F0B93456290A80728DE3A3E2 /* Pods_iOS_ParticlesCommonModelsTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 310CDE4821C8C132009665CF = { + isa = PBXGroup; + children = ( + 02684EFE28BD415B0007CEFF /* Dependencies */, + 310CDE5421C8C133009665CF /* ParticlesCommonModels */, + 310CDF2821C8C3F0009665CF /* ParticlesCommonModelsAppleTV */, + 310CDF3321C8C3F1009665CF /* ParticlesCommonModelsAppleTVTests */, + 310CDF1B21C8C3DC009665CF /* ParticlesCommonModelsAppleWatch */, + 310CDE5F21C8C133009665CF /* ParticlesCommonModelsTests */, + 310CDE5321C8C133009665CF /* Products */, + 365B28A85094B5DACA508C93 /* Pods */, + 0D8C55EA9512FD156ADCC74F /* Frameworks */, + ); + sourceTree = ""; + }; + 310CDE5321C8C133009665CF /* Products */ = { + isa = PBXGroup; + children = ( + 310CDE5221C8C133009665CF /* ParticlesCommonModels.framework */, + 310CDE5B21C8C133009665CF /* ParticlesCommonModelsTests.xctest */, + 310CDF1A21C8C3DC009665CF /* ParticlesCommonModels.framework */, + 310CDF2721C8C3F0009665CF /* ParticlesCommonModels.framework */, + 310CDF2F21C8C3F0009665CF /* ParticlesCommonModelsAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 310CDE5421C8C133009665CF /* ParticlesCommonModels */ = { + isa = PBXGroup; + children = ( + 314B606D23DCCE4500139EB3 /* _Action */, + 314B606A23DCCE4500139EB3 /* _App */, + 314B607423DCCE4500139EB3 /* _Browse */, + 31EAFDF225C634FC00412F11 /* _NavigationObjects */, + 314B607823DCCE4500139EB3 /* _Doer */, + 314B607023DCCE4500139EB3 /* _Like */, + 310CDE5621C8C133009665CF /* Info.plist */, + 310CDE5521C8C133009665CF /* ParticlesCommonModels.h */, + ); + path = ParticlesCommonModels; + sourceTree = ""; + }; + 310CDE5F21C8C133009665CF /* ParticlesCommonModelsTests */ = { + isa = PBXGroup; + children = ( + 310CDE6021C8C133009665CF /* ParticlesCommonModelsTests.swift */, + 310CDE6221C8C133009665CF /* Info.plist */, + ); + path = ParticlesCommonModelsTests; + sourceTree = ""; + }; + 310CDE8921C8C1D8009665CF /* Products */ = { + isa = PBXGroup; + children = ( + 310CDE9321C8C1D8009665CF /* ParticlesKit.framework */, + 310CDE9521C8C1D8009665CF /* ParticlesKit.framework */, + 310CDE9721C8C1D8009665CF /* ParticlesKit.framework */, + 310CDE9B21C8C1D8009665CF /* ParticlesKitTests.xctest */, + 310CDE9D21C8C1D8009665CF /* ParticlesKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 310CDEA121C8C1D8009665CF /* Products */ = { + isa = PBXGroup; + children = ( + 310CDEAB21C8C1D8009665CF /* Utilities.framework */, + 310CDEAD21C8C1D8009665CF /* Utilities.framework */, + 310CDEAF21C8C1D8009665CF /* Utilities.framework */, + 310CDEB321C8C1D8009665CF /* UtilitiesTests.xctest */, + 310CDEB521C8C1D8009665CF /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 310CDF1B21C8C3DC009665CF /* ParticlesCommonModelsAppleWatch */ = { + isa = PBXGroup; + children = ( + 310CDF1C21C8C3DC009665CF /* ParticlesCommonModels.h */, + 310CDF1D21C8C3DC009665CF /* Info.plist */, + ); + path = ParticlesCommonModelsAppleWatch; + sourceTree = ""; + }; + 310CDF2821C8C3F0009665CF /* ParticlesCommonModelsAppleTV */ = { + isa = PBXGroup; + children = ( + 310CDF2921C8C3F0009665CF /* ParticlesCommonModels.h */, + 310CDF2A21C8C3F0009665CF /* Info.plist */, + ); + path = ParticlesCommonModelsAppleTV; + sourceTree = ""; + }; + 310CDF3321C8C3F1009665CF /* ParticlesCommonModelsAppleTVTests */ = { + isa = PBXGroup; + children = ( + 310CDF3421C8C3F1009665CF /* ParticlesCommonModelsAppleTVTests.swift */, + 310CDF3621C8C3F1009665CF /* Info.plist */, + ); + path = ParticlesCommonModelsAppleTVTests; + sourceTree = ""; + }; + 314B606A23DCCE4500139EB3 /* _App */ = { + isa = PBXGroup; + children = ( + 314B606C23DCCE4500139EB3 /* ExternalAppInteractor.swift */, + ); + path = _App; + sourceTree = ""; + }; + 314B606D23DCCE4500139EB3 /* _Action */ = { + isa = PBXGroup; + children = ( + 314B606F23DCCE4500139EB3 /* SimpleAction.swift */, + ); + path = _Action; + sourceTree = ""; + }; + 314B607023DCCE4500139EB3 /* _Like */ = { + isa = PBXGroup; + children = ( + 314B607123DCCE4500139EB3 /* DislikedListInteractor.swift */, + 314B607223DCCE4500139EB3 /* LikedListInteractor.swift */, + 314B607323DCCE4500139EB3 /* LikedKeysInteractor.swift */, + ); + path = _Like; + sourceTree = ""; + }; + 314B607423DCCE4500139EB3 /* _Browse */ = { + isa = PBXGroup; + children = ( + 314B607523DCCE4500139EB3 /* BrowsingListInteractor.swift */, + 314B607723DCCE4500139EB3 /* FilteredListInteractor.swift */, + 314B607623DCCE4500139EB3 /* LocalSavedSearchCacheInteractor.swift */, + 316FD74726B324E200F74AAD /* SelfFilteredListInteractor.swift */, + ); + path = _Browse; + sourceTree = ""; + }; + 314B607823DCCE4500139EB3 /* _Doer */ = { + isa = PBXGroup; + children = ( + 314B607923DCCE4500139EB3 /* Doer.swift */, + 314B607A23DCCE4500139EB3 /* LikeDoer.swift */, + ); + path = _Doer; + sourceTree = ""; + }; + 31EAFDF225C634FC00412F11 /* _NavigationObjects */ = { + isa = PBXGroup; + children = ( + 31EAFDF725C6352300412F11 /* NavigationModelProtocols.swift */, + 31EAFE0825C635F900412F11 /* NavigationObject.swift */, + 31EAFE1825C638BD00412F11 /* NavigationInteractor.swift */, + ); + path = _NavigationObjects; + sourceTree = ""; + }; + 365B28A85094B5DACA508C93 /* Pods */ = { + isa = PBXGroup; + children = ( + 56FA4D1740881427D12DA216 /* Pods-iOS-ParticlesCommonModels.debug.xcconfig */, + 72A6BED7E4F7A2C1BADA516E /* Pods-iOS-ParticlesCommonModels.release.xcconfig */, + FF3247AC7DFD64B9A846E515 /* Pods-iOS-ParticlesCommonModelsTests.debug.xcconfig */, + 9F37F47726893B8A244DAC3F /* Pods-iOS-ParticlesCommonModelsTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 310CDE4D21C8C133009665CF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 310CDE6321C8C133009665CF /* ParticlesCommonModels.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDF1521C8C3DC009665CF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 310CDF1E21C8C3DC009665CF /* ParticlesCommonModels.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDF2221C8C3F0009665CF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 310CDF3721C8C3F1009665CF /* ParticlesCommonModels.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 310CDE5121C8C133009665CF /* ParticlesCommonModels */ = { + isa = PBXNativeTarget; + buildConfigurationList = 310CDE6621C8C133009665CF /* Build configuration list for PBXNativeTarget "ParticlesCommonModels" */; + buildPhases = ( + 4823A7A17C6B5EDB884164D5 /* [CP] Check Pods Manifest.lock */, + 310CDE4D21C8C133009665CF /* Headers */, + 310CDE4E21C8C133009665CF /* Sources */, + 310CDE4F21C8C133009665CF /* Frameworks */, + 310CDE5021C8C133009665CF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 310CDEB921C8C1E4009665CF /* PBXTargetDependency */, + 310CDEBB21C8C1E4009665CF /* PBXTargetDependency */, + ); + name = ParticlesCommonModels; + productName = ParticlesCommonModels; + productReference = 310CDE5221C8C133009665CF /* ParticlesCommonModels.framework */; + productType = "com.apple.product-type.framework"; + }; + 310CDE5A21C8C133009665CF /* ParticlesCommonModelsTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 310CDE6921C8C133009665CF /* Build configuration list for PBXNativeTarget "ParticlesCommonModelsTests" */; + buildPhases = ( + 94DB2B52A5710D26939D9879 /* [CP] Check Pods Manifest.lock */, + 310CDE5721C8C133009665CF /* Sources */, + 310CDE5821C8C133009665CF /* Frameworks */, + 310CDE5921C8C133009665CF /* Resources */, + DA7E18964776CF156A747275 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 310CDE5E21C8C133009665CF /* PBXTargetDependency */, + ); + name = ParticlesCommonModelsTests; + productName = ParticlesCommonModelsTests; + productReference = 310CDE5B21C8C133009665CF /* ParticlesCommonModelsTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 310CDF1921C8C3DC009665CF /* ParticlesCommonModelsAppleWatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 310CDF1F21C8C3DC009665CF /* Build configuration list for PBXNativeTarget "ParticlesCommonModelsAppleWatch" */; + buildPhases = ( + 310CDF1521C8C3DC009665CF /* Headers */, + 310CDF1621C8C3DC009665CF /* Sources */, + 310CDF1721C8C3DC009665CF /* Frameworks */, + 310CDF1821C8C3DC009665CF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 310CDF5B21C8C412009665CF /* PBXTargetDependency */, + 310CDF5D21C8C412009665CF /* PBXTargetDependency */, + ); + name = ParticlesCommonModelsAppleWatch; + productName = ParticlesCommonModelsAppleWatch; + productReference = 310CDF1A21C8C3DC009665CF /* ParticlesCommonModels.framework */; + productType = "com.apple.product-type.framework"; + }; + 310CDF2621C8C3F0009665CF /* ParticlesCommonModelsAppleTV */ = { + isa = PBXNativeTarget; + buildConfigurationList = 310CDF3821C8C3F1009665CF /* Build configuration list for PBXNativeTarget "ParticlesCommonModelsAppleTV" */; + buildPhases = ( + 310CDF2221C8C3F0009665CF /* Headers */, + 310CDF2321C8C3F0009665CF /* Sources */, + 310CDF2421C8C3F0009665CF /* Frameworks */, + 310CDF2521C8C3F0009665CF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 310CDF6121C8C434009665CF /* PBXTargetDependency */, + 310CDF6321C8C434009665CF /* PBXTargetDependency */, + ); + name = ParticlesCommonModelsAppleTV; + productName = ParticlesCommonModelsAppleTV; + productReference = 310CDF2721C8C3F0009665CF /* ParticlesCommonModels.framework */; + productType = "com.apple.product-type.framework"; + }; + 310CDF2E21C8C3F0009665CF /* ParticlesCommonModelsAppleTVTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 310CDF3B21C8C3F1009665CF /* Build configuration list for PBXNativeTarget "ParticlesCommonModelsAppleTVTests" */; + buildPhases = ( + 310CDF2B21C8C3F0009665CF /* Sources */, + 310CDF2C21C8C3F0009665CF /* Frameworks */, + 310CDF2D21C8C3F0009665CF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 310CDF3221C8C3F1009665CF /* PBXTargetDependency */, + ); + name = ParticlesCommonModelsAppleTVTests; + productName = ParticlesCommonModelsAppleTVTests; + productReference = 310CDF2F21C8C3F0009665CF /* ParticlesCommonModelsAppleTVTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 310CDE4921C8C132009665CF /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "dYdX Trading Inc"; + TargetAttributes = { + 310CDE5121C8C133009665CF = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1020; + }; + 310CDE5A21C8C133009665CF = { + CreatedOnToolsVersion = 10.1; + }; + 310CDF1921C8C3DC009665CF = { + CreatedOnToolsVersion = 10.1; + }; + 310CDF2621C8C3F0009665CF = { + CreatedOnToolsVersion = 10.1; + }; + 310CDF2E21C8C3F0009665CF = { + CreatedOnToolsVersion = 10.1; + }; + }; + }; + buildConfigurationList = 310CDE4C21C8C132009665CF /* Build configuration list for PBXProject "ParticlesCommonModels" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 310CDE4821C8C132009665CF; + productRefGroup = 310CDE5321C8C133009665CF /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 310CDE8921C8C1D8009665CF /* Products */; + ProjectRef = 310CDE8821C8C1D8009665CF /* ParticlesKit.xcodeproj */; + }, + { + ProductGroup = 310CDEA121C8C1D8009665CF /* Products */; + ProjectRef = 310CDEA021C8C1D8009665CF /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 310CDE5121C8C133009665CF /* ParticlesCommonModels */, + 310CDF1921C8C3DC009665CF /* ParticlesCommonModelsAppleWatch */, + 310CDF2621C8C3F0009665CF /* ParticlesCommonModelsAppleTV */, + 310CDE5A21C8C133009665CF /* ParticlesCommonModelsTests */, + 310CDF2E21C8C3F0009665CF /* ParticlesCommonModelsAppleTVTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 310CDE9321C8C1D8009665CF /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 310CDE9221C8C1D8009665CF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 310CDE9521C8C1D8009665CF /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 310CDE9421C8C1D8009665CF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 310CDE9721C8C1D8009665CF /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 310CDE9621C8C1D8009665CF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 310CDE9B21C8C1D8009665CF /* ParticlesKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitTests.xctest; + remoteRef = 310CDE9A21C8C1D8009665CF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 310CDE9D21C8C1D8009665CF /* ParticlesKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitAppleTVTests.xctest; + remoteRef = 310CDE9C21C8C1D8009665CF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 310CDEAB21C8C1D8009665CF /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 310CDEAA21C8C1D8009665CF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 310CDEAD21C8C1D8009665CF /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 310CDEAC21C8C1D8009665CF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 310CDEAF21C8C1D8009665CF /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 310CDEAE21C8C1D8009665CF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 310CDEB321C8C1D8009665CF /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 310CDEB221C8C1D8009665CF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 310CDEB521C8C1D8009665CF /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 310CDEB421C8C1D8009665CF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 310CDE5021C8C133009665CF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDE5921C8C133009665CF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDF1821C8C3DC009665CF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDF2521C8C3F0009665CF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDF2D21C8C3F0009665CF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 4823A7A17C6B5EDB884164D5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-ParticlesCommonModels-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 94DB2B52A5710D26939D9879 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-ParticlesCommonModelsTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + DA7E18964776CF156A747275 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-ParticlesCommonModelsTests/Pods-iOS-ParticlesCommonModelsTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-ParticlesCommonModelsTests/Pods-iOS-ParticlesCommonModelsTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-ParticlesCommonModelsTests/Pods-iOS-ParticlesCommonModelsTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 310CDE4E21C8C133009665CF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B608F23DCCE4600139EB3 /* LikedListInteractor.swift in Sources */, + 31EAFE1925C638BD00412F11 /* NavigationInteractor.swift in Sources */, + 316FD74826B324E200F74AAD /* SelfFilteredListInteractor.swift in Sources */, + 314B609F23DCCE4600139EB3 /* FilteredListInteractor.swift in Sources */, + 314B60A323DCCE4600139EB3 /* Doer.swift in Sources */, + 314B609723DCCE4600139EB3 /* BrowsingListInteractor.swift in Sources */, + 314B60A723DCCE4600139EB3 /* LikeDoer.swift in Sources */, + 314B608723DCCE4600139EB3 /* SimpleAction.swift in Sources */, + 314B608B23DCCE4600139EB3 /* DislikedListInteractor.swift in Sources */, + 314B609B23DCCE4600139EB3 /* LocalSavedSearchCacheInteractor.swift in Sources */, + 31EAFE0925C635F900412F11 /* NavigationObject.swift in Sources */, + 314B609323DCCE4600139EB3 /* LikedKeysInteractor.swift in Sources */, + 31EAFDF825C6352300412F11 /* NavigationModelProtocols.swift in Sources */, + 314B607F23DCCE4600139EB3 /* ExternalAppInteractor.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDE5721C8C133009665CF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 310CDE6121C8C133009665CF /* ParticlesCommonModelsTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDF1621C8C3DC009665CF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B609023DCCE4600139EB3 /* LikedListInteractor.swift in Sources */, + 31EAFE1A25C638BD00412F11 /* NavigationInteractor.swift in Sources */, + 314B60A023DCCE4600139EB3 /* FilteredListInteractor.swift in Sources */, + 314B60A423DCCE4600139EB3 /* Doer.swift in Sources */, + 31EAFE1125C635FE00412F11 /* NavigationModelProtocols.swift in Sources */, + 314B609823DCCE4600139EB3 /* BrowsingListInteractor.swift in Sources */, + 314B60A823DCCE4600139EB3 /* LikeDoer.swift in Sources */, + 314B608823DCCE4600139EB3 /* SimpleAction.swift in Sources */, + 314B608C23DCCE4600139EB3 /* DislikedListInteractor.swift in Sources */, + 314B609C23DCCE4600139EB3 /* LocalSavedSearchCacheInteractor.swift in Sources */, + 314B609423DCCE4600139EB3 /* LikedKeysInteractor.swift in Sources */, + 31EAFE0A25C635F900412F11 /* NavigationObject.swift in Sources */, + 314B608023DCCE4600139EB3 /* ExternalAppInteractor.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDF2321C8C3F0009665CF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B609123DCCE4600139EB3 /* LikedListInteractor.swift in Sources */, + 31EAFE1B25C638BD00412F11 /* NavigationInteractor.swift in Sources */, + 314B60A123DCCE4600139EB3 /* FilteredListInteractor.swift in Sources */, + 314B60A523DCCE4600139EB3 /* Doer.swift in Sources */, + 31EAFE1225C635FF00412F11 /* NavigationModelProtocols.swift in Sources */, + 314B609923DCCE4600139EB3 /* BrowsingListInteractor.swift in Sources */, + 314B60A923DCCE4600139EB3 /* LikeDoer.swift in Sources */, + 314B608923DCCE4600139EB3 /* SimpleAction.swift in Sources */, + 314B608D23DCCE4600139EB3 /* DislikedListInteractor.swift in Sources */, + 314B609D23DCCE4600139EB3 /* LocalSavedSearchCacheInteractor.swift in Sources */, + 314B609523DCCE4600139EB3 /* LikedKeysInteractor.swift in Sources */, + 31EAFE0B25C635F900412F11 /* NavigationObject.swift in Sources */, + 314B608123DCCE4600139EB3 /* ExternalAppInteractor.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310CDF2B21C8C3F0009665CF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 310CDF3521C8C3F1009665CF /* ParticlesCommonModelsAppleTVTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 310CDE5E21C8C133009665CF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 310CDE5121C8C133009665CF /* ParticlesCommonModels */; + targetProxy = 310CDE5D21C8C133009665CF /* PBXContainerItemProxy */; + }; + 310CDEB921C8C1E4009665CF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKit; + targetProxy = 310CDEB821C8C1E4009665CF /* PBXContainerItemProxy */; + }; + 310CDEBB21C8C1E4009665CF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 310CDEBA21C8C1E4009665CF /* PBXContainerItemProxy */; + }; + 310CDF3221C8C3F1009665CF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 310CDF2621C8C3F0009665CF /* ParticlesCommonModelsAppleTV */; + targetProxy = 310CDF3121C8C3F1009665CF /* PBXContainerItemProxy */; + }; + 310CDF5B21C8C412009665CF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKitAppleWatch; + targetProxy = 310CDF5A21C8C412009665CF /* PBXContainerItemProxy */; + }; + 310CDF5D21C8C412009665CF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleWatch; + targetProxy = 310CDF5C21C8C412009665CF /* PBXContainerItemProxy */; + }; + 310CDF6121C8C434009665CF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKitAppleTV; + targetProxy = 310CDF6021C8C434009665CF /* PBXContainerItemProxy */; + }; + 310CDF6321C8C434009665CF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleTV; + targetProxy = 310CDF6221C8C434009665CF /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 310CDE6421C8C133009665CF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 310CDE6521C8C133009665CF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 310CDE6721C8C133009665CF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 56FA4D1740881427D12DA216 /* Pods-iOS-ParticlesCommonModels.debug.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ParticlesCommonModels/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.ParticlesCommonModels; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 310CDE6821C8C133009665CF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 72A6BED7E4F7A2C1BADA516E /* Pods-iOS-ParticlesCommonModels.release.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ParticlesCommonModels/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.ParticlesCommonModels; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 310CDE6A21C8C133009665CF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FF3247AC7DFD64B9A846E515 /* Pods-iOS-ParticlesCommonModelsTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = ParticlesCommonModelsTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesCommonModelsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 310CDE6B21C8C133009665CF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9F37F47726893B8A244DAC3F /* Pods-iOS-ParticlesCommonModelsTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = ParticlesCommonModelsTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesCommonModelsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 310CDF2021C8C3DC009665CF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ParticlesCommonModelsAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesCommonModelsAppleWatch; + PRODUCT_NAME = ParticlesCommonModels; + SDKROOT = watchos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Debug; + }; + 310CDF2121C8C3DC009665CF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ParticlesCommonModelsAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesCommonModelsAppleWatch; + PRODUCT_NAME = ParticlesCommonModels; + SDKROOT = watchos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Release; + }; + 310CDF3921C8C3F1009665CF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ParticlesCommonModelsAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesCommonModelsAppleTV; + PRODUCT_NAME = ParticlesCommonModels; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 310CDF3A21C8C3F1009665CF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ParticlesCommonModelsAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesCommonModelsAppleTV; + PRODUCT_NAME = ParticlesCommonModels; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 310CDF3C21C8C3F1009665CF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = ParticlesCommonModelsAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesCommonModelsAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 310CDF3D21C8C3F1009665CF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = ParticlesCommonModelsAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesCommonModelsAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 310CDE4C21C8C132009665CF /* Build configuration list for PBXProject "ParticlesCommonModels" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 310CDE6421C8C133009665CF /* Debug */, + 310CDE6521C8C133009665CF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 310CDE6621C8C133009665CF /* Build configuration list for PBXNativeTarget "ParticlesCommonModels" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 310CDE6721C8C133009665CF /* Debug */, + 310CDE6821C8C133009665CF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 310CDE6921C8C133009665CF /* Build configuration list for PBXNativeTarget "ParticlesCommonModelsTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 310CDE6A21C8C133009665CF /* Debug */, + 310CDE6B21C8C133009665CF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 310CDF1F21C8C3DC009665CF /* Build configuration list for PBXNativeTarget "ParticlesCommonModelsAppleWatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 310CDF2021C8C3DC009665CF /* Debug */, + 310CDF2121C8C3DC009665CF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 310CDF3821C8C3F1009665CF /* Build configuration list for PBXNativeTarget "ParticlesCommonModelsAppleTV" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 310CDF3921C8C3F1009665CF /* Debug */, + 310CDF3A21C8C3F1009665CF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 310CDF3B21C8C3F1009665CF /* Build configuration list for PBXNativeTarget "ParticlesCommonModelsAppleTVTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 310CDF3C21C8C3F1009665CF /* Debug */, + 310CDF3D21C8C3F1009665CF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 310CDE4921C8C132009665CF /* Project object */; +} diff --git a/ParticlesCommonModels/ParticlesCommonModels.xcodeproj/xcshareddata/xcschemes/ParticlesCommonModels.xcscheme b/ParticlesCommonModels/ParticlesCommonModels.xcodeproj/xcshareddata/xcschemes/ParticlesCommonModels.xcscheme new file mode 100644 index 000000000..44fb308f1 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels.xcodeproj/xcshareddata/xcschemes/ParticlesCommonModels.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ParticlesCommonModels/ParticlesCommonModels/DataManager.swift b/ParticlesCommonModels/ParticlesCommonModels/DataManager.swift new file mode 100644 index 000000000..afe89ec01 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/DataManager.swift @@ -0,0 +1,108 @@ +// +// ListManager.swift +// InteractorLib +// +// Created by Qiang Huang on 11/10/18. +// Copyright © 2018 Qiang Huang. All rights reserved. +// + +import KVOController +import Utilities + +@objc open class JsonCache: NSObject, InteractorProtocol, LocalCacheProtocol { + open var key: String? + open var defaultJson: String? + open var loader: LoaderProtocol? + public var debouncer: Debouncer = Debouncer() + @objc open dynamic var entity: (NSObject & ModelObjectProtocol)? { + didSet { + if entity !== oldValue { + let keyPath = #keyPath(DictionaryEntity.data) + kvoController.unobserve(oldValue, keyPath: keyPath) + kvoController.observe(dictionaryEntity, keyPath: keyPath, options: [.initial]) { [weak self] _, _, _ in + self?.save() + } + } + } + } + + private var dictionaryEntity: DictionaryEntity? { + return entity as? DictionaryEntity + } + + public var persistTag: String? { + if let key = key { + return "\(String(describing: type(of: self))).persist.\(key)" + } + return nil + } + + public var persistDataFile: String? { + if let persistTag = persistTag { + return Directory.document?.stringByAppendingPathComponent(path: "\(persistTag).data.json") + } + return nil + } + + public override init() { + super.init() + } + + public init(key: String? = nil, default defaultJson: String? = nil) { + self.key = key + self.defaultJson = defaultJson + super.init() + load() + } + + open func load() { + readData() + loader = createLoader() + } + + open func save() { + writeData() + } + + open func createLoader() -> LoaderProtocol? { + return nil + } + + open func load(data: Any?) { + entity = data as? (NSObject & ModelObjectProtocol) + } + + open func readData() { + if let persistDataFile = self.persistDataFile, let dictionary = JsonLoader.load(file: persistDataFile) as? [String: Any] { + entity = entity(from: dictionary) + } else { + entity = entity(from: nil) + } + } + + open func writeData() { + if let entity = self.entity, let persistDataFile = self.persistDataFile, let persist = (entity as? PersistableEntity)?.persist { + JsonWriter.write(persist, to: persistDataFile) + } + } + + public func entity(from data: [String: Any]?) -> (NSObject & ModelObjectProtocol)? { + let entity = self.entity ?? createEntity() + if let data = data { + (entity as? ParsingProtocol)?.parseDictionary?(data) + } else { + readDefault(into: entity) + } + return entity + } + + open func createEntity() -> (NSObject & ModelObjectProtocol) { + return DictionaryEntity() + } + + public func readDefault(into entity: (NSObject & ModelObjectProtocol)) { + if let data = JsonLoader.load(bundle: Bundle.ui(), fileName: defaultJson) as? [String: Any] { + (entity as? ParsingProtocol)?.parseDictionary?(data) + } + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/Info.plist b/ParticlesCommonModels/ParticlesCommonModels/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/ParticlesCommonModels/ParticlesCommonModels/ParticlesCommonModels.h b/ParticlesCommonModels/ParticlesCommonModels/ParticlesCommonModels.h new file mode 100644 index 000000000..9552b680b --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/ParticlesCommonModels.h @@ -0,0 +1,19 @@ +// +// ParticlesCommonModels.h +// ParticlesCommonModels +// +// Created by Qiang Huang on 12/17/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for ParticlesCommonModels. +FOUNDATION_EXPORT double ParticlesCommonModelsVersionNumber; + +//! Project version string for ParticlesCommonModels. +FOUNDATION_EXPORT const unsigned char ParticlesCommonModelsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/ParticlesCommonModels/ParticlesCommonModels/_Action/SimpleAction.swift b/ParticlesCommonModels/ParticlesCommonModels/_Action/SimpleAction.swift new file mode 100644 index 000000000..dfecb45d6 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_Action/SimpleAction.swift @@ -0,0 +1,42 @@ +// +// ActionInteractor.swift +// ParticlesCommonModels +// +// Created by Qiang Huang on 2/10/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit + +open class SimpleAction: DictionaryEntity, ActionProtocol { + open var title: String? { + return parser.asString(data?["title"]) + } + + open var subtitle: String? { + return parser.asString(data?["subtitle"]) + } + + open var detail: String? { + return parser.asString(data?["detail"]) + } + + open var image: String? { + return parser.asString(data?["image"]) + } + + open var routing: RoutingRequest? { + if let routing = parser.asDictionary(data?["routing"]), let path = parser.asString(routing["path"]) { + return RoutingRequest(path: path, params: parser.asDictionary(routing["params"])) + } + return nil + } + + open var detailRouting: RoutingRequest? { + if let routing = parser.asDictionary(data?["detailRouting"]), let path = parser.asString(routing["path"]) { + return RoutingRequest(path: path, params: parser.asDictionary(routing["params"])) + } + return nil + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_App/ExternalAppInteractor.swift b/ParticlesCommonModels/ParticlesCommonModels/_App/ExternalAppInteractor.swift new file mode 100644 index 000000000..452345d2f --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_App/ExternalAppInteractor.swift @@ -0,0 +1,24 @@ +// +// ExternalAppInteractor.swift +// ParticlesCommonModels +// +// Created by Qiang Huang on 11/1/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +public class ExternalAppInteractor: NSObject, InteractorProtocol { + public var entity: ModelObjectProtocol? + + public var url: String? + public var appId: String? + + open func open() { + if let urlString = url, let url = URL(string: urlString) { + if URLHandler.shared?.canOpenURL(url) ?? false { + } + } + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_Browse/BrowsingListInteractor.swift b/ParticlesCommonModels/ParticlesCommonModels/_Browse/BrowsingListInteractor.swift new file mode 100644 index 000000000..4b8df8be4 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_Browse/BrowsingListInteractor.swift @@ -0,0 +1,123 @@ +// +// BrowsingListInteractor.swift +// InteractorLib +// +// Created by Qiang Huang on 11/10/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +@objc open class BrowsingListInteractor: FilteredListInteractor { + @objc open dynamic var dataCache: DataPoolInteractor? { + didSet { + didSetDataCache(oldValue: oldValue) + } + } + + @objc open private(set) dynamic var isLoading: Bool = false { + didSet { + didSetIsLoading(oldValue: oldValue) + } + } + + @objc open dynamic var isReady: Bool = false { + didSet { + didSetIsReady(oldValue: oldValue) + } + } + + @objc override open dynamic var data: [ModelObjectProtocol]? { + if let values = map?.transformed?.values { + return Array(values) + } else { + return sequential?.sequenceTransformed + } + } + + @objc open private(set) dynamic var map: DataPoolInteractor? { + didSet { + didSetMap(oldValue: oldValue) + } + } + + @objc open private(set) dynamic var sequential: DataPoolInteractor? { + didSet { + didSetSequential(oldValue: oldValue) + } + } + + open func didSetDataCache(oldValue: DataPoolInteractor?) { + if dataCache !== oldValue { + if dataCache?.sequential ?? false { + map = nil + sequential = dataCache + } else { + sequential = nil + map = dataCache + } + } + } + + open func didSetMap(oldValue: DataPoolInteractor?) { + changeObservation(from: oldValue, to: map, keyPath: #keyPath(DataPoolInteractor.data)) { [weak self] _, _, _, _ in + self?.dataChanged() + } + changeObservation(from: oldValue, to: map, keyPath: #keyPath(DataPoolInteractor.isLoading)) { [weak self] _, _, _, _ in + self?.isLoading = self?.map?.isLoading ?? false + } + } + + open func didSetSequential(oldValue: DataPoolInteractor?) { + changeObservation(from: oldValue, to: sequential, keyPath: #keyPath(DataPoolInteractor.sequence)) { [weak self] _, _, _, _ in + self?.sequentialChanged() + } + changeObservation(from: oldValue, to: sequential, keyPath: #keyPath(DataPoolInteractor.isLoading)) { [weak self] _, _, _, _ in + self?.isLoading = self?.sequential?.isLoading ?? false + } + } + + open func didSetIsLoading(oldValue: Bool) { + updateIsReady() + } + + open func didSetIsReady(oldValue: Bool) { + } + + open func updateIsReady() { + if isLoading { + isReady = (list?.count ?? 0) > 0 + } else { + isReady = true + } + } + + override open func sync(_ list: [ModelObjectProtocol]?) { + super.sync(list) + updateIsReady() + } + + open func dataChanged() { + filter() + } + + open func sequentialChanged() { + filter() + } + + override open func filter(data: ModelObjectProtocol, key: String, value: Any) -> Bool { + switch key { + default: + return true + } + } + + override open func sort(data: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + if sequential != nil { + return data + } else { + return super.sort(data: data) + } + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_Browse/FilteredListInteractor.swift b/ParticlesCommonModels/ParticlesCommonModels/_Browse/FilteredListInteractor.swift new file mode 100644 index 000000000..c426cc43e --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_Browse/FilteredListInteractor.swift @@ -0,0 +1,245 @@ +// +// FilteredListInteractor.swift +// InteractorLib +// +// Created by Qiang Huang on 10/26/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +// interactor is responsible for loading/transforming/assembling data +@objc open class FilteredListInteractor: ListInteractor, FilteredListInteractorProtocol { + @IBInspectable public var directFiltering: Bool = false + private var debouncer: Debouncer = Debouncer() + + override open var loading: Bool { + didSet { + if loading != oldValue { + status = loading ? LoadingStatus.shared : nil + } + } + } + + private var status: LoadingStatus? { + didSet { + if status !== oldValue { +// oldValue?.minus() +// status?.plus() + } + } + } + + @objc public dynamic var onlyShowLiked: Bool = false { + didSet { + if onlyShowLiked != oldValue { + filter() + } + } + } + + public var liked: LikedObjectsProtocol? { + didSet { + changeObservation(from: oldValue, to: liked, keyPath: #keyPath(LikedKeysInteractor.liked)) { [weak self] _, _, _, _ in + self?.likedChanged() + } + + changeObservation(from: oldValue, to: liked, keyPath: #keyPath(LikedKeysInteractor.disliked)) { [weak self] _, _, _, _ in + self?.filter() + } + } + } + + public var filters: FilterEntity? { + didSet { + changeObservation(from: oldValue, to: filters, keyPath: #keyPath(FilterEntity.data)) { [weak self] _, _, _, animated in + self?.filter() + } + } + } + + open var data: [ModelObjectProtocol]? { + return nil + } + + open var filterText: String? { + didSet { + if filterText != oldValue { + filter() + } + } + } + + open var filterFields: [String]? { + return nil + } + + deinit { + loading = false + } + + open func likedChanged() { + if onlyShowLiked { + filter() + } + } + + open func filter(data: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + let filterText = self.filterText?.lowercased().trim() + return data?.filter({ (item: ModelObjectProtocol) -> Bool in + let key = key(obj: item) + if self.onlyShowLiked, let key = key { + if self.liked?.liked?.contains(key) ?? false { + return self.filter(data: item, text: filterText, filters: self.filters?.data) + } + } else if let key = key, self.liked?.disliked?.contains(key) ?? false { + return false + } else { + return self.filter(data: item, text: filterText, filters: self.filters?.data) + } + return false + }) + } + + private func key(obj: ModelObjectProtocol) -> String? { + if let key = obj.key { + return key + } else { + return nil + } + } + + open func filter() { + if directFiltering { + if let data = prefilter(data: data) { + loading = true + let filtered = filter(data: data) + let grouped = group(data: filtered) + let sorted = sort(data: grouped) + finalize(data: sorted) + loading = false + } else { + finalize(data: nil) + } + } else { + if let data = prefilter(data: data) { + loading = true + var filtered: [ModelObjectProtocol]? + var grouped: [ModelObjectProtocol]? + var sorted: [ModelObjectProtocol]? + debouncer.debounce()?.run(background: { [weak self] in + filtered = self?.filter(data: data) + }, then: { [weak self] in + grouped = self?.group(data: filtered) + }, then: { [weak self] in + sorted = self?.sort(data: grouped) + }, final: { [weak self] in + self?.finalize(data: sorted) + self?.loading = false + }, delay: 0.0) + } else { + debouncer.debounce()?.run({ [weak self] in + self?.finalize(data: nil) + }, delay: 0.0) + } + } + } + + open func prefilter(data: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + return data + } + + open func filter(data: ModelObjectProtocol, text: String?, filters: [String: Any]?) -> Bool { + return filter(data: data, text: text) && filter(data: data, filters: filters) + } + + open func filter(data: ModelObjectProtocol, text: String?) -> Bool { + if let filteredObject = data as? FilteredObjectProtocol { + return filteredObject.filter(text: text) + } else { + if let lowercased = text?.trim()?.lowercased(), let dictionary = (data as? DictionaryEntity)?.force.data { + if let filterFields = filterFields { + if let _ = filterFields.first(where: { (filterField: String) -> Bool in + if let data = data as? NSObject, let string = parser.asString(data.value(forKey: filterField))?.lowercased() { + return string.contains(lowercased) + } + return false + }) { + return true + } + } else { + if let _ = dictionary.values.first(where: { (value: Any) -> Bool in + if let string = (value as? String)?.lowercased() { + return string.contains(lowercased) + } + return false + }) { + return true + } + } + return false + } + return true + } + } + + open func filter(data: ModelObjectProtocol, filters: [String: Any]?) -> Bool { + if let filters = filters { + var ok = true + for (key, value) in filters { + let lowercased = (value as? String)?.lowercased().components(separatedBy: ",") + if !filter(data: data, key: key, value: lowercased ?? value) { + ok = false + break + } + } + return ok + } + return true + } + + open func filter(data: ModelObjectProtocol, key: String, value: Any) -> Bool { + guard let data = data as? (NSObject & ModelObjectProtocol) else { + return false + } + if let strings = value as? [String] { + if let value = parser.asString(data.value(forKey: key)) { + return strings.first(where: { (string) -> Bool in + value.contains(string) + }) != nil + } + } else if let string = value as? String { + if let value = parser.asString(data.value(forKey: key)) { + return value.contains(string) + } + } else if let int = value as? Int { + if let value = parser.asNumber(data.value(forKey: key))?.intValue { + return value == int + } + } else if let float = value as? Float { + if let value = parser.asNumber(data.value(forKey: key))?.floatValue { + return value == float + } + } else if let bool = value as? Bool { + if let value = parser.asNumber(data.value(forKey: key))?.boolValue { + return value == bool + } + } + return false + } + + open func group(data: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + return data + } + + open func sort(data: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + return data?.sorted(by: { (data1, data2) -> Bool in + data1.order?(ascending: data2) ?? true + }) + } + + open func finalize(data: [ModelObjectProtocol]?) { + sync(data) + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_Browse/LocalSavedSearchCacheInteractor.swift b/ParticlesCommonModels/ParticlesCommonModels/_Browse/LocalSavedSearchCacheInteractor.swift new file mode 100644 index 000000000..b4c5cfd98 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_Browse/LocalSavedSearchCacheInteractor.swift @@ -0,0 +1,38 @@ +// +// LocalSavedSearchCacheInteractor.swift +// InteractorLib +// +// Created by Qiang Huang on 10/30/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit + +@objc open class LocalSavedSearchCacheInteractor: DataListInteractor, SavedSearchesProtocol { + @objc public dynamic var savedSearches: [SavedSearchEntity]? { + get { return data as? [SavedSearchEntity] } + set { data = newValue } + } + + public func add(name: String, search: String?, filters: [String: Any]?) { + var savedSearches = self.savedSearches ?? [SavedSearchEntity]() + let savedSearch = SavedSearchEntity() + savedSearch.name = name + savedSearch.text = search + savedSearch.filters = filters + savedSearches.append(savedSearch) + self.savedSearches = savedSearches + + loader?.save(object: data) + } + + public func remove(savedSearch: SavedSearchEntity) { + var savedSearches = self.savedSearches + if let index = savedSearches?.firstIndex(where: { (item: SavedSearchEntity) -> Bool in + item === savedSearch + }) { + savedSearches?.remove(at: index) + } + self.savedSearches = savedSearches + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_Browse/SelfFilteredListInteractor.swift b/ParticlesCommonModels/ParticlesCommonModels/_Browse/SelfFilteredListInteractor.swift new file mode 100644 index 000000000..bd30ddeec --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_Browse/SelfFilteredListInteractor.swift @@ -0,0 +1,36 @@ +// +// SelfFilteredListInteractor.swift +// SelfFilteredListInteractor +// +// Created by Qiang Huang on 7/29/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import ParticlesKit + +@objc open class SelfFilteredListInteractor: FilteredListInteractor { + @objc public dynamic var pool: [ModelObjectProtocol]? { + didSet { + didSetPool(oldValue: oldValue) + } + } + + public override var data: [ModelObjectProtocol]? { + return pool + } + + private func didSetPool(oldValue: [ModelObjectProtocol]?) { + filter() + } + + public override func sort(data: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + return data + } + + public override func filter(data: ModelObjectProtocol, text: String?) -> Bool { + if let list = data as? FilteredListInteractorProtocol { + list.filterText = text + } + return true + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_Doer/Doer.swift b/ParticlesCommonModels/ParticlesCommonModels/_Doer/Doer.swift new file mode 100644 index 000000000..165560b5a --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_Doer/Doer.swift @@ -0,0 +1,39 @@ +// +// Doer.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/2/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +public protocol DoerProtocol { + func perform() -> Bool + func undo() +} + +public final class Doer: NSObject, SingletonProtocol { + private var doers: [DoerProtocol] = [DoerProtocol]() + + public static var shared: Doer = { + Doer() + }() + + public func perform(_ doer: DoerProtocol) -> Bool { + if doer.perform() { + doers.append(doer) + return true + } else { + return false + } + } + + public func undo() { + if let doer = doers.last { + doer.undo() + doers.removeLast() + } + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_Doer/LikeDoer.swift b/ParticlesCommonModels/ParticlesCommonModels/_Doer/LikeDoer.swift new file mode 100644 index 000000000..2af01dbc2 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_Doer/LikeDoer.swift @@ -0,0 +1,59 @@ +// +// LikeDoer.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/2/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit + +public enum LikeDoerAction { + case like + case dislike +} + +public class LikeDoer: DoerProtocol { + public var likedManager: LikedObjectsProtocol? + public var key: String? + public var action: LikeDoerAction + + public var liked: Bool = false + public var disliked: Bool = false + + public init(_ action: LikeDoerAction, likedManager: LikedObjectsProtocol?, key: String?) { + self.action = action + self.likedManager = likedManager + self.key = key + } + + public func perform() -> Bool { + if let likedManager = likedManager, let key = key { + liked = likedManager.liked(key: key) + disliked = likedManager.disliked(key: key) + switch action { + case .like: + likedManager.toggleLike(key: key) + return true + + case .dislike: + likedManager.toggleDislike(key: key) + return true + } + } + return false + } + + public func undo() { + if liked { + likedManager?.addLike(key: key) + } else { + likedManager?.removeLike(key: key) + } + if disliked { + likedManager?.addDislike(key: key) + } else { + likedManager?.removeDislike(key: key) + } + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_Like/DislikedListInteractor.swift b/ParticlesCommonModels/ParticlesCommonModels/_Like/DislikedListInteractor.swift new file mode 100644 index 000000000..affc8e343 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_Like/DislikedListInteractor.swift @@ -0,0 +1,50 @@ +// +// DislikedListInteractor.swift +// InteractorLib +// +// Created by Qiang Huang on 10/23/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +@objc open class DislikedListInteractor: ListInteractor { + open var dataCache: DataPoolInteractor? { + didSet { + changeObservation(from: oldValue, to: dataCache, keyPath: #keyPath(DataPoolInteractor.data)) { [weak self] _, _, _, _ in + self?.update() + } + } + } + + open var likedManager: LikedKeysInteractor? { + didSet { + changeObservation(from: oldValue, to: likedManager, keyPath: #keyPath(LikedKeysInteractor.disliked)) { [weak self] _, _, _, _ in + self?.update() + } + } + } + + public override init() { + super.init() + setup() + } + + open func setup() { + } + + open func update() { + if let data = self.dataCache?.data, let disliked = self.likedManager?.disliked { + var dislikes = [ModelObjectProtocol]() + for key in disliked { + if let object = data[key] { + dislikes.append(object) + } + } + sync(dislikes) + } else { + sync(nil) + } + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_Like/LikedKeysInteractor.swift b/ParticlesCommonModels/ParticlesCommonModels/_Like/LikedKeysInteractor.swift new file mode 100644 index 000000000..22ae726da --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_Like/LikedKeysInteractor.swift @@ -0,0 +1,48 @@ +// +// LikedKeysInteractor.swift +// InteractorLib +// +// Created by Qiang Huang on 10/23/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +@objc open class LikedKeysInteractor: LocalEntityCacheInteractor, LikedObjectsProtocol { + private let likedTag = "liked" + private let dislikedTag = "unliked" + + public var liked: [String]? { + get { return (entity as? DictionaryEntity)?.force.json?[likedTag] as? [String] } + set { + HapticFeedback.shared?.impact(level: .low) + willChangeValue(forKey: #keyPath(liked)) + (entity as? DictionaryEntity)?.force.data?[likedTag] = newValue + didChangeValue(forKey: #keyPath(liked)) + } + } + + public var disliked: [String]? { + get { return (entity as? DictionaryEntity)?.force.json?[dislikedTag] as? [String] } + set { + willChangeValue(forKey: #keyPath(disliked)) + (entity as? DictionaryEntity)?.force.data?[dislikedTag] = newValue + didChangeValue(forKey: #keyPath(disliked)) + } + } + + override open func createLoader() -> LoaderProtocol? { + if let path = path { + return LoaderProvider.shared?.localAsyncLoader(path: path, cache: self) + } + return nil + } + + override open func entityObject() -> ModelObjectProtocol { + let entity = DictionaryEntity().force + entity.json?[likedTag] = [String]() + entity.json?[dislikedTag] = [String]() + return entity + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_Like/LikedListInteractor.swift b/ParticlesCommonModels/ParticlesCommonModels/_Like/LikedListInteractor.swift new file mode 100644 index 000000000..890a9c451 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_Like/LikedListInteractor.swift @@ -0,0 +1,51 @@ +// +// LikedListInteractor.swift +// InteractorLib +// +// Created by Qiang Huang on 10/23/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +@objc open class LikedListInteractor: ListInteractor { + open var dataCache: DataPoolInteractor? { + didSet { + changeObservation(from: oldValue, to: dataCache, keyPath: #keyPath(DataPoolInteractor.data)) { [weak self] _, _, _, _ in + self?.update() + } + } + } + + open var likedManager: LikedKeysInteractor? { + didSet { + changeObservation(from: oldValue, to: likedManager, keyPath: #keyPath(LikedKeysInteractor.liked)) { [weak self] _, _, _, _ in + self?.update() + } + } + } + + public override init() { + super.init() + setup() + } + + open func setup() { + } + + open func update() { + if let data = self.dataCache?.data, let liked = self.likedManager?.liked { + let favorites = liked.compactMap { (key) -> ModelObjectProtocol? in + filter(object: data[key]) + } + sync(favorites) + } else { + sync(nil) + } + } + + open func filter(object: ModelObjectProtocol?) -> ModelObjectProtocol? { + return object + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_NavigationObjects/NavigationInteractor.swift b/ParticlesCommonModels/ParticlesCommonModels/_NavigationObjects/NavigationInteractor.swift new file mode 100644 index 000000000..860792c1b --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_NavigationObjects/NavigationInteractor.swift @@ -0,0 +1,54 @@ +// +// NavigationInteractor.swift +// ParticlesCommonModels +// +// Created by Qiang Huang on 1/30/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import ParticlesKit + +@objc public class NavigationInteractor: NSObject, InteractorProtocol { + @objc public dynamic var entity: ModelObjectProtocol? { + didSet { + changeObservation(from: oldValue, to: entity, keyPath: #keyPath(NavigationModelProtocol.children)) { [weak self] _, _, _, _ in + self?.syncChildren() + } + changeObservation(from: oldValue, to: entity, keyPath: #keyPath(NavigationModelProtocol.actions)) { [weak self] _, _, _, _ in + self?.syncActions() + } + } + } + + @objc public dynamic var navigation: NavigationModelProtocol? { + get { + return entity as? NavigationModelProtocol + } + set { + entity = newValue + } + } + + @objc public dynamic var children: ListInteractor? + @objc public dynamic var actions: ListInteractor? + + private func syncChildren() { + if let children = navigation?.children { + let list = self.children ?? ListInteractor() + list.sync(children) + self.children = list + } else { + children = nil + } + } + + private func syncActions() { + if let actions = navigation?.actions { + let list = self.actions ?? ListInteractor() + list.sync(actions) + self.actions = list + } else { + actions = nil + } + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_NavigationObjects/NavigationModelProtocols.swift b/ParticlesCommonModels/ParticlesCommonModels/_NavigationObjects/NavigationModelProtocols.swift new file mode 100644 index 000000000..0b7ba870a --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_NavigationObjects/NavigationModelProtocols.swift @@ -0,0 +1,24 @@ +// +// CommonModelProtocols.swift +// ParticlesCommonModels +// +// Created by Qiang Huang on 1/30/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import ParticlesKit + +@objc public protocol NavigationModelProtocol: ModelObjectProtocol { + @objc var title: String? { get } + @objc var subtitle: String? { get } + @objc var text: String? { get } + @objc var subtext: String? { get } + @objc var color: String? { get } + @objc var icon: URL? { get } + @objc var image: URL? { get } + @objc var link: URL? { get } + @objc var tag: String? { get } + + @objc var children: [NavigationModelProtocol]? { get } + @objc var actions: [NavigationModelProtocol]? { get } +} diff --git a/ParticlesCommonModels/ParticlesCommonModels/_NavigationObjects/NavigationObject.swift b/ParticlesCommonModels/ParticlesCommonModels/_NavigationObjects/NavigationObject.swift new file mode 100644 index 000000000..c844e0d01 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModels/_NavigationObjects/NavigationObject.swift @@ -0,0 +1,82 @@ +// +// NavigationObject.swift +// ParticlesCommonModels +// +// Created by Qiang Huang on 1/30/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import ParticlesKit + +public class NavigationObject: DictionaryEntity, NavigationModelProtocol { + @objc public var type: String? { + return parser.asString(data?["type"]) + } + + @objc public var title: String? { + return parser.asString(data?["title"]) + } + + @objc public var subtitle: String? { + return parser.asString(data?["subtitle"]) + } + + @objc public var text: String? { + return parser.asString(data?["text"]) + } + + @objc public var subtext: String? { + return parser.asString(data?["subtext"]) + } + + @objc public var color: String? { + return parser.asString(data?["color"]) + } + + @objc public var icon: URL? { + return parser.asURL(data?["icon"]) + } + + @objc public var image: URL? { + return parser.asURL(data?["image"]) + } + + @objc public var link: URL? { + return parser.asURL(data?["link"]) + } + + @objc public var tag: String? { + return parser.asString(data?["tag"]) + } + + @objc public var children: [NavigationModelProtocol]? + @objc public var actions: [NavigationModelProtocol]? + + override public func parse(dictionary: [String: Any]) { + super.parse(dictionary: dictionary) + + if let childrenData = parser.asArray(dictionary["children"]) as? [[String: Any]] { + var children = [NavigationModelProtocol]() + for childData in childrenData { + let child = NavigationObject() + child.parse(dictionary: childData) + children.append(child) + } + self.children = children + } else { + children = nil + } + + if let actionsData = parser.asArray(dictionary["actions"]) as? [[String: Any]] { + var actions = [NavigationModelProtocol]() + for actionData in actionsData { + let action = NavigationObject() + action.parse(dictionary: actionData) + actions.append(action) + } + self.actions = actions + } else { + actions = nil + } + } +} diff --git a/ParticlesCommonModels/ParticlesCommonModelsAppleTV/Info.plist b/ParticlesCommonModels/ParticlesCommonModelsAppleTV/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModelsAppleTV/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/ParticlesCommonModels/ParticlesCommonModelsAppleTV/ParticlesCommonModels.h b/ParticlesCommonModels/ParticlesCommonModelsAppleTV/ParticlesCommonModels.h new file mode 100644 index 000000000..6c3d2d707 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModelsAppleTV/ParticlesCommonModels.h @@ -0,0 +1,19 @@ +// +// ParticlesCommonModelsAppleTV.h +// ParticlesCommonModelsAppleTV +// +// Created by Qiang Huang on 12/17/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for ParticlesCommonModelsAppleTV. +FOUNDATION_EXPORT double ParticlesCommonModelsAppleTVVersionNumber; + +//! Project version string for ParticlesCommonModelsAppleTV. +FOUNDATION_EXPORT const unsigned char ParticlesCommonModelsAppleTVVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/ParticlesCommonModels/ParticlesCommonModelsAppleTVTests/Info.plist b/ParticlesCommonModels/ParticlesCommonModelsAppleTVTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModelsAppleTVTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/ParticlesCommonModels/ParticlesCommonModelsAppleTVTests/ParticlesCommonModelsAppleTVTests.swift b/ParticlesCommonModels/ParticlesCommonModelsAppleTVTests/ParticlesCommonModelsAppleTVTests.swift new file mode 100644 index 000000000..014d64fb8 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModelsAppleTVTests/ParticlesCommonModelsAppleTVTests.swift @@ -0,0 +1,34 @@ +// +// ParticlesCommonModelsAppleTVTests.swift +// ParticlesCommonModelsAppleTVTests +// +// Created by Qiang Huang on 12/17/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import XCTest +@testable import ParticlesCommonModels + +class ParticlesCommonModelsAppleTVTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/ParticlesCommonModels/ParticlesCommonModelsAppleWatch/Info.plist b/ParticlesCommonModels/ParticlesCommonModelsAppleWatch/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModelsAppleWatch/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/ParticlesCommonModels/ParticlesCommonModelsAppleWatch/ParticlesCommonModels.h b/ParticlesCommonModels/ParticlesCommonModelsAppleWatch/ParticlesCommonModels.h new file mode 100644 index 000000000..907a37599 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModelsAppleWatch/ParticlesCommonModels.h @@ -0,0 +1,19 @@ +// +// ParticlesCommonModelsAppleWatch.h +// ParticlesCommonModelsAppleWatch +// +// Created by Qiang Huang on 12/17/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for ParticlesCommonModelsAppleWatch. +FOUNDATION_EXPORT double ParticlesCommonModelsAppleWatchVersionNumber; + +//! Project version string for ParticlesCommonModelsAppleWatch. +FOUNDATION_EXPORT const unsigned char ParticlesCommonModelsAppleWatchVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/ParticlesCommonModels/ParticlesCommonModelsTests/Info.plist b/ParticlesCommonModels/ParticlesCommonModelsTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModelsTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/ParticlesCommonModels/ParticlesCommonModelsTests/ParticlesCommonModelsTests.swift b/ParticlesCommonModels/ParticlesCommonModelsTests/ParticlesCommonModelsTests.swift new file mode 100644 index 000000000..430196dcb --- /dev/null +++ b/ParticlesCommonModels/ParticlesCommonModelsTests/ParticlesCommonModelsTests.swift @@ -0,0 +1,34 @@ +// +// ParticlesCommonModelsTests.swift +// ParticlesCommonModelsTests +// +// Created by Qiang Huang on 12/17/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import XCTest +@testable import ParticlesCommonModels + +class ParticlesCommonModelsTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/ParticlesKit/ParticlesKit.xcodeproj/project.pbxproj b/ParticlesKit/ParticlesKit.xcodeproj/project.pbxproj new file mode 100644 index 000000000..113c12181 --- /dev/null +++ b/ParticlesKit/ParticlesKit.xcodeproj/project.pbxproj @@ -0,0 +1,2297 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 027D5C0C28AB0AE300A5B900 /* ParticilesKitConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027D5C0B28AB0AE300A5B900 /* ParticilesKitConfig.swift */; }; + 02BCE2632899D64000E7BDCB /* WorkerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02BCE2622899D64000E7BDCB /* WorkerProtocol.swift */; }; + 02EE8FDA28270CB000225B56 /* BaseInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EE8FD928270CB000225B56 /* BaseInteractor.swift */; }; + 3101F98225112E5600AC4010 /* AuthLoginAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F98125112E5600AC4010 /* AuthLoginAction.swift */; }; + 3101F98325112E5600AC4010 /* AuthLoginAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F98125112E5600AC4010 /* AuthLoginAction.swift */; }; + 3101F98425112E5600AC4010 /* AuthLoginAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F98125112E5600AC4010 /* AuthLoginAction.swift */; }; + 3101F98625112E7300AC4010 /* AuthLogoutAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F98525112E7300AC4010 /* AuthLogoutAction.swift */; }; + 3101F98725112E7300AC4010 /* AuthLogoutAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F98525112E7300AC4010 /* AuthLogoutAction.swift */; }; + 3101F98825112E7300AC4010 /* AuthLogoutAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F98525112E7300AC4010 /* AuthLogoutAction.swift */; }; + 3101F98B25112EBE00AC4010 /* LocatorService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F98A25112EBE00AC4010 /* LocatorService.swift */; }; + 3101F98C25112EBE00AC4010 /* LocatorService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F98A25112EBE00AC4010 /* LocatorService.swift */; }; + 3101F98D25112EBE00AC4010 /* LocatorService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F98A25112EBE00AC4010 /* LocatorService.swift */; }; + 3101F99325112F2600AC4010 /* Login.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3101F99125112F2500AC4010 /* Login.xib */; }; + 3101F99425112F2600AC4010 /* Logout.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3101F99225112F2600AC4010 /* Logout.xib */; }; + 310C4F742533B57700DF1D62 /* StreamApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310C4F732533B57700DF1D62 /* StreamApi.swift */; }; + 310C4F752533B57700DF1D62 /* StreamApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310C4F732533B57700DF1D62 /* StreamApi.swift */; }; + 310C4F762533B57700DF1D62 /* StreamApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310C4F732533B57700DF1D62 /* StreamApi.swift */; }; + 311C0FE421B0E9C4001775BA /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 311C0FDA21B0E9C4001775BA /* ParticlesKit.framework */; }; + 311C0FE921B0E9C4001775BA /* ParticlesKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311C0FE821B0E9C4001775BA /* ParticlesKitTests.swift */; }; + 311C0FEB21B0E9C4001775BA /* ParticlesKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 311C0FDD21B0E9C4001775BA /* ParticlesKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 312CE30B2630867E00C519C0 /* JsonKeychainCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312CE30A2630867E00C519C0 /* JsonKeychainCaching.swift */; }; + 312CE30C2630867E00C519C0 /* JsonKeychainCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312CE30A2630867E00C519C0 /* JsonKeychainCaching.swift */; }; + 312CE30D2630867E00C519C0 /* JsonKeychainCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312CE30A2630867E00C519C0 /* JsonKeychainCaching.swift */; }; + 312F933B2530ED37005874B6 /* KeyedDataInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312F933A2530ED37005874B6 /* KeyedDataInteractor.swift */; }; + 312F933C2530ED37005874B6 /* KeyedDataInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312F933A2530ED37005874B6 /* KeyedDataInteractor.swift */; }; + 312F933D2530ED37005874B6 /* KeyedDataInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312F933A2530ED37005874B6 /* KeyedDataInteractor.swift */; }; + 31308B6B24FAEE67003B5B9A /* PlacemarkProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B6A24FAEE67003B5B9A /* PlacemarkProtocols.swift */; }; + 31308B6C24FAEE67003B5B9A /* PlacemarkProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B6A24FAEE67003B5B9A /* PlacemarkProtocols.swift */; }; + 31308B6D24FAEE67003B5B9A /* PlacemarkProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B6A24FAEE67003B5B9A /* PlacemarkProtocols.swift */; }; + 313EB16A21BA246A00BEF926 /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB16121BA246A00BEF926 /* ParticlesKit.framework */; }; + 313EB16F21BA246B00BEF926 /* ParticlesKitAppleTVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EB16E21BA246B00BEF926 /* ParticlesKitAppleTVTests.swift */; }; + 313EB17121BA246B00BEF926 /* ParticlesKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 313EB16321BA246A00BEF926 /* ParticlesKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 313EB21A21BA268600BEF926 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB17C21BA246B00BEF926 /* Utilities.framework */; }; + 313EB28121BA282300BEF926 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB27821BA27F800BEF926 /* RoutingKit.framework */; }; + 31439A152672B1A4003871A1 /* ObjectLineInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A142672B1A4003871A1 /* ObjectLineInteractor.swift */; }; + 31439A162672B1A4003871A1 /* ObjectLineInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A142672B1A4003871A1 /* ObjectLineInteractor.swift */; }; + 31439A172672B1A4003871A1 /* ObjectLineInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A142672B1A4003871A1 /* ObjectLineInteractor.swift */; }; + 31439A192672B23B003871A1 /* ObjectValueLineInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A182672B23B003871A1 /* ObjectValueLineInteractor.swift */; }; + 31439A1A2672B23B003871A1 /* ObjectValueLineInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A182672B23B003871A1 /* ObjectValueLineInteractor.swift */; }; + 31439A1B2672B23B003871A1 /* ObjectValueLineInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A182672B23B003871A1 /* ObjectValueLineInteractor.swift */; }; + 31439A302672BCDC003871A1 /* FormatterProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A2F2672BCDC003871A1 /* FormatterProtocols.swift */; }; + 31439A312672BCDC003871A1 /* FormatterProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A2F2672BCDC003871A1 /* FormatterProtocols.swift */; }; + 31439A322672BCDC003871A1 /* FormatterProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A2F2672BCDC003871A1 /* FormatterProtocols.swift */; }; + 31439A342672BD28003871A1 /* DollarValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A332672BD28003871A1 /* DollarValueFormatter.swift */; }; + 31439A352672BD28003871A1 /* DollarValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A332672BD28003871A1 /* DollarValueFormatter.swift */; }; + 31439A362672BD28003871A1 /* DollarValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A332672BD28003871A1 /* DollarValueFormatter.swift */; }; + 31439A382672BEB3003871A1 /* AmountValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A372672BEB3003871A1 /* AmountValueFormatter.swift */; }; + 31439A392672BEB3003871A1 /* AmountValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A372672BEB3003871A1 /* AmountValueFormatter.swift */; }; + 31439A3A2672BEB3003871A1 /* AmountValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A372672BEB3003871A1 /* AmountValueFormatter.swift */; }; + 31439A3C2672C0AE003871A1 /* PercentValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A3B2672C0AE003871A1 /* PercentValueFormatter.swift */; }; + 31439A3D2672C0AE003871A1 /* PercentValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A3B2672C0AE003871A1 /* PercentValueFormatter.swift */; }; + 31439A3E2672C0AE003871A1 /* PercentValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A3B2672C0AE003871A1 /* PercentValueFormatter.swift */; }; + 31439A582673DAC6003871A1 /* ObjectOptionLineInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A572673DAC6003871A1 /* ObjectOptionLineInteractor.swift */; }; + 31439A592673DAC6003871A1 /* ObjectOptionLineInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A572673DAC6003871A1 /* ObjectOptionLineInteractor.swift */; }; + 31439A5A2673DAC6003871A1 /* ObjectOptionLineInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A572673DAC6003871A1 /* ObjectOptionLineInteractor.swift */; }; + 31439A7A26741B6A003871A1 /* LeverageValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A7926741B6A003871A1 /* LeverageValueFormatter.swift */; }; + 31439A7B26741B6A003871A1 /* LeverageValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A7926741B6A003871A1 /* LeverageValueFormatter.swift */; }; + 31439A7C26741B6A003871A1 /* LeverageValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A7926741B6A003871A1 /* LeverageValueFormatter.swift */; }; + 31439A802677F53D003871A1 /* ObjectCompositeLineInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A7F2677F53D003871A1 /* ObjectCompositeLineInteractor.swift */; }; + 31439A812677F53D003871A1 /* ObjectCompositeLineInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A7F2677F53D003871A1 /* ObjectCompositeLineInteractor.swift */; }; + 31439A822677F53D003871A1 /* ObjectCompositeLineInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A7F2677F53D003871A1 /* ObjectCompositeLineInteractor.swift */; }; + 31471FA624BA34E600057221 /* DictionaryInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FA524BA34E600057221 /* DictionaryInteractor.swift */; }; + 31471FA724BA34E600057221 /* DictionaryInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FA524BA34E600057221 /* DictionaryInteractor.swift */; }; + 31471FA824BA34E600057221 /* DictionaryInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FA524BA34E600057221 /* DictionaryInteractor.swift */; }; + 31471FAB24BA353B00057221 /* UrlBadgingInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FAA24BA353B00057221 /* UrlBadgingInteractor.swift */; }; + 31471FAC24BA353B00057221 /* UrlBadgingInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FAA24BA353B00057221 /* UrlBadgingInteractor.swift */; }; + 31471FAD24BA353B00057221 /* UrlBadgingInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FAA24BA353B00057221 /* UrlBadgingInteractor.swift */; }; + 314B612123DCCE6100139EB3 /* SavedSearchesProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B323DCCE5F00139EB3 /* SavedSearchesProtocol.swift */; }; + 314B612223DCCE6100139EB3 /* SavedSearchesProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B323DCCE5F00139EB3 /* SavedSearchesProtocol.swift */; }; + 314B612323DCCE6100139EB3 /* SavedSearchesProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B323DCCE5F00139EB3 /* SavedSearchesProtocol.swift */; }; + 314B612523DCCE6100139EB3 /* LikedObjectsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B423DCCE5F00139EB3 /* LikedObjectsProtocol.swift */; }; + 314B612623DCCE6100139EB3 /* LikedObjectsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B423DCCE5F00139EB3 /* LikedObjectsProtocol.swift */; }; + 314B612723DCCE6100139EB3 /* LikedObjectsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B423DCCE5F00139EB3 /* LikedObjectsProtocol.swift */; }; + 314B612923DCCE6100139EB3 /* IOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B523DCCE5F00139EB3 /* IOProtocol.swift */; }; + 314B612A23DCCE6100139EB3 /* IOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B523DCCE5F00139EB3 /* IOProtocol.swift */; }; + 314B612B23DCCE6100139EB3 /* IOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B523DCCE5F00139EB3 /* IOProtocol.swift */; }; + 314B612D23DCCE6100139EB3 /* JsonCachingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B723DCCE5F00139EB3 /* JsonCachingProtocol.swift */; }; + 314B612E23DCCE6100139EB3 /* JsonCachingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B723DCCE5F00139EB3 /* JsonCachingProtocol.swift */; }; + 314B612F23DCCE6100139EB3 /* JsonCachingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B723DCCE5F00139EB3 /* JsonCachingProtocol.swift */; }; + 314B613123DCCE6100139EB3 /* JsonDocumentFileAsyncCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B823DCCE5F00139EB3 /* JsonDocumentFileAsyncCaching.swift */; }; + 314B613223DCCE6100139EB3 /* JsonDocumentFileAsyncCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B823DCCE5F00139EB3 /* JsonDocumentFileAsyncCaching.swift */; }; + 314B613323DCCE6100139EB3 /* JsonDocumentFileAsyncCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B823DCCE5F00139EB3 /* JsonDocumentFileAsyncCaching.swift */; }; + 314B613523DCCE6100139EB3 /* JsonDocumentFileCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B923DCCE5F00139EB3 /* JsonDocumentFileCaching.swift */; }; + 314B613623DCCE6100139EB3 /* JsonDocumentFileCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B923DCCE5F00139EB3 /* JsonDocumentFileCaching.swift */; }; + 314B613723DCCE6100139EB3 /* JsonDocumentFileCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60B923DCCE5F00139EB3 /* JsonDocumentFileCaching.swift */; }; + 314B614523DCCE6100139EB3 /* AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C023DCCE5F00139EB3 /* AppInfo.swift */; }; + 314B614623DCCE6100139EB3 /* AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C023DCCE5F00139EB3 /* AppInfo.swift */; }; + 314B614723DCCE6100139EB3 /* AppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C023DCCE5F00139EB3 /* AppInfo.swift */; }; + 314B614923DCCE6100139EB3 /* ParallelWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C323DCCE6000139EB3 /* ParallelWebApi.swift */; }; + 314B614A23DCCE6100139EB3 /* ParallelWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C323DCCE6000139EB3 /* ParallelWebApi.swift */; }; + 314B614B23DCCE6100139EB3 /* ParallelWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C323DCCE6000139EB3 /* ParallelWebApi.swift */; }; + 314B614D23DCCE6100139EB3 /* HttpApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C423DCCE6000139EB3 /* HttpApi.swift */; }; + 314B614E23DCCE6100139EB3 /* HttpApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C423DCCE6000139EB3 /* HttpApi.swift */; }; + 314B614F23DCCE6100139EB3 /* HttpApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C423DCCE6000139EB3 /* HttpApi.swift */; }; + 314B615123DCCE6100139EB3 /* WebApiInjectionProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C523DCCE6000139EB3 /* WebApiInjectionProtocols.swift */; }; + 314B615223DCCE6100139EB3 /* WebApiInjectionProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C523DCCE6000139EB3 /* WebApiInjectionProtocols.swift */; }; + 314B615323DCCE6100139EB3 /* WebApiInjectionProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C523DCCE6000139EB3 /* WebApiInjectionProtocols.swift */; }; + 314B615523DCCE6100139EB3 /* WebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C623DCCE6000139EB3 /* WebApi.swift */; }; + 314B615623DCCE6100139EB3 /* WebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C623DCCE6000139EB3 /* WebApi.swift */; }; + 314B615723DCCE6100139EB3 /* WebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C623DCCE6000139EB3 /* WebApi.swift */; }; + 314B615D23DCCE6100139EB3 /* PagedWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C823DCCE6000139EB3 /* PagedWebApi.swift */; }; + 314B615E23DCCE6100139EB3 /* PagedWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C823DCCE6000139EB3 /* PagedWebApi.swift */; }; + 314B615F23DCCE6100139EB3 /* PagedWebApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C823DCCE6000139EB3 /* PagedWebApi.swift */; }; + 314B616123DCCE6100139EB3 /* ApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C923DCCE6000139EB3 /* ApiProtocol.swift */; }; + 314B616223DCCE6100139EB3 /* ApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C923DCCE6000139EB3 /* ApiProtocol.swift */; }; + 314B616323DCCE6100139EB3 /* ApiProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60C923DCCE6000139EB3 /* ApiProtocol.swift */; }; + 314B616523DCCE6100139EB3 /* UploadService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60CB23DCCE6000139EB3 /* UploadService.swift */; }; + 314B616623DCCE6100139EB3 /* UploadService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60CB23DCCE6000139EB3 /* UploadService.swift */; }; + 314B616723DCCE6100139EB3 /* UploadService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60CB23DCCE6000139EB3 /* UploadService.swift */; }; + 314B616923DCCE6100139EB3 /* UploadApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60CC23DCCE6000139EB3 /* UploadApi.swift */; }; + 314B616A23DCCE6100139EB3 /* UploadApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60CC23DCCE6000139EB3 /* UploadApi.swift */; }; + 314B616B23DCCE6100139EB3 /* UploadApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60CC23DCCE6000139EB3 /* UploadApi.swift */; }; + 314B616D23DCCE6100139EB3 /* EndpointResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60CE23DCCE6000139EB3 /* EndpointResolver.swift */; }; + 314B616E23DCCE6100139EB3 /* EndpointResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60CE23DCCE6000139EB3 /* EndpointResolver.swift */; }; + 314B616F23DCCE6100139EB3 /* EndpointResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60CE23DCCE6000139EB3 /* EndpointResolver.swift */; }; + 314B617123DCCE6100139EB3 /* JsonEndpointResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60CF23DCCE6000139EB3 /* JsonEndpointResolver.swift */; }; + 314B617223DCCE6100139EB3 /* JsonEndpointResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60CF23DCCE6000139EB3 /* JsonEndpointResolver.swift */; }; + 314B617323DCCE6100139EB3 /* JsonEndpointResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60CF23DCCE6000139EB3 /* JsonEndpointResolver.swift */; }; + 314B617D23DCCE6100139EB3 /* GridPresenterManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60D523DCCE6000139EB3 /* GridPresenterManagerProtocol.swift */; }; + 314B617E23DCCE6100139EB3 /* GridPresenterManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60D523DCCE6000139EB3 /* GridPresenterManagerProtocol.swift */; }; + 314B617F23DCCE6100139EB3 /* GridPresenterManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60D523DCCE6000139EB3 /* GridPresenterManagerProtocol.swift */; }; + 314B618123DCCE6100139EB3 /* GridPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60D623DCCE6000139EB3 /* GridPresenter.swift */; }; + 314B618223DCCE6100139EB3 /* GridPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60D623DCCE6000139EB3 /* GridPresenter.swift */; }; + 314B618323DCCE6100139EB3 /* GridPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60D623DCCE6000139EB3 /* GridPresenter.swift */; }; + 314B618523DCCE6100139EB3 /* GridObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60D723DCCE6000139EB3 /* GridObjectPresenter.swift */; }; + 314B618623DCCE6100139EB3 /* GridObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60D723DCCE6000139EB3 /* GridObjectPresenter.swift */; }; + 314B618723DCCE6100139EB3 /* GridObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60D723DCCE6000139EB3 /* GridObjectPresenter.swift */; }; + 314B618923DCCE6100139EB3 /* XibRegisterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60D923DCCE6000139EB3 /* XibRegisterProtocol.swift */; }; + 314B618A23DCCE6100139EB3 /* XibRegisterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60D923DCCE6000139EB3 /* XibRegisterProtocol.swift */; }; + 314B618B23DCCE6100139EB3 /* XibRegisterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60D923DCCE6000139EB3 /* XibRegisterProtocol.swift */; }; + 314B618D23DCCE6100139EB3 /* XibProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60DA23DCCE6000139EB3 /* XibProviderProtocol.swift */; }; + 314B618E23DCCE6100139EB3 /* XibProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60DA23DCCE6000139EB3 /* XibProviderProtocol.swift */; }; + 314B618F23DCCE6100139EB3 /* XibProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60DA23DCCE6000139EB3 /* XibProviderProtocol.swift */; }; + 314B619523DCCE6100139EB3 /* SelectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60DD23DCCE6000139EB3 /* SelectionHandler.swift */; }; + 314B619623DCCE6100139EB3 /* SelectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60DD23DCCE6000139EB3 /* SelectionHandler.swift */; }; + 314B619723DCCE6100139EB3 /* SelectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60DD23DCCE6000139EB3 /* SelectionHandler.swift */; }; + 314B619D23DCCE6100139EB3 /* ObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E023DCCE6000139EB3 /* ObjectPresenter.swift */; }; + 314B619E23DCCE6100139EB3 /* ObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E023DCCE6000139EB3 /* ObjectPresenter.swift */; }; + 314B619F23DCCE6100139EB3 /* ObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E023DCCE6000139EB3 /* ObjectPresenter.swift */; }; + 314B61A123DCCE6100139EB3 /* ListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E223DCCE6000139EB3 /* ListPresenter.swift */; }; + 314B61A223DCCE6100139EB3 /* ListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E223DCCE6000139EB3 /* ListPresenter.swift */; }; + 314B61A323DCCE6100139EB3 /* ListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E223DCCE6000139EB3 /* ListPresenter.swift */; }; + 314B61A523DCCE6100139EB3 /* ListObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E323DCCE6000139EB3 /* ListObjectPresenter.swift */; }; + 314B61A623DCCE6100139EB3 /* ListObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E323DCCE6000139EB3 /* ListObjectPresenter.swift */; }; + 314B61A723DCCE6100139EB3 /* ListObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E323DCCE6000139EB3 /* ListObjectPresenter.swift */; }; + 314B61A923DCCE6100139EB3 /* ListPresenterManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E423DCCE6000139EB3 /* ListPresenterManagerProtocol.swift */; }; + 314B61AA23DCCE6100139EB3 /* ListPresenterManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E423DCCE6000139EB3 /* ListPresenterManagerProtocol.swift */; }; + 314B61AB23DCCE6100139EB3 /* ListPresenterManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E423DCCE6000139EB3 /* ListPresenterManagerProtocol.swift */; }; + 314B61B123DCCE6100139EB3 /* XibAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E823DCCE6000139EB3 /* XibAction.swift */; }; + 314B61B223DCCE6100139EB3 /* XibAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E823DCCE6000139EB3 /* XibAction.swift */; }; + 314B61B323DCCE6100139EB3 /* XibAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E823DCCE6000139EB3 /* XibAction.swift */; }; + 314B61B523DCCE6100139EB3 /* FilterEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E923DCCE6000139EB3 /* FilterEntity.swift */; }; + 314B61B623DCCE6100139EB3 /* FilterEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E923DCCE6000139EB3 /* FilterEntity.swift */; }; + 314B61B723DCCE6100139EB3 /* FilterEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60E923DCCE6000139EB3 /* FilterEntity.swift */; }; + 314B61B923DCCE6100139EB3 /* SavedSearchEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60EA23DCCE6000139EB3 /* SavedSearchEntity.swift */; }; + 314B61BA23DCCE6100139EB3 /* SavedSearchEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60EA23DCCE6000139EB3 /* SavedSearchEntity.swift */; }; + 314B61BB23DCCE6100139EB3 /* SavedSearchEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60EA23DCCE6000139EB3 /* SavedSearchEntity.swift */; }; + 314B61BD23DCCE6200139EB3 /* DictionaryEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60EC23DCCE6000139EB3 /* DictionaryEntity.swift */; }; + 314B61BE23DCCE6200139EB3 /* DictionaryEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60EC23DCCE6000139EB3 /* DictionaryEntity.swift */; }; + 314B61BF23DCCE6200139EB3 /* DictionaryEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60EC23DCCE6000139EB3 /* DictionaryEntity.swift */; }; + 314B61C123DCCE6200139EB3 /* TimeCounter+Particle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60ED23DCCE6000139EB3 /* TimeCounter+Particle.swift */; }; + 314B61C223DCCE6200139EB3 /* TimeCounter+Particle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60ED23DCCE6000139EB3 /* TimeCounter+Particle.swift */; }; + 314B61C323DCCE6200139EB3 /* TimeCounter+Particle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60ED23DCCE6000139EB3 /* TimeCounter+Particle.swift */; }; + 314B61C523DCCE6200139EB3 /* ModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60EF23DCCE6000139EB3 /* ModelProtocol.swift */; }; + 314B61C623DCCE6200139EB3 /* ModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60EF23DCCE6000139EB3 /* ModelProtocol.swift */; }; + 314B61C723DCCE6200139EB3 /* ModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60EF23DCCE6000139EB3 /* ModelProtocol.swift */; }; + 314B61C923DCCE6200139EB3 /* Loader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F123DCCE6000139EB3 /* Loader.swift */; }; + 314B61CA23DCCE6200139EB3 /* Loader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F123DCCE6000139EB3 /* Loader.swift */; }; + 314B61CB23DCCE6200139EB3 /* Loader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F123DCCE6000139EB3 /* Loader.swift */; }; + 314B61CD23DCCE6200139EB3 /* LoaderProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F223DCCE6000139EB3 /* LoaderProvider.swift */; }; + 314B61CE23DCCE6200139EB3 /* LoaderProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F223DCCE6000139EB3 /* LoaderProvider.swift */; }; + 314B61CF23DCCE6200139EB3 /* LoaderProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F223DCCE6000139EB3 /* LoaderProvider.swift */; }; + 314B61D123DCCE6200139EB3 /* LoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F323DCCE6000139EB3 /* LoaderProtocol.swift */; }; + 314B61D223DCCE6200139EB3 /* LoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F323DCCE6000139EB3 /* LoaderProtocol.swift */; }; + 314B61D323DCCE6200139EB3 /* LoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F323DCCE6000139EB3 /* LoaderProtocol.swift */; }; + 314B61D523DCCE6200139EB3 /* CompositeErrorLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F623DCCE6000139EB3 /* CompositeErrorLogging.swift */; }; + 314B61D623DCCE6200139EB3 /* CompositeErrorLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F623DCCE6000139EB3 /* CompositeErrorLogging.swift */; }; + 314B61D723DCCE6200139EB3 /* CompositeErrorLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F623DCCE6000139EB3 /* CompositeErrorLogging.swift */; }; + 314B61D923DCCE6200139EB3 /* DebugErrorLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F723DCCE6000139EB3 /* DebugErrorLogging.swift */; }; + 314B61DA23DCCE6200139EB3 /* DebugErrorLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F723DCCE6000139EB3 /* DebugErrorLogging.swift */; }; + 314B61DB23DCCE6200139EB3 /* DebugErrorLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F723DCCE6000139EB3 /* DebugErrorLogging.swift */; }; + 314B61DD23DCCE6200139EB3 /* ErrorLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F823DCCE6000139EB3 /* ErrorLogging.swift */; }; + 314B61DE23DCCE6200139EB3 /* ErrorLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F823DCCE6000139EB3 /* ErrorLogging.swift */; }; + 314B61DF23DCCE6200139EB3 /* ErrorLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60F823DCCE6000139EB3 /* ErrorLogging.swift */; }; + 314B61E123DCCE6200139EB3 /* ApiReplayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60FA23DCCE6000139EB3 /* ApiReplayer.swift */; }; + 314B61E223DCCE6200139EB3 /* ApiReplayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60FA23DCCE6000139EB3 /* ApiReplayer.swift */; }; + 314B61E323DCCE6200139EB3 /* ApiReplayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60FA23DCCE6000139EB3 /* ApiReplayer.swift */; }; + 314B61E523DCCE6200139EB3 /* DateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60FD23DCCE6000139EB3 /* DateService.swift */; }; + 314B61E623DCCE6200139EB3 /* DateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60FD23DCCE6000139EB3 /* DateService.swift */; }; + 314B61E723DCCE6200139EB3 /* DateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B60FD23DCCE6000139EB3 /* DateService.swift */; }; + 314B61F123DCCE6200139EB3 /* JsonCredentialsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610423DCCE6000139EB3 /* JsonCredentialsProvider.swift */; }; + 314B61F223DCCE6200139EB3 /* JsonCredentialsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610423DCCE6000139EB3 /* JsonCredentialsProvider.swift */; }; + 314B61F323DCCE6200139EB3 /* JsonCredentialsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610423DCCE6000139EB3 /* JsonCredentialsProvider.swift */; }; + 314B61F523DCCE6200139EB3 /* ParticlesInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610623DCCE6000139EB3 /* ParticlesInjection.swift */; }; + 314B61F623DCCE6200139EB3 /* ParticlesInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610623DCCE6000139EB3 /* ParticlesInjection.swift */; }; + 314B61F723DCCE6200139EB3 /* ParticlesInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610623DCCE6000139EB3 /* ParticlesInjection.swift */; }; + 314B61F923DCCE6200139EB3 /* LocalJsonCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610823DCCE6100139EB3 /* LocalJsonCacheInteractor.swift */; }; + 314B61FA23DCCE6200139EB3 /* LocalJsonCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610823DCCE6100139EB3 /* LocalJsonCacheInteractor.swift */; }; + 314B61FB23DCCE6200139EB3 /* LocalJsonCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610823DCCE6100139EB3 /* LocalJsonCacheInteractor.swift */; }; + 314B61FD23DCCE6200139EB3 /* FilteredListInteractorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610A23DCCE6100139EB3 /* FilteredListInteractorProtocol.swift */; }; + 314B61FE23DCCE6200139EB3 /* FilteredListInteractorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610A23DCCE6100139EB3 /* FilteredListInteractorProtocol.swift */; }; + 314B61FF23DCCE6200139EB3 /* FilteredListInteractorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610A23DCCE6100139EB3 /* FilteredListInteractorProtocol.swift */; }; + 314B620123DCCE6200139EB3 /* LoadingObjectInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610C23DCCE6100139EB3 /* LoadingObjectInteractor.swift */; }; + 314B620223DCCE6200139EB3 /* LoadingObjectInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610C23DCCE6100139EB3 /* LoadingObjectInteractor.swift */; }; + 314B620323DCCE6200139EB3 /* LoadingObjectInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610C23DCCE6100139EB3 /* LoadingObjectInteractor.swift */; }; + 314B620523DCCE6200139EB3 /* InteractorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610D23DCCE6100139EB3 /* InteractorProtocol.swift */; }; + 314B620623DCCE6200139EB3 /* InteractorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610D23DCCE6100139EB3 /* InteractorProtocol.swift */; }; + 314B620723DCCE6200139EB3 /* InteractorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610D23DCCE6100139EB3 /* InteractorProtocol.swift */; }; + 314B620923DCCE6200139EB3 /* SelectableListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610F23DCCE6100139EB3 /* SelectableListInteractor.swift */; }; + 314B620A23DCCE6200139EB3 /* SelectableListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610F23DCCE6100139EB3 /* SelectableListInteractor.swift */; }; + 314B620B23DCCE6200139EB3 /* SelectableListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B610F23DCCE6100139EB3 /* SelectableListInteractor.swift */; }; + 314B620D23DCCE6200139EB3 /* ListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611023DCCE6100139EB3 /* ListInteractor.swift */; }; + 314B620E23DCCE6200139EB3 /* ListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611023DCCE6100139EB3 /* ListInteractor.swift */; }; + 314B620F23DCCE6200139EB3 /* ListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611023DCCE6100139EB3 /* ListInteractor.swift */; }; + 314B621123DCCE6200139EB3 /* XibJsonFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611123DCCE6100139EB3 /* XibJsonFile.swift */; }; + 314B621223DCCE6200139EB3 /* XibJsonFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611123DCCE6100139EB3 /* XibJsonFile.swift */; }; + 314B621323DCCE6200139EB3 /* XibJsonFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611123DCCE6100139EB3 /* XibJsonFile.swift */; }; + 314B621523DCCE6200139EB3 /* DataListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611223DCCE6100139EB3 /* DataListInteractor.swift */; }; + 314B621623DCCE6200139EB3 /* DataListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611223DCCE6100139EB3 /* DataListInteractor.swift */; }; + 314B621723DCCE6200139EB3 /* DataListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611223DCCE6100139EB3 /* DataListInteractor.swift */; }; + 314B621923DCCE6200139EB3 /* DataPoolInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611323DCCE6100139EB3 /* DataPoolInteractor.swift */; }; + 314B621A23DCCE6200139EB3 /* DataPoolInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611323DCCE6100139EB3 /* DataPoolInteractor.swift */; }; + 314B621B23DCCE6200139EB3 /* DataPoolInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611323DCCE6100139EB3 /* DataPoolInteractor.swift */; }; + 314B621D23DCCE6200139EB3 /* LocalEntityCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611523DCCE6100139EB3 /* LocalEntityCacheInteractor.swift */; }; + 314B621E23DCCE6200139EB3 /* LocalEntityCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611523DCCE6100139EB3 /* LocalEntityCacheInteractor.swift */; }; + 314B621F23DCCE6200139EB3 /* LocalEntityCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611523DCCE6100139EB3 /* LocalEntityCacheInteractor.swift */; }; + 314B622123DCCE6200139EB3 /* LocalDebugCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611623DCCE6100139EB3 /* LocalDebugCacheInteractor.swift */; }; + 314B622223DCCE6200139EB3 /* LocalDebugCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611623DCCE6100139EB3 /* LocalDebugCacheInteractor.swift */; }; + 314B622323DCCE6200139EB3 /* LocalDebugCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611623DCCE6100139EB3 /* LocalDebugCacheInteractor.swift */; }; + 314B622923DCCE6200139EB3 /* LocalMapCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611823DCCE6100139EB3 /* LocalMapCacheInteractor.swift */; }; + 314B622A23DCCE6200139EB3 /* LocalMapCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611823DCCE6100139EB3 /* LocalMapCacheInteractor.swift */; }; + 314B622B23DCCE6200139EB3 /* LocalMapCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611823DCCE6100139EB3 /* LocalMapCacheInteractor.swift */; }; + 314B622D23DCCE6200139EB3 /* LocalFeatureFlagsCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611923DCCE6100139EB3 /* LocalFeatureFlagsCacheInteractor.swift */; }; + 314B622E23DCCE6200139EB3 /* LocalFeatureFlagsCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611923DCCE6100139EB3 /* LocalFeatureFlagsCacheInteractor.swift */; }; + 314B622F23DCCE6200139EB3 /* LocalFeatureFlagsCacheInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611923DCCE6100139EB3 /* LocalFeatureFlagsCacheInteractor.swift */; }; + 314B623123DCCE6200139EB3 /* GridInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611B23DCCE6100139EB3 /* GridInteractor.swift */; }; + 314B623223DCCE6200139EB3 /* GridInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611B23DCCE6100139EB3 /* GridInteractor.swift */; }; + 314B623323DCCE6200139EB3 /* GridInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B611B23DCCE6100139EB3 /* GridInteractor.swift */; }; + 314B73CD23DD1C5000139EB3 /* MapProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B73CC23DD1C5000139EB3 /* MapProvider.swift */; }; + 314B73CE23DD1C5000139EB3 /* MapProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B73CC23DD1C5000139EB3 /* MapProvider.swift */; }; + 314B73CF23DD1C5000139EB3 /* MapProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B73CC23DD1C5000139EB3 /* MapProvider.swift */; }; + 314B757623DD1DEC00139EB3 /* MapDataPoolInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B757523DD1DEC00139EB3 /* MapDataPoolInteractor.swift */; }; + 314B757723DD1DEC00139EB3 /* MapDataPoolInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B757523DD1DEC00139EB3 /* MapDataPoolInteractor.swift */; }; + 314B757823DD1DEC00139EB3 /* MapDataPoolInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B757523DD1DEC00139EB3 /* MapDataPoolInteractor.swift */; }; + 318A084B24DF1CFA00981B5F /* WebApiRequestHeaderInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318A084A24DF1CFA00981B5F /* WebApiRequestHeaderInjection.swift */; }; + 319682B821B7967B00AE0F28 /* ParticlesKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 319682B621B7967B00AE0F28 /* ParticlesKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 319682C821B7969100AE0F28 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 319682BE21B7967B00AE0F28 /* Utilities.framework */; }; + 319682C921B7969100AE0F28 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 319682C221B7967B00AE0F28 /* RoutingKit.framework */; }; + 31975F7E2680FBAD008EB757 /* ObjectLinesInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31975F7D2680FBAD008EB757 /* ObjectLinesInteractor.swift */; }; + 31975F7F2680FBAD008EB757 /* ObjectLinesInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31975F7D2680FBAD008EB757 /* ObjectLinesInteractor.swift */; }; + 31975F802680FBAD008EB757 /* ObjectLinesInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31975F7D2680FBAD008EB757 /* ObjectLinesInteractor.swift */; }; + 319BB7422429298200DC9E97 /* XibFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319BB73C2429298200DC9E97 /* XibFileLoader.swift */; }; + 319BB7432429298200DC9E97 /* XibFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319BB73C2429298200DC9E97 /* XibFileLoader.swift */; }; + 319BB7442429298200DC9E97 /* XibPresenterCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319BB7412429298200DC9E97 /* XibPresenterCache.swift */; }; + 319BB7452429298200DC9E97 /* XibPresenterCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319BB7412429298200DC9E97 /* XibPresenterCache.swift */; }; + 31AA4B302704F4BC00451307 /* GraphingProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AA4B2F2704F4BC00451307 /* GraphingProtocols.swift */; }; + 31ACB70C21B0ED5100391ADF /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB70421B0ED3F00391ADF /* RoutingKit.framework */; }; + 31ACB70D21B0ED5600391ADF /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB6FB21B0ED3A00391ADF /* Utilities.framework */; }; + 31C016ED2735E530004B300E /* SequentialDataPoolInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31C016EC2735E530004B300E /* SequentialDataPoolInteractor.swift */; }; + 31E8C49125F2B6A000F21CED /* WebSocketApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E8C49025F2B6A000F21CED /* WebSocketApi.swift */; }; + 31E8C49225F2B6A000F21CED /* WebSocketApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E8C49025F2B6A000F21CED /* WebSocketApi.swift */; }; + 31E8C49325F2B6A000F21CED /* WebSocketApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E8C49025F2B6A000F21CED /* WebSocketApi.swift */; }; + 43610BEC16BD2BC8D491E1B1 /* Pods_iOS_ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F5A39C4D74D1558E7AC8B3D /* Pods_iOS_ParticlesKit.framework */; }; + F34126D2B87AD51FA888323B /* Pods_iOS_ParticlesKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60D1A77CC9AD706005601A80 /* Pods_iOS_ParticlesKitTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 311C0FE521B0E9C4001775BA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311C0FD121B0E9C4001775BA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 311C0FD921B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 313EB16B21BA246A00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311C0FD121B0E9C4001775BA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 313EB16021BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 313EB17B21BA246B00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6F521B0ED3A00391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 313EB17F21BA246B00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6F521B0ED3A00391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 313EB21821BA267E00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6F521B0ED3A00391ADF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313A536421B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 313EB27721BA27F800BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6FE21B0ED3F00391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22321BA26D700BEF926; + remoteInfo = RoutingKitAppleTV; + }; + 313EB27B21BA27F800BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6FE21B0ED3F00391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22B21BA26D800BEF926; + remoteInfo = RoutingKitAppleTVTests; + }; + 313EB27F21BA280100BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6FE21B0ED3F00391ADF /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313EB22221BA26D700BEF926; + remoteInfo = RoutingKitAppleTV; + }; + 319682BD21B7967B00AE0F28 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6F521B0ED3A00391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 319682C121B7967B00AE0F28 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6FE21B0ED3F00391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196825B21B7947600AE0F28; + remoteInfo = RoutingKitAppleWatch; + }; + 319682C421B7968600AE0F28 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6FE21B0ED3F00391ADF /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 3196825A21B7947600AE0F28; + remoteInfo = RoutingKitAppleWatch; + }; + 319682C621B7968600AE0F28 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6F521B0ED3A00391ADF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 3196823621B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 31ACB6FA21B0ED3A00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6F521B0ED3A00391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 31ACB6FC21B0ED3A00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6F521B0ED3A00391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 31ACB70321B0ED3F00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6FE21B0ED3F00391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B01216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 31ACB70521B0ED3F00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6FE21B0ED3F00391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B0A216BCA10008ABEE9; + remoteInfo = RoutingKitTests; + }; + 31ACB70721B0ED4800391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6FE21B0ED3F00391ADF /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65B00216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 31ACB70921B0ED4800391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB6F521B0ED3A00391ADF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 027D5C0B28AB0AE300A5B900 /* ParticilesKitConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticilesKitConfig.swift; sourceTree = ""; }; + 02BCE2622899D64000E7BDCB /* WorkerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkerProtocol.swift; sourceTree = ""; }; + 02EE8FD928270CB000225B56 /* BaseInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseInteractor.swift; sourceTree = ""; }; + 0F5A39C4D74D1558E7AC8B3D /* Pods_iOS_ParticlesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_ParticlesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2A672EF94927348861EE35DA /* Pods-iOS-ParticlesKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-ParticlesKitTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-ParticlesKitTests/Pods-iOS-ParticlesKitTests.debug.xcconfig"; sourceTree = ""; }; + 3101F98125112E5600AC4010 /* AuthLoginAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthLoginAction.swift; sourceTree = ""; }; + 3101F98525112E7300AC4010 /* AuthLogoutAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthLogoutAction.swift; sourceTree = ""; }; + 3101F98A25112EBE00AC4010 /* LocatorService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocatorService.swift; sourceTree = ""; }; + 3101F99125112F2500AC4010 /* Login.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Login.xib; sourceTree = ""; }; + 3101F99225112F2600AC4010 /* Logout.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Logout.xib; sourceTree = ""; }; + 310C4F732533B57700DF1D62 /* StreamApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamApi.swift; sourceTree = ""; }; + 311C0FDA21B0E9C4001775BA /* ParticlesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParticlesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 311C0FDD21B0E9C4001775BA /* ParticlesKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ParticlesKit.h; sourceTree = ""; }; + 311C0FDE21B0E9C4001775BA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 311C0FE321B0E9C4001775BA /* ParticlesKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ParticlesKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 311C0FE821B0E9C4001775BA /* ParticlesKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesKitTests.swift; sourceTree = ""; }; + 311C0FEA21B0E9C4001775BA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 312CE30A2630867E00C519C0 /* JsonKeychainCaching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonKeychainCaching.swift; sourceTree = ""; }; + 312F933A2530ED37005874B6 /* KeyedDataInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedDataInteractor.swift; sourceTree = ""; }; + 31308B6A24FAEE67003B5B9A /* PlacemarkProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlacemarkProtocols.swift; sourceTree = ""; }; + 313EB16121BA246A00BEF926 /* ParticlesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParticlesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EB16321BA246A00BEF926 /* ParticlesKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ParticlesKit.h; sourceTree = ""; }; + 313EB16421BA246A00BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EB16921BA246A00BEF926 /* ParticlesKitAppleTVTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ParticlesKitAppleTVTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EB16E21BA246B00BEF926 /* ParticlesKitAppleTVTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesKitAppleTVTests.swift; sourceTree = ""; }; + 313EB17021BA246B00BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31439A142672B1A4003871A1 /* ObjectLineInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectLineInteractor.swift; sourceTree = ""; }; + 31439A182672B23B003871A1 /* ObjectValueLineInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectValueLineInteractor.swift; sourceTree = ""; }; + 31439A2F2672BCDC003871A1 /* FormatterProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormatterProtocols.swift; sourceTree = ""; }; + 31439A332672BD28003871A1 /* DollarValueFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DollarValueFormatter.swift; sourceTree = ""; }; + 31439A372672BEB3003871A1 /* AmountValueFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmountValueFormatter.swift; sourceTree = ""; }; + 31439A3B2672C0AE003871A1 /* PercentValueFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PercentValueFormatter.swift; sourceTree = ""; }; + 31439A572673DAC6003871A1 /* ObjectOptionLineInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectOptionLineInteractor.swift; sourceTree = ""; }; + 31439A7926741B6A003871A1 /* LeverageValueFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeverageValueFormatter.swift; sourceTree = ""; }; + 31439A7F2677F53D003871A1 /* ObjectCompositeLineInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCompositeLineInteractor.swift; sourceTree = ""; }; + 31471FA524BA34E600057221 /* DictionaryInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryInteractor.swift; sourceTree = ""; }; + 31471FAA24BA353B00057221 /* UrlBadgingInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlBadgingInteractor.swift; sourceTree = ""; }; + 314B60B323DCCE5F00139EB3 /* SavedSearchesProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SavedSearchesProtocol.swift; sourceTree = ""; }; + 314B60B423DCCE5F00139EB3 /* LikedObjectsProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LikedObjectsProtocol.swift; sourceTree = ""; }; + 314B60B523DCCE5F00139EB3 /* IOProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IOProtocol.swift; sourceTree = ""; }; + 314B60B723DCCE5F00139EB3 /* JsonCachingProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonCachingProtocol.swift; sourceTree = ""; }; + 314B60B823DCCE5F00139EB3 /* JsonDocumentFileAsyncCaching.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonDocumentFileAsyncCaching.swift; sourceTree = ""; }; + 314B60B923DCCE5F00139EB3 /* JsonDocumentFileCaching.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonDocumentFileCaching.swift; sourceTree = ""; }; + 314B60C023DCCE5F00139EB3 /* AppInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppInfo.swift; sourceTree = ""; }; + 314B60C323DCCE6000139EB3 /* ParallelWebApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParallelWebApi.swift; sourceTree = ""; }; + 314B60C423DCCE6000139EB3 /* HttpApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpApi.swift; sourceTree = ""; }; + 314B60C523DCCE6000139EB3 /* WebApiInjectionProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebApiInjectionProtocols.swift; sourceTree = ""; }; + 314B60C623DCCE6000139EB3 /* WebApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebApi.swift; sourceTree = ""; }; + 314B60C823DCCE6000139EB3 /* PagedWebApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagedWebApi.swift; sourceTree = ""; }; + 314B60C923DCCE6000139EB3 /* ApiProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiProtocol.swift; sourceTree = ""; }; + 314B60CB23DCCE6000139EB3 /* UploadService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadService.swift; sourceTree = ""; }; + 314B60CC23DCCE6000139EB3 /* UploadApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadApi.swift; sourceTree = ""; }; + 314B60CE23DCCE6000139EB3 /* EndpointResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EndpointResolver.swift; sourceTree = ""; }; + 314B60CF23DCCE6000139EB3 /* JsonEndpointResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonEndpointResolver.swift; sourceTree = ""; }; + 314B60D523DCCE6000139EB3 /* GridPresenterManagerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridPresenterManagerProtocol.swift; sourceTree = ""; }; + 314B60D623DCCE6000139EB3 /* GridPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridPresenter.swift; sourceTree = ""; }; + 314B60D723DCCE6000139EB3 /* GridObjectPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridObjectPresenter.swift; sourceTree = ""; }; + 314B60D923DCCE6000139EB3 /* XibRegisterProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibRegisterProtocol.swift; sourceTree = ""; }; + 314B60DA23DCCE6000139EB3 /* XibProviderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibProviderProtocol.swift; sourceTree = ""; }; + 314B60DD23DCCE6000139EB3 /* SelectionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectionHandler.swift; sourceTree = ""; }; + 314B60E023DCCE6000139EB3 /* ObjectPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectPresenter.swift; sourceTree = ""; }; + 314B60E223DCCE6000139EB3 /* ListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListPresenter.swift; sourceTree = ""; }; + 314B60E323DCCE6000139EB3 /* ListObjectPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListObjectPresenter.swift; sourceTree = ""; }; + 314B60E423DCCE6000139EB3 /* ListPresenterManagerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListPresenterManagerProtocol.swift; sourceTree = ""; }; + 314B60E823DCCE6000139EB3 /* XibAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibAction.swift; sourceTree = ""; }; + 314B60E923DCCE6000139EB3 /* FilterEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterEntity.swift; sourceTree = ""; }; + 314B60EA23DCCE6000139EB3 /* SavedSearchEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SavedSearchEntity.swift; sourceTree = ""; }; + 314B60EC23DCCE6000139EB3 /* DictionaryEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DictionaryEntity.swift; sourceTree = ""; }; + 314B60ED23DCCE6000139EB3 /* TimeCounter+Particle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TimeCounter+Particle.swift"; sourceTree = ""; }; + 314B60EF23DCCE6000139EB3 /* ModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelProtocol.swift; sourceTree = ""; }; + 314B60F123DCCE6000139EB3 /* Loader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Loader.swift; sourceTree = ""; }; + 314B60F223DCCE6000139EB3 /* LoaderProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoaderProvider.swift; sourceTree = ""; }; + 314B60F323DCCE6000139EB3 /* LoaderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoaderProtocol.swift; sourceTree = ""; }; + 314B60F623DCCE6000139EB3 /* CompositeErrorLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompositeErrorLogging.swift; sourceTree = ""; }; + 314B60F723DCCE6000139EB3 /* DebugErrorLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugErrorLogging.swift; sourceTree = ""; }; + 314B60F823DCCE6000139EB3 /* ErrorLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorLogging.swift; sourceTree = ""; }; + 314B60FA23DCCE6000139EB3 /* ApiReplayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiReplayer.swift; sourceTree = ""; }; + 314B60FD23DCCE6000139EB3 /* DateService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateService.swift; sourceTree = ""; }; + 314B610423DCCE6000139EB3 /* JsonCredentialsProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonCredentialsProvider.swift; sourceTree = ""; }; + 314B610623DCCE6000139EB3 /* ParticlesInjection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticlesInjection.swift; sourceTree = ""; }; + 314B610823DCCE6100139EB3 /* LocalJsonCacheInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalJsonCacheInteractor.swift; sourceTree = ""; }; + 314B610A23DCCE6100139EB3 /* FilteredListInteractorProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilteredListInteractorProtocol.swift; sourceTree = ""; }; + 314B610C23DCCE6100139EB3 /* LoadingObjectInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingObjectInteractor.swift; sourceTree = ""; }; + 314B610D23DCCE6100139EB3 /* InteractorProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractorProtocol.swift; sourceTree = ""; }; + 314B610F23DCCE6100139EB3 /* SelectableListInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectableListInteractor.swift; sourceTree = ""; }; + 314B611023DCCE6100139EB3 /* ListInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListInteractor.swift; sourceTree = ""; }; + 314B611123DCCE6100139EB3 /* XibJsonFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibJsonFile.swift; sourceTree = ""; }; + 314B611223DCCE6100139EB3 /* DataListInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataListInteractor.swift; sourceTree = ""; }; + 314B611323DCCE6100139EB3 /* DataPoolInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataPoolInteractor.swift; sourceTree = ""; }; + 314B611523DCCE6100139EB3 /* LocalEntityCacheInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalEntityCacheInteractor.swift; sourceTree = ""; }; + 314B611623DCCE6100139EB3 /* LocalDebugCacheInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalDebugCacheInteractor.swift; sourceTree = ""; }; + 314B611823DCCE6100139EB3 /* LocalMapCacheInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalMapCacheInteractor.swift; sourceTree = ""; }; + 314B611923DCCE6100139EB3 /* LocalFeatureFlagsCacheInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalFeatureFlagsCacheInteractor.swift; sourceTree = ""; }; + 314B611B23DCCE6100139EB3 /* GridInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridInteractor.swift; sourceTree = ""; }; + 314B73CC23DD1C5000139EB3 /* MapProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapProvider.swift; sourceTree = ""; }; + 314B757523DD1DEC00139EB3 /* MapDataPoolInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapDataPoolInteractor.swift; sourceTree = ""; }; + 318A084A24DF1CFA00981B5F /* WebApiRequestHeaderInjection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebApiRequestHeaderInjection.swift; sourceTree = ""; }; + 319682B421B7967B00AE0F28 /* ParticlesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParticlesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 319682B621B7967B00AE0F28 /* ParticlesKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ParticlesKit.h; sourceTree = ""; }; + 319682B721B7967B00AE0F28 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31975F7D2680FBAD008EB757 /* ObjectLinesInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectLinesInteractor.swift; sourceTree = ""; }; + 319BB73C2429298200DC9E97 /* XibFileLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibFileLoader.swift; sourceTree = ""; }; + 319BB7412429298200DC9E97 /* XibPresenterCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibPresenterCache.swift; sourceTree = ""; }; + 31AA4B2F2704F4BC00451307 /* GraphingProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphingProtocols.swift; sourceTree = ""; }; + 31ACB6F521B0ED3A00391ADF /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 31ACB6FE21B0ED3F00391ADF /* RoutingKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RoutingKit.xcodeproj; path = ../RoutingKit/RoutingKit.xcodeproj; sourceTree = ""; }; + 31C016EC2735E530004B300E /* SequentialDataPoolInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SequentialDataPoolInteractor.swift; sourceTree = ""; }; + 31E8C49025F2B6A000F21CED /* WebSocketApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketApi.swift; sourceTree = ""; }; + 60D1A77CC9AD706005601A80 /* Pods_iOS_ParticlesKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_ParticlesKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E42E061D721836FD4F3D1BB1 /* Pods-iOS-ParticlesKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-ParticlesKitTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-ParticlesKitTests/Pods-iOS-ParticlesKitTests.release.xcconfig"; sourceTree = ""; }; + F4958EED19842A183A1F5D89 /* Pods-iOS-ParticlesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-ParticlesKit.debug.xcconfig"; path = "Target Support Files/Pods-iOS-ParticlesKit/Pods-iOS-ParticlesKit.debug.xcconfig"; sourceTree = ""; }; + F8B0D0DBC7474890A91F85F0 /* Pods-iOS-ParticlesKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-ParticlesKit.release.xcconfig"; path = "Target Support Files/Pods-iOS-ParticlesKit/Pods-iOS-ParticlesKit.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 311C0FD721B0E9C4001775BA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31ACB70D21B0ED5600391ADF /* Utilities.framework in Frameworks */, + 31ACB70C21B0ED5100391ADF /* RoutingKit.framework in Frameworks */, + 43610BEC16BD2BC8D491E1B1 /* Pods_iOS_ParticlesKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 311C0FE021B0E9C4001775BA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 311C0FE421B0E9C4001775BA /* ParticlesKit.framework in Frameworks */, + F34126D2B87AD51FA888323B /* Pods_iOS_ParticlesKitTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB15E21BA246A00BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB28121BA282300BEF926 /* RoutingKit.framework in Frameworks */, + 313EB21A21BA268600BEF926 /* Utilities.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB16621BA246A00BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB16A21BA246A00BEF926 /* ParticlesKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 319682B121B7967B00AE0F28 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 319682C821B7969100AE0F28 /* Utilities.framework in Frameworks */, + 319682C921B7969100AE0F28 /* RoutingKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02684F2128BD41860007CEFF /* Dependencies */ = { + isa = PBXGroup; + children = ( + 31ACB6FE21B0ED3F00391ADF /* RoutingKit.xcodeproj */, + 31ACB6F521B0ED3A00391ADF /* Utilities.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 02BCE25D2899D63500E7BDCB /* _Worker */ = { + isa = PBXGroup; + children = ( + 02BCE2622899D64000E7BDCB /* WorkerProtocol.swift */, + ); + path = _Worker; + sourceTree = ""; + }; + 3101F98025112E3800AC4010 /* _Auth */ = { + isa = PBXGroup; + children = ( + 3101F98125112E5600AC4010 /* AuthLoginAction.swift */, + 3101F98525112E7300AC4010 /* AuthLogoutAction.swift */, + ); + path = _Auth; + sourceTree = ""; + }; + 3101F98925112EA700AC4010 /* _Locator */ = { + isa = PBXGroup; + children = ( + 3101F98A25112EBE00AC4010 /* LocatorService.swift */, + ); + path = _Locator; + sourceTree = ""; + }; + 3101F98E25112EE700AC4010 /* Resources */ = { + isa = PBXGroup; + children = ( + 3101F98F25112EF800AC4010 /* Routing */, + ); + path = Resources; + sourceTree = ""; + }; + 3101F98F25112EF800AC4010 /* Routing */ = { + isa = PBXGroup; + children = ( + 3101F99025112EFE00AC4010 /* Xib */, + ); + path = Routing; + sourceTree = ""; + }; + 3101F99025112EFE00AC4010 /* Xib */ = { + isa = PBXGroup; + children = ( + 3101F99125112F2500AC4010 /* Login.xib */, + 3101F99225112F2600AC4010 /* Logout.xib */, + ); + path = Xib; + sourceTree = ""; + }; + 3102D2FF2720C5DD000E7B3A /* _Features */ = { + isa = PBXGroup; + children = ( + ); + path = _Features; + sourceTree = ""; + }; + 310C4F6E2533B55D00DF1D62 /* _Stream */ = { + isa = PBXGroup; + children = ( + 310C4F732533B57700DF1D62 /* StreamApi.swift */, + ); + path = _Stream; + sourceTree = ""; + }; + 311C0FD021B0E9C4001775BA = { + isa = PBXGroup; + children = ( + 02684F2128BD41860007CEFF /* Dependencies */, + 311C0FDC21B0E9C4001775BA /* ParticlesKit */, + 313EB16221BA246A00BEF926 /* ParticlesKitAppleTV */, + 313EB16D21BA246B00BEF926 /* ParticlesKitAppleTVTests */, + 319682B521B7967B00AE0F28 /* ParticlesKitAppleWatch */, + 311C0FE721B0E9C4001775BA /* ParticlesKitTests */, + 311C0FDB21B0E9C4001775BA /* Products */, + 96CDA79CEA1CC47871044D6A /* Pods */, + 3A1D787FE115AAF15B0C0C17 /* Frameworks */, + ); + sourceTree = ""; + }; + 311C0FDB21B0E9C4001775BA /* Products */ = { + isa = PBXGroup; + children = ( + 311C0FDA21B0E9C4001775BA /* ParticlesKit.framework */, + 311C0FE321B0E9C4001775BA /* ParticlesKitTests.xctest */, + 319682B421B7967B00AE0F28 /* ParticlesKit.framework */, + 313EB16121BA246A00BEF926 /* ParticlesKit.framework */, + 313EB16921BA246A00BEF926 /* ParticlesKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 311C0FDC21B0E9C4001775BA /* ParticlesKit */ = { + isa = PBXGroup; + children = ( + 02BCE25D2899D63500E7BDCB /* _Worker */, + 314B60C123DCCE5F00139EB3 /* _Api */, + 3101F98025112E3800AC4010 /* _Auth */, + 31471FA924BA352800057221 /* _Badging */, + 314B60B123DCCE5F00139EB3 /* _Cache */, + 314B60BF23DCCE5F00139EB3 /* _Common */, + 314B610323DCCE6000139EB3 /* _Credentials */, + 314B60E623DCCE6000139EB3 /* _Entity */, + 314B60F423DCCE6000139EB3 /* _Error */, + 31439A2E2672BCC9003871A1 /* _Formatter */, + 314B610723DCCE6100139EB3 /* _Interactor */, + 314B60F023DCCE6000139EB3 /* _Loader */, + 314B73C723DD1C3C00139EB3 /* _Map */, + 314B60D323DCCE6000139EB3 /* _Presenter */, + 314B60F923DCCE6000139EB3 /* _Recorder */, + 314B60FB23DCCE6000139EB3 /* _Services */, + 314B610523DCCE6000139EB3 /* _Shared */, + 3145CCF125F3E5DD00BCCFCA /* _WebSocket */, + 311C0FDE21B0E9C4001775BA /* Info.plist */, + 311C0FDD21B0E9C4001775BA /* ParticlesKit.h */, + 3101F98E25112EE700AC4010 /* Resources */, + 027D5C0B28AB0AE300A5B900 /* ParticilesKitConfig.swift */, + ); + path = ParticlesKit; + sourceTree = ""; + }; + 311C0FE721B0E9C4001775BA /* ParticlesKitTests */ = { + isa = PBXGroup; + children = ( + 311C0FE821B0E9C4001775BA /* ParticlesKitTests.swift */, + 311C0FEA21B0E9C4001775BA /* Info.plist */, + ); + path = ParticlesKitTests; + sourceTree = ""; + }; + 312CE3052630865D00C519C0 /* _Keychain */ = { + isa = PBXGroup; + children = ( + 312CE30A2630867E00C519C0 /* JsonKeychainCaching.swift */, + ); + path = _Keychain; + sourceTree = ""; + }; + 313EB16221BA246A00BEF926 /* ParticlesKitAppleTV */ = { + isa = PBXGroup; + children = ( + 313EB16321BA246A00BEF926 /* ParticlesKit.h */, + 313EB16421BA246A00BEF926 /* Info.plist */, + ); + path = ParticlesKitAppleTV; + sourceTree = ""; + }; + 313EB16D21BA246B00BEF926 /* ParticlesKitAppleTVTests */ = { + isa = PBXGroup; + children = ( + 313EB16E21BA246B00BEF926 /* ParticlesKitAppleTVTests.swift */, + 313EB17021BA246B00BEF926 /* Info.plist */, + ); + path = ParticlesKitAppleTVTests; + sourceTree = ""; + }; + 31439A0F2672B0FC003871A1 /* _Lines */ = { + isa = PBXGroup; + children = ( + 31439A7F2677F53D003871A1 /* ObjectCompositeLineInteractor.swift */, + 31439A142672B1A4003871A1 /* ObjectLineInteractor.swift */, + 31439A572673DAC6003871A1 /* ObjectOptionLineInteractor.swift */, + 31439A182672B23B003871A1 /* ObjectValueLineInteractor.swift */, + 31975F7D2680FBAD008EB757 /* ObjectLinesInteractor.swift */, + ); + path = _Lines; + sourceTree = ""; + }; + 31439A2E2672BCC9003871A1 /* _Formatter */ = { + isa = PBXGroup; + children = ( + 31439A372672BEB3003871A1 /* AmountValueFormatter.swift */, + 31439A332672BD28003871A1 /* DollarValueFormatter.swift */, + 31439A2F2672BCDC003871A1 /* FormatterProtocols.swift */, + 31439A7926741B6A003871A1 /* LeverageValueFormatter.swift */, + 31439A3B2672C0AE003871A1 /* PercentValueFormatter.swift */, + ); + path = _Formatter; + sourceTree = ""; + }; + 3145CCF125F3E5DD00BCCFCA /* _WebSocket */ = { + isa = PBXGroup; + children = ( + 31E8C49025F2B6A000F21CED /* WebSocketApi.swift */, + ); + path = _WebSocket; + sourceTree = ""; + }; + 31471FA024BA34CD00057221 /* _Dictionary */ = { + isa = PBXGroup; + children = ( + 31471FA524BA34E600057221 /* DictionaryInteractor.swift */, + ); + path = _Dictionary; + sourceTree = ""; + }; + 31471FA924BA352800057221 /* _Badging */ = { + isa = PBXGroup; + children = ( + 31471FAA24BA353B00057221 /* UrlBadgingInteractor.swift */, + ); + path = _Badging; + sourceTree = ""; + }; + 314B60B123DCCE5F00139EB3 /* _Cache */ = { + isa = PBXGroup; + children = ( + 3102D2FF2720C5DD000E7B3A /* _Features */, + 314B60B623DCCE5F00139EB3 /* _Json */, + 312CE3052630865D00C519C0 /* _Keychain */, + 314B60B223DCCE5F00139EB3 /* _Protocols */, + 314B60B523DCCE5F00139EB3 /* IOProtocol.swift */, + ); + path = _Cache; + sourceTree = ""; + }; + 314B60B223DCCE5F00139EB3 /* _Protocols */ = { + isa = PBXGroup; + children = ( + 314B60B423DCCE5F00139EB3 /* LikedObjectsProtocol.swift */, + 314B60B323DCCE5F00139EB3 /* SavedSearchesProtocol.swift */, + ); + path = _Protocols; + sourceTree = ""; + }; + 314B60B623DCCE5F00139EB3 /* _Json */ = { + isa = PBXGroup; + children = ( + 314B60B723DCCE5F00139EB3 /* JsonCachingProtocol.swift */, + 314B60B823DCCE5F00139EB3 /* JsonDocumentFileAsyncCaching.swift */, + 314B60B923DCCE5F00139EB3 /* JsonDocumentFileCaching.swift */, + ); + path = _Json; + sourceTree = ""; + }; + 314B60BF23DCCE5F00139EB3 /* _Common */ = { + isa = PBXGroup; + children = ( + 314B60C023DCCE5F00139EB3 /* AppInfo.swift */, + ); + path = _Common; + sourceTree = ""; + }; + 314B60C123DCCE5F00139EB3 /* _Api */ = { + isa = PBXGroup; + children = ( + 314B60CD23DCCE6000139EB3 /* _Endpoint */, + 310C4F6E2533B55D00DF1D62 /* _Stream */, + 314B60CA23DCCE6000139EB3 /* _Upload */, + 314B60C223DCCE6000139EB3 /* _WebApi */, + 314B60C923DCCE6000139EB3 /* ApiProtocol.swift */, + ); + path = _Api; + sourceTree = ""; + }; + 314B60C223DCCE6000139EB3 /* _WebApi */ = { + isa = PBXGroup; + children = ( + 314B60C423DCCE6000139EB3 /* HttpApi.swift */, + 314B60C823DCCE6000139EB3 /* PagedWebApi.swift */, + 314B60C323DCCE6000139EB3 /* ParallelWebApi.swift */, + 314B60C623DCCE6000139EB3 /* WebApi.swift */, + 314B60C523DCCE6000139EB3 /* WebApiInjectionProtocols.swift */, + 318A084A24DF1CFA00981B5F /* WebApiRequestHeaderInjection.swift */, + ); + path = _WebApi; + sourceTree = ""; + }; + 314B60CA23DCCE6000139EB3 /* _Upload */ = { + isa = PBXGroup; + children = ( + 314B60CC23DCCE6000139EB3 /* UploadApi.swift */, + 314B60CB23DCCE6000139EB3 /* UploadService.swift */, + ); + path = _Upload; + sourceTree = ""; + }; + 314B60CD23DCCE6000139EB3 /* _Endpoint */ = { + isa = PBXGroup; + children = ( + 314B60CE23DCCE6000139EB3 /* EndpointResolver.swift */, + 314B60CF23DCCE6000139EB3 /* JsonEndpointResolver.swift */, + ); + path = _Endpoint; + sourceTree = ""; + }; + 314B60D323DCCE6000139EB3 /* _Presenter */ = { + isa = PBXGroup; + children = ( + 314B60D423DCCE6000139EB3 /* _Grid Presenter */, + 314B60E123DCCE6000139EB3 /* _List Presenter */, + 314B60DF23DCCE6000139EB3 /* _Object Presenter */, + 314B60D823DCCE6000139EB3 /* _Protocol */, + 314B60DC23DCCE6000139EB3 /* _Selection */, + 319BB73C2429298200DC9E97 /* XibFileLoader.swift */, + 319BB7412429298200DC9E97 /* XibPresenterCache.swift */, + ); + path = _Presenter; + sourceTree = ""; + }; + 314B60D423DCCE6000139EB3 /* _Grid Presenter */ = { + isa = PBXGroup; + children = ( + 314B60D723DCCE6000139EB3 /* GridObjectPresenter.swift */, + 314B60D623DCCE6000139EB3 /* GridPresenter.swift */, + 314B60D523DCCE6000139EB3 /* GridPresenterManagerProtocol.swift */, + ); + path = "_Grid Presenter"; + sourceTree = ""; + }; + 314B60D823DCCE6000139EB3 /* _Protocol */ = { + isa = PBXGroup; + children = ( + 314B60DA23DCCE6000139EB3 /* XibProviderProtocol.swift */, + 314B60D923DCCE6000139EB3 /* XibRegisterProtocol.swift */, + 31AA4B2F2704F4BC00451307 /* GraphingProtocols.swift */, + ); + path = _Protocol; + sourceTree = ""; + }; + 314B60DC23DCCE6000139EB3 /* _Selection */ = { + isa = PBXGroup; + children = ( + 314B60DD23DCCE6000139EB3 /* SelectionHandler.swift */, + ); + path = _Selection; + sourceTree = ""; + }; + 314B60DF23DCCE6000139EB3 /* _Object Presenter */ = { + isa = PBXGroup; + children = ( + 314B60E023DCCE6000139EB3 /* ObjectPresenter.swift */, + ); + path = "_Object Presenter"; + sourceTree = ""; + }; + 314B60E123DCCE6000139EB3 /* _List Presenter */ = { + isa = PBXGroup; + children = ( + 314B60E323DCCE6000139EB3 /* ListObjectPresenter.swift */, + 314B60E223DCCE6000139EB3 /* ListPresenter.swift */, + 314B60E423DCCE6000139EB3 /* ListPresenterManagerProtocol.swift */, + ); + path = "_List Presenter"; + sourceTree = ""; + }; + 314B60E623DCCE6000139EB3 /* _Entity */ = { + isa = PBXGroup; + children = ( + 314B60E723DCCE6000139EB3 /* _Common */, + 314B60EB23DCCE6000139EB3 /* _Entity */, + 314B60EE23DCCE6000139EB3 /* _Model */, + ); + path = _Entity; + sourceTree = ""; + }; + 314B60E723DCCE6000139EB3 /* _Common */ = { + isa = PBXGroup; + children = ( + 314B60E923DCCE6000139EB3 /* FilterEntity.swift */, + 314B60EA23DCCE6000139EB3 /* SavedSearchEntity.swift */, + 314B60E823DCCE6000139EB3 /* XibAction.swift */, + ); + path = _Common; + sourceTree = ""; + }; + 314B60EB23DCCE6000139EB3 /* _Entity */ = { + isa = PBXGroup; + children = ( + 314B60EC23DCCE6000139EB3 /* DictionaryEntity.swift */, + 314B60ED23DCCE6000139EB3 /* TimeCounter+Particle.swift */, + ); + path = _Entity; + sourceTree = ""; + }; + 314B60EE23DCCE6000139EB3 /* _Model */ = { + isa = PBXGroup; + children = ( + 314B60EF23DCCE6000139EB3 /* ModelProtocol.swift */, + ); + path = _Model; + sourceTree = ""; + }; + 314B60F023DCCE6000139EB3 /* _Loader */ = { + isa = PBXGroup; + children = ( + 314B60F123DCCE6000139EB3 /* Loader.swift */, + 314B60F323DCCE6000139EB3 /* LoaderProtocol.swift */, + 314B60F223DCCE6000139EB3 /* LoaderProvider.swift */, + ); + path = _Loader; + sourceTree = ""; + }; + 314B60F423DCCE6000139EB3 /* _Error */ = { + isa = PBXGroup; + children = ( + 314B60F523DCCE6000139EB3 /* _Log */, + ); + path = _Error; + sourceTree = ""; + }; + 314B60F523DCCE6000139EB3 /* _Log */ = { + isa = PBXGroup; + children = ( + 314B60F623DCCE6000139EB3 /* CompositeErrorLogging.swift */, + 314B60F723DCCE6000139EB3 /* DebugErrorLogging.swift */, + 314B60F823DCCE6000139EB3 /* ErrorLogging.swift */, + ); + path = _Log; + sourceTree = ""; + }; + 314B60F923DCCE6000139EB3 /* _Recorder */ = { + isa = PBXGroup; + children = ( + 314B60FA23DCCE6000139EB3 /* ApiReplayer.swift */, + ); + path = _Recorder; + sourceTree = ""; + }; + 314B60FB23DCCE6000139EB3 /* _Services */ = { + isa = PBXGroup; + children = ( + 314B60FC23DCCE6000139EB3 /* _Calendar */, + 3101F98925112EA700AC4010 /* _Locator */, + 314B60FE23DCCE6000139EB3 /* _Places */, + ); + path = _Services; + sourceTree = ""; + }; + 314B60FC23DCCE6000139EB3 /* _Calendar */ = { + isa = PBXGroup; + children = ( + 314B60FD23DCCE6000139EB3 /* DateService.swift */, + ); + path = _Calendar; + sourceTree = ""; + }; + 314B60FE23DCCE6000139EB3 /* _Places */ = { + isa = PBXGroup; + children = ( + ); + path = _Places; + sourceTree = ""; + }; + 314B610323DCCE6000139EB3 /* _Credentials */ = { + isa = PBXGroup; + children = ( + 314B610423DCCE6000139EB3 /* JsonCredentialsProvider.swift */, + ); + path = _Credentials; + sourceTree = ""; + }; + 314B610523DCCE6000139EB3 /* _Shared */ = { + isa = PBXGroup; + children = ( + 314B610623DCCE6000139EB3 /* ParticlesInjection.swift */, + ); + path = _Shared; + sourceTree = ""; + }; + 314B610723DCCE6100139EB3 /* _Interactor */ = { + isa = PBXGroup; + children = ( + 31471FA024BA34CD00057221 /* _Dictionary */, + 314B611A23DCCE6100139EB3 /* _Grid */, + 31439A0F2672B0FC003871A1 /* _Lines */, + 314B610E23DCCE6100139EB3 /* _List */, + 314B611423DCCE6100139EB3 /* _LocalData */, + 314B610B23DCCE6100139EB3 /* _Object */, + 314B610923DCCE6100139EB3 /* _Protocol */, + 314B610823DCCE6100139EB3 /* LocalJsonCacheInteractor.swift */, + 02EE8FD928270CB000225B56 /* BaseInteractor.swift */, + ); + path = _Interactor; + sourceTree = ""; + }; + 314B610923DCCE6100139EB3 /* _Protocol */ = { + isa = PBXGroup; + children = ( + 314B610A23DCCE6100139EB3 /* FilteredListInteractorProtocol.swift */, + ); + path = _Protocol; + sourceTree = ""; + }; + 314B610B23DCCE6100139EB3 /* _Object */ = { + isa = PBXGroup; + children = ( + 314B610D23DCCE6100139EB3 /* InteractorProtocol.swift */, + 314B610C23DCCE6100139EB3 /* LoadingObjectInteractor.swift */, + ); + path = _Object; + sourceTree = ""; + }; + 314B610E23DCCE6100139EB3 /* _List */ = { + isa = PBXGroup; + children = ( + 314B611223DCCE6100139EB3 /* DataListInteractor.swift */, + 314B611323DCCE6100139EB3 /* DataPoolInteractor.swift */, + 312F933A2530ED37005874B6 /* KeyedDataInteractor.swift */, + 314B611023DCCE6100139EB3 /* ListInteractor.swift */, + 314B757523DD1DEC00139EB3 /* MapDataPoolInteractor.swift */, + 314B610F23DCCE6100139EB3 /* SelectableListInteractor.swift */, + 31C016EC2735E530004B300E /* SequentialDataPoolInteractor.swift */, + 314B611123DCCE6100139EB3 /* XibJsonFile.swift */, + ); + path = _List; + sourceTree = ""; + }; + 314B611423DCCE6100139EB3 /* _LocalData */ = { + isa = PBXGroup; + children = ( + 314B611623DCCE6100139EB3 /* LocalDebugCacheInteractor.swift */, + 314B611523DCCE6100139EB3 /* LocalEntityCacheInteractor.swift */, + 314B611923DCCE6100139EB3 /* LocalFeatureFlagsCacheInteractor.swift */, + 314B611823DCCE6100139EB3 /* LocalMapCacheInteractor.swift */, + ); + path = _LocalData; + sourceTree = ""; + }; + 314B611A23DCCE6100139EB3 /* _Grid */ = { + isa = PBXGroup; + children = ( + 314B611B23DCCE6100139EB3 /* GridInteractor.swift */, + ); + path = _Grid; + sourceTree = ""; + }; + 314B73C723DD1C3C00139EB3 /* _Map */ = { + isa = PBXGroup; + children = ( + 314B73CC23DD1C5000139EB3 /* MapProvider.swift */, + 31308B6A24FAEE67003B5B9A /* PlacemarkProtocols.swift */, + ); + path = _Map; + sourceTree = ""; + }; + 319682B521B7967B00AE0F28 /* ParticlesKitAppleWatch */ = { + isa = PBXGroup; + children = ( + 319682B621B7967B00AE0F28 /* ParticlesKit.h */, + 319682B721B7967B00AE0F28 /* Info.plist */, + ); + path = ParticlesKitAppleWatch; + sourceTree = ""; + }; + 31ACB6F621B0ED3A00391ADF /* Products */ = { + isa = PBXGroup; + children = ( + 31ACB6FB21B0ED3A00391ADF /* Utilities.framework */, + 319682BE21B7967B00AE0F28 /* Utilities.framework */, + 313EB17C21BA246B00BEF926 /* Utilities.framework */, + 31ACB6FD21B0ED3A00391ADF /* UtilitiesTests.xctest */, + 313EB18021BA246B00BEF926 /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31ACB6FF21B0ED3F00391ADF /* Products */ = { + isa = PBXGroup; + children = ( + 31ACB70421B0ED3F00391ADF /* RoutingKit.framework */, + 319682C221B7967B00AE0F28 /* RoutingKit.framework */, + 313EB27821BA27F800BEF926 /* RoutingKit.framework */, + 31ACB70621B0ED3F00391ADF /* RoutingKitTests.xctest */, + 313EB27C21BA27F800BEF926 /* RoutingKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 3A1D787FE115AAF15B0C0C17 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0F5A39C4D74D1558E7AC8B3D /* Pods_iOS_ParticlesKit.framework */, + 60D1A77CC9AD706005601A80 /* Pods_iOS_ParticlesKitTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 96CDA79CEA1CC47871044D6A /* Pods */ = { + isa = PBXGroup; + children = ( + F4958EED19842A183A1F5D89 /* Pods-iOS-ParticlesKit.debug.xcconfig */, + F8B0D0DBC7474890A91F85F0 /* Pods-iOS-ParticlesKit.release.xcconfig */, + 2A672EF94927348861EE35DA /* Pods-iOS-ParticlesKitTests.debug.xcconfig */, + E42E061D721836FD4F3D1BB1 /* Pods-iOS-ParticlesKitTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 311C0FD521B0E9C4001775BA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 311C0FEB21B0E9C4001775BA /* ParticlesKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB15C21BA246A00BEF926 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB17121BA246B00BEF926 /* ParticlesKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 319682AF21B7967B00AE0F28 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 319682B821B7967B00AE0F28 /* ParticlesKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 311C0FD921B0E9C4001775BA /* ParticlesKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 311C0FEE21B0E9C4001775BA /* Build configuration list for PBXNativeTarget "ParticlesKit" */; + buildPhases = ( + 951D57A7D97EE5EA27F6AA50 /* [CP] Check Pods Manifest.lock */, + 311C0FD521B0E9C4001775BA /* Headers */, + 311C0FD621B0E9C4001775BA /* Sources */, + 311C0FD721B0E9C4001775BA /* Frameworks */, + 311C0FD821B0E9C4001775BA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 31ACB70821B0ED4800391ADF /* PBXTargetDependency */, + 31ACB70A21B0ED4800391ADF /* PBXTargetDependency */, + ); + name = ParticlesKit; + productName = ParticlesKit; + productReference = 311C0FDA21B0E9C4001775BA /* ParticlesKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 311C0FE221B0E9C4001775BA /* ParticlesKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 311C0FF121B0E9C4001775BA /* Build configuration list for PBXNativeTarget "ParticlesKitTests" */; + buildPhases = ( + 468DD600B99D16F585C15085 /* [CP] Check Pods Manifest.lock */, + 311C0FDF21B0E9C4001775BA /* Sources */, + 311C0FE021B0E9C4001775BA /* Frameworks */, + 311C0FE121B0E9C4001775BA /* Resources */, + F2EB2AC14AD2688FE5159478 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 311C0FE621B0E9C4001775BA /* PBXTargetDependency */, + ); + name = ParticlesKitTests; + productName = ParticlesKitTests; + productReference = 311C0FE321B0E9C4001775BA /* ParticlesKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 313EB16021BA246A00BEF926 /* ParticlesKitAppleTV */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EB18421BA246B00BEF926 /* Build configuration list for PBXNativeTarget "ParticlesKitAppleTV" */; + buildPhases = ( + 313EB15C21BA246A00BEF926 /* Headers */, + 313EB15D21BA246A00BEF926 /* Sources */, + 313EB15E21BA246A00BEF926 /* Frameworks */, + 313EB15F21BA246A00BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EB28021BA280100BEF926 /* PBXTargetDependency */, + 313EB21921BA267E00BEF926 /* PBXTargetDependency */, + ); + name = ParticlesKitAppleTV; + productName = ParticlesKitAppleTV; + productReference = 313EB16121BA246A00BEF926 /* ParticlesKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 313EB16821BA246A00BEF926 /* ParticlesKitAppleTVTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EB18521BA246B00BEF926 /* Build configuration list for PBXNativeTarget "ParticlesKitAppleTVTests" */; + buildPhases = ( + 313EB16521BA246A00BEF926 /* Sources */, + 313EB16621BA246A00BEF926 /* Frameworks */, + 313EB16721BA246A00BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EB16C21BA246A00BEF926 /* PBXTargetDependency */, + ); + name = ParticlesKitAppleTVTests; + productName = ParticlesKitAppleTVTests; + productReference = 313EB16921BA246A00BEF926 /* ParticlesKitAppleTVTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 319682B321B7967B00AE0F28 /* ParticlesKitAppleWatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 319682C321B7967B00AE0F28 /* Build configuration list for PBXNativeTarget "ParticlesKitAppleWatch" */; + buildPhases = ( + 319682AF21B7967B00AE0F28 /* Headers */, + 319682B021B7967B00AE0F28 /* Sources */, + 319682B121B7967B00AE0F28 /* Frameworks */, + 319682B221B7967B00AE0F28 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 319682C521B7968600AE0F28 /* PBXTargetDependency */, + 319682C721B7968600AE0F28 /* PBXTargetDependency */, + ); + name = ParticlesKitAppleWatch; + productName = ParticlesKitAppleWatch; + productReference = 319682B421B7967B00AE0F28 /* ParticlesKit.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 311C0FD121B0E9C4001775BA /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "dYdX Trading Inc"; + TargetAttributes = { + 311C0FD921B0E9C4001775BA = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1020; + }; + 311C0FE221B0E9C4001775BA = { + CreatedOnToolsVersion = 10.1; + }; + 313EB16021BA246A00BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 313EB16821BA246A00BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 319682B321B7967B00AE0F28 = { + CreatedOnToolsVersion = 10.1; + }; + }; + }; + buildConfigurationList = 311C0FD421B0E9C4001775BA /* Build configuration list for PBXProject "ParticlesKit" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 311C0FD021B0E9C4001775BA; + productRefGroup = 311C0FDB21B0E9C4001775BA /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 31ACB6FF21B0ED3F00391ADF /* Products */; + ProjectRef = 31ACB6FE21B0ED3F00391ADF /* RoutingKit.xcodeproj */; + }, + { + ProductGroup = 31ACB6F621B0ED3A00391ADF /* Products */; + ProjectRef = 31ACB6F521B0ED3A00391ADF /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 311C0FD921B0E9C4001775BA /* ParticlesKit */, + 319682B321B7967B00AE0F28 /* ParticlesKitAppleWatch */, + 313EB16021BA246A00BEF926 /* ParticlesKitAppleTV */, + 311C0FE221B0E9C4001775BA /* ParticlesKitTests */, + 313EB16821BA246A00BEF926 /* ParticlesKitAppleTVTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 313EB17C21BA246B00BEF926 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 313EB17B21BA246B00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB18021BA246B00BEF926 /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 313EB17F21BA246B00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB27821BA27F800BEF926 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 313EB27721BA27F800BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB27C21BA27F800BEF926 /* RoutingKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitAppleTVTests.xctest; + remoteRef = 313EB27B21BA27F800BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319682BE21B7967B00AE0F28 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 319682BD21B7967B00AE0F28 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 319682C221B7967B00AE0F28 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 319682C121B7967B00AE0F28 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB6FB21B0ED3A00391ADF /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 31ACB6FA21B0ED3A00391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB6FD21B0ED3A00391ADF /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 31ACB6FC21B0ED3A00391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB70421B0ED3F00391ADF /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 31ACB70321B0ED3F00391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB70621B0ED3F00391ADF /* RoutingKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitTests.xctest; + remoteRef = 31ACB70521B0ED3F00391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 311C0FD821B0E9C4001775BA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3101F99325112F2600AC4010 /* Login.xib in Resources */, + 3101F99425112F2600AC4010 /* Logout.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 311C0FE121B0E9C4001775BA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB15F21BA246A00BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB16721BA246A00BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 319682B221B7967B00AE0F28 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 468DD600B99D16F585C15085 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-ParticlesKitTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 951D57A7D97EE5EA27F6AA50 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-ParticlesKit-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + F2EB2AC14AD2688FE5159478 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-ParticlesKitTests/Pods-iOS-ParticlesKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-ParticlesKitTests/Pods-iOS-ParticlesKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-ParticlesKitTests/Pods-iOS-ParticlesKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 311C0FD621B0E9C4001775BA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31308B6B24FAEE67003B5B9A /* PlacemarkProtocols.swift in Sources */, + 314B61F123DCCE6200139EB3 /* JsonCredentialsProvider.swift in Sources */, + 314B61E123DCCE6200139EB3 /* ApiReplayer.swift in Sources */, + 314B617D23DCCE6100139EB3 /* GridPresenterManagerProtocol.swift in Sources */, + 314B614523DCCE6100139EB3 /* AppInfo.swift in Sources */, + 314B614D23DCCE6100139EB3 /* HttpApi.swift in Sources */, + 3101F98625112E7300AC4010 /* AuthLogoutAction.swift in Sources */, + 314B614923DCCE6100139EB3 /* ParallelWebApi.swift in Sources */, + 314B613123DCCE6100139EB3 /* JsonDocumentFileAsyncCaching.swift in Sources */, + 314B616923DCCE6100139EB3 /* UploadApi.swift in Sources */, + 31439A342672BD28003871A1 /* DollarValueFormatter.swift in Sources */, + 314B618923DCCE6100139EB3 /* XibRegisterProtocol.swift in Sources */, + 314B615D23DCCE6100139EB3 /* PagedWebApi.swift in Sources */, + 314B618123DCCE6100139EB3 /* GridPresenter.swift in Sources */, + 314B612523DCCE6100139EB3 /* LikedObjectsProtocol.swift in Sources */, + 314B61BD23DCCE6200139EB3 /* DictionaryEntity.swift in Sources */, + 314B61DD23DCCE6200139EB3 /* ErrorLogging.swift in Sources */, + 314B620523DCCE6200139EB3 /* InteractorProtocol.swift in Sources */, + 314B618D23DCCE6100139EB3 /* XibProviderProtocol.swift in Sources */, + 314B623123DCCE6200139EB3 /* GridInteractor.swift in Sources */, + 314B61C123DCCE6200139EB3 /* TimeCounter+Particle.swift in Sources */, + 314B61D123DCCE6200139EB3 /* LoaderProtocol.swift in Sources */, + 3101F98B25112EBE00AC4010 /* LocatorService.swift in Sources */, + 314B620123DCCE6200139EB3 /* LoadingObjectInteractor.swift in Sources */, + 314B616523DCCE6100139EB3 /* UploadService.swift in Sources */, + 314B622D23DCCE6200139EB3 /* LocalFeatureFlagsCacheInteractor.swift in Sources */, + 314B612D23DCCE6100139EB3 /* JsonCachingProtocol.swift in Sources */, + 318A084B24DF1CFA00981B5F /* WebApiRequestHeaderInjection.swift in Sources */, + 314B61D523DCCE6200139EB3 /* CompositeErrorLogging.swift in Sources */, + 314B621123DCCE6200139EB3 /* XibJsonFile.swift in Sources */, + 319BB7422429298200DC9E97 /* XibFileLoader.swift in Sources */, + 319BB7442429298200DC9E97 /* XibPresenterCache.swift in Sources */, + 31439A582673DAC6003871A1 /* ObjectOptionLineInteractor.swift in Sources */, + 314B61C523DCCE6200139EB3 /* ModelProtocol.swift in Sources */, + 31AA4B302704F4BC00451307 /* GraphingProtocols.swift in Sources */, + 31439A192672B23B003871A1 /* ObjectValueLineInteractor.swift in Sources */, + 314B621523DCCE6200139EB3 /* DataListInteractor.swift in Sources */, + 314B620D23DCCE6200139EB3 /* ListInteractor.swift in Sources */, + 02BCE2632899D64000E7BDCB /* WorkerProtocol.swift in Sources */, + 314B61F923DCCE6200139EB3 /* LocalJsonCacheInteractor.swift in Sources */, + 314B61A123DCCE6100139EB3 /* ListPresenter.swift in Sources */, + 027D5C0C28AB0AE300A5B900 /* ParticilesKitConfig.swift in Sources */, + 314B618523DCCE6100139EB3 /* GridObjectPresenter.swift in Sources */, + 314B621D23DCCE6200139EB3 /* LocalEntityCacheInteractor.swift in Sources */, + 314B61E523DCCE6200139EB3 /* DateService.swift in Sources */, + 314B616D23DCCE6100139EB3 /* EndpointResolver.swift in Sources */, + 31471FAB24BA353B00057221 /* UrlBadgingInteractor.swift in Sources */, + 314B622123DCCE6200139EB3 /* LocalDebugCacheInteractor.swift in Sources */, + 312CE30B2630867E00C519C0 /* JsonKeychainCaching.swift in Sources */, + 31975F7E2680FBAD008EB757 /* ObjectLinesInteractor.swift in Sources */, + 31471FA624BA34E600057221 /* DictionaryInteractor.swift in Sources */, + 31439A382672BEB3003871A1 /* AmountValueFormatter.swift in Sources */, + 314B612123DCCE6100139EB3 /* SavedSearchesProtocol.swift in Sources */, + 314B613523DCCE6100139EB3 /* JsonDocumentFileCaching.swift in Sources */, + 314B757623DD1DEC00139EB3 /* MapDataPoolInteractor.swift in Sources */, + 314B61A523DCCE6100139EB3 /* ListObjectPresenter.swift in Sources */, + 31439A302672BCDC003871A1 /* FormatterProtocols.swift in Sources */, + 314B621923DCCE6200139EB3 /* DataPoolInteractor.swift in Sources */, + 314B617123DCCE6100139EB3 /* JsonEndpointResolver.swift in Sources */, + 314B61A923DCCE6100139EB3 /* ListPresenterManagerProtocol.swift in Sources */, + 31439A3C2672C0AE003871A1 /* PercentValueFormatter.swift in Sources */, + 314B61CD23DCCE6200139EB3 /* LoaderProvider.swift in Sources */, + 312F933B2530ED37005874B6 /* KeyedDataInteractor.swift in Sources */, + 314B619523DCCE6100139EB3 /* SelectionHandler.swift in Sources */, + 314B73CD23DD1C5000139EB3 /* MapProvider.swift in Sources */, + 314B61B123DCCE6100139EB3 /* XibAction.swift in Sources */, + 314B61B523DCCE6100139EB3 /* FilterEntity.swift in Sources */, + 314B61F523DCCE6200139EB3 /* ParticlesInjection.swift in Sources */, + 314B620923DCCE6200139EB3 /* SelectableListInteractor.swift in Sources */, + 314B622923DCCE6200139EB3 /* LocalMapCacheInteractor.swift in Sources */, + 314B61B923DCCE6100139EB3 /* SavedSearchEntity.swift in Sources */, + 31C016ED2735E530004B300E /* SequentialDataPoolInteractor.swift in Sources */, + 314B615523DCCE6100139EB3 /* WebApi.swift in Sources */, + 31439A152672B1A4003871A1 /* ObjectLineInteractor.swift in Sources */, + 314B616123DCCE6100139EB3 /* ApiProtocol.swift in Sources */, + 31439A802677F53D003871A1 /* ObjectCompositeLineInteractor.swift in Sources */, + 31E8C49125F2B6A000F21CED /* WebSocketApi.swift in Sources */, + 310C4F742533B57700DF1D62 /* StreamApi.swift in Sources */, + 3101F98225112E5600AC4010 /* AuthLoginAction.swift in Sources */, + 314B615123DCCE6100139EB3 /* WebApiInjectionProtocols.swift in Sources */, + 02EE8FDA28270CB000225B56 /* BaseInteractor.swift in Sources */, + 314B612923DCCE6100139EB3 /* IOProtocol.swift in Sources */, + 31439A7A26741B6A003871A1 /* LeverageValueFormatter.swift in Sources */, + 314B619D23DCCE6100139EB3 /* ObjectPresenter.swift in Sources */, + 314B61C923DCCE6200139EB3 /* Loader.swift in Sources */, + 314B61D923DCCE6200139EB3 /* DebugErrorLogging.swift in Sources */, + 314B61FD23DCCE6200139EB3 /* FilteredListInteractorProtocol.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 311C0FDF21B0E9C4001775BA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 311C0FE921B0E9C4001775BA /* ParticlesKitTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB15D21BA246A00BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3101F98425112E5600AC4010 /* AuthLoginAction.swift in Sources */, + 31439A822677F53D003871A1 /* ObjectCompositeLineInteractor.swift in Sources */, + 31439A172672B1A4003871A1 /* ObjectLineInteractor.swift in Sources */, + 314B61F323DCCE6200139EB3 /* JsonCredentialsProvider.swift in Sources */, + 314B61E323DCCE6200139EB3 /* ApiReplayer.swift in Sources */, + 314B617F23DCCE6100139EB3 /* GridPresenterManagerProtocol.swift in Sources */, + 314B614723DCCE6100139EB3 /* AppInfo.swift in Sources */, + 314B614F23DCCE6100139EB3 /* HttpApi.swift in Sources */, + 314B614B23DCCE6100139EB3 /* ParallelWebApi.swift in Sources */, + 314B613323DCCE6100139EB3 /* JsonDocumentFileAsyncCaching.swift in Sources */, + 314B616B23DCCE6100139EB3 /* UploadApi.swift in Sources */, + 314B618B23DCCE6100139EB3 /* XibRegisterProtocol.swift in Sources */, + 314B615F23DCCE6100139EB3 /* PagedWebApi.swift in Sources */, + 31439A5A2673DAC6003871A1 /* ObjectOptionLineInteractor.swift in Sources */, + 314B618323DCCE6100139EB3 /* GridPresenter.swift in Sources */, + 31439A362672BD28003871A1 /* DollarValueFormatter.swift in Sources */, + 314B612723DCCE6100139EB3 /* LikedObjectsProtocol.swift in Sources */, + 314B61BF23DCCE6200139EB3 /* DictionaryEntity.swift in Sources */, + 31439A3E2672C0AE003871A1 /* PercentValueFormatter.swift in Sources */, + 31439A3A2672BEB3003871A1 /* AmountValueFormatter.swift in Sources */, + 314B61DF23DCCE6200139EB3 /* ErrorLogging.swift in Sources */, + 314B620723DCCE6200139EB3 /* InteractorProtocol.swift in Sources */, + 314B618F23DCCE6100139EB3 /* XibProviderProtocol.swift in Sources */, + 314B623323DCCE6200139EB3 /* GridInteractor.swift in Sources */, + 314B61C323DCCE6200139EB3 /* TimeCounter+Particle.swift in Sources */, + 314B61D323DCCE6200139EB3 /* LoaderProtocol.swift in Sources */, + 314B620323DCCE6200139EB3 /* LoadingObjectInteractor.swift in Sources */, + 314B616723DCCE6100139EB3 /* UploadService.swift in Sources */, + 314B622F23DCCE6200139EB3 /* LocalFeatureFlagsCacheInteractor.swift in Sources */, + 314B612F23DCCE6100139EB3 /* JsonCachingProtocol.swift in Sources */, + 314B61D723DCCE6200139EB3 /* CompositeErrorLogging.swift in Sources */, + 314B621323DCCE6200139EB3 /* XibJsonFile.swift in Sources */, + 319BB7432429298200DC9E97 /* XibFileLoader.swift in Sources */, + 319BB7452429298200DC9E97 /* XibPresenterCache.swift in Sources */, + 314B61C723DCCE6200139EB3 /* ModelProtocol.swift in Sources */, + 31E8C49325F2B6A000F21CED /* WebSocketApi.swift in Sources */, + 314B621723DCCE6200139EB3 /* DataListInteractor.swift in Sources */, + 314B620F23DCCE6200139EB3 /* ListInteractor.swift in Sources */, + 314B61FB23DCCE6200139EB3 /* LocalJsonCacheInteractor.swift in Sources */, + 314B61A323DCCE6100139EB3 /* ListPresenter.swift in Sources */, + 314B618723DCCE6100139EB3 /* GridObjectPresenter.swift in Sources */, + 31439A1B2672B23B003871A1 /* ObjectValueLineInteractor.swift in Sources */, + 314B621F23DCCE6200139EB3 /* LocalEntityCacheInteractor.swift in Sources */, + 31439A7C26741B6A003871A1 /* LeverageValueFormatter.swift in Sources */, + 314B61E723DCCE6200139EB3 /* DateService.swift in Sources */, + 314B616F23DCCE6100139EB3 /* EndpointResolver.swift in Sources */, + 31471FAD24BA353B00057221 /* UrlBadgingInteractor.swift in Sources */, + 314B622323DCCE6200139EB3 /* LocalDebugCacheInteractor.swift in Sources */, + 31471FA824BA34E600057221 /* DictionaryInteractor.swift in Sources */, + 314B612323DCCE6100139EB3 /* SavedSearchesProtocol.swift in Sources */, + 314B613723DCCE6100139EB3 /* JsonDocumentFileCaching.swift in Sources */, + 314B757823DD1DEC00139EB3 /* MapDataPoolInteractor.swift in Sources */, + 31308B6D24FAEE67003B5B9A /* PlacemarkProtocols.swift in Sources */, + 314B61A723DCCE6100139EB3 /* ListObjectPresenter.swift in Sources */, + 314B621B23DCCE6200139EB3 /* DataPoolInteractor.swift in Sources */, + 314B617323DCCE6100139EB3 /* JsonEndpointResolver.swift in Sources */, + 314B61AB23DCCE6100139EB3 /* ListPresenterManagerProtocol.swift in Sources */, + 31439A322672BCDC003871A1 /* FormatterProtocols.swift in Sources */, + 314B61CF23DCCE6200139EB3 /* LoaderProvider.swift in Sources */, + 314B619723DCCE6100139EB3 /* SelectionHandler.swift in Sources */, + 3101F98D25112EBE00AC4010 /* LocatorService.swift in Sources */, + 314B73CF23DD1C5000139EB3 /* MapProvider.swift in Sources */, + 314B61B323DCCE6100139EB3 /* XibAction.swift in Sources */, + 314B61B723DCCE6100139EB3 /* FilterEntity.swift in Sources */, + 314B61F723DCCE6200139EB3 /* ParticlesInjection.swift in Sources */, + 314B620B23DCCE6200139EB3 /* SelectableListInteractor.swift in Sources */, + 314B622B23DCCE6200139EB3 /* LocalMapCacheInteractor.swift in Sources */, + 314B61BB23DCCE6100139EB3 /* SavedSearchEntity.swift in Sources */, + 3101F98825112E7300AC4010 /* AuthLogoutAction.swift in Sources */, + 314B615723DCCE6100139EB3 /* WebApi.swift in Sources */, + 314B616323DCCE6100139EB3 /* ApiProtocol.swift in Sources */, + 312F933D2530ED37005874B6 /* KeyedDataInteractor.swift in Sources */, + 314B615323DCCE6100139EB3 /* WebApiInjectionProtocols.swift in Sources */, + 314B612B23DCCE6100139EB3 /* IOProtocol.swift in Sources */, + 31975F802680FBAD008EB757 /* ObjectLinesInteractor.swift in Sources */, + 310C4F762533B57700DF1D62 /* StreamApi.swift in Sources */, + 314B619F23DCCE6100139EB3 /* ObjectPresenter.swift in Sources */, + 314B61CB23DCCE6200139EB3 /* Loader.swift in Sources */, + 314B61DB23DCCE6200139EB3 /* DebugErrorLogging.swift in Sources */, + 312CE30D2630867E00C519C0 /* JsonKeychainCaching.swift in Sources */, + 314B61FF23DCCE6200139EB3 /* FilteredListInteractorProtocol.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB16521BA246A00BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB16F21BA246B00BEF926 /* ParticlesKitAppleTVTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 319682B021B7967B00AE0F28 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B61F223DCCE6200139EB3 /* JsonCredentialsProvider.swift in Sources */, + 31308B6C24FAEE67003B5B9A /* PlacemarkProtocols.swift in Sources */, + 314B61E223DCCE6200139EB3 /* ApiReplayer.swift in Sources */, + 312F933C2530ED37005874B6 /* KeyedDataInteractor.swift in Sources */, + 314B617E23DCCE6100139EB3 /* GridPresenterManagerProtocol.swift in Sources */, + 314B614623DCCE6100139EB3 /* AppInfo.swift in Sources */, + 314B614E23DCCE6100139EB3 /* HttpApi.swift in Sources */, + 3101F98325112E5600AC4010 /* AuthLoginAction.swift in Sources */, + 31439A312672BCDC003871A1 /* FormatterProtocols.swift in Sources */, + 314B614A23DCCE6100139EB3 /* ParallelWebApi.swift in Sources */, + 31439A392672BEB3003871A1 /* AmountValueFormatter.swift in Sources */, + 314B613223DCCE6100139EB3 /* JsonDocumentFileAsyncCaching.swift in Sources */, + 314B616A23DCCE6100139EB3 /* UploadApi.swift in Sources */, + 31471FAC24BA353B00057221 /* UrlBadgingInteractor.swift in Sources */, + 314B618A23DCCE6100139EB3 /* XibRegisterProtocol.swift in Sources */, + 314B615E23DCCE6100139EB3 /* PagedWebApi.swift in Sources */, + 314B618223DCCE6100139EB3 /* GridPresenter.swift in Sources */, + 3101F98C25112EBE00AC4010 /* LocatorService.swift in Sources */, + 31439A592673DAC6003871A1 /* ObjectOptionLineInteractor.swift in Sources */, + 314B612623DCCE6100139EB3 /* LikedObjectsProtocol.swift in Sources */, + 314B61BE23DCCE6200139EB3 /* DictionaryEntity.swift in Sources */, + 31439A352672BD28003871A1 /* DollarValueFormatter.swift in Sources */, + 31439A3D2672C0AE003871A1 /* PercentValueFormatter.swift in Sources */, + 31975F7F2680FBAD008EB757 /* ObjectLinesInteractor.swift in Sources */, + 314B61DE23DCCE6200139EB3 /* ErrorLogging.swift in Sources */, + 31439A812677F53D003871A1 /* ObjectCompositeLineInteractor.swift in Sources */, + 314B620623DCCE6200139EB3 /* InteractorProtocol.swift in Sources */, + 314B618E23DCCE6100139EB3 /* XibProviderProtocol.swift in Sources */, + 314B623223DCCE6200139EB3 /* GridInteractor.swift in Sources */, + 314B61C223DCCE6200139EB3 /* TimeCounter+Particle.swift in Sources */, + 314B61D223DCCE6200139EB3 /* LoaderProtocol.swift in Sources */, + 314B620223DCCE6200139EB3 /* LoadingObjectInteractor.swift in Sources */, + 31439A1A2672B23B003871A1 /* ObjectValueLineInteractor.swift in Sources */, + 314B616623DCCE6100139EB3 /* UploadService.swift in Sources */, + 314B622E23DCCE6200139EB3 /* LocalFeatureFlagsCacheInteractor.swift in Sources */, + 314B612E23DCCE6100139EB3 /* JsonCachingProtocol.swift in Sources */, + 314B61D623DCCE6200139EB3 /* CompositeErrorLogging.swift in Sources */, + 314B621223DCCE6200139EB3 /* XibJsonFile.swift in Sources */, + 31439A162672B1A4003871A1 /* ObjectLineInteractor.swift in Sources */, + 314B61C623DCCE6200139EB3 /* ModelProtocol.swift in Sources */, + 314B621623DCCE6200139EB3 /* DataListInteractor.swift in Sources */, + 314B620E23DCCE6200139EB3 /* ListInteractor.swift in Sources */, + 314B61FA23DCCE6200139EB3 /* LocalJsonCacheInteractor.swift in Sources */, + 3101F98725112E7300AC4010 /* AuthLogoutAction.swift in Sources */, + 314B61A223DCCE6100139EB3 /* ListPresenter.swift in Sources */, + 314B618623DCCE6100139EB3 /* GridObjectPresenter.swift in Sources */, + 314B621E23DCCE6200139EB3 /* LocalEntityCacheInteractor.swift in Sources */, + 31439A7B26741B6A003871A1 /* LeverageValueFormatter.swift in Sources */, + 314B61E623DCCE6200139EB3 /* DateService.swift in Sources */, + 31E8C49225F2B6A000F21CED /* WebSocketApi.swift in Sources */, + 314B616E23DCCE6100139EB3 /* EndpointResolver.swift in Sources */, + 314B622223DCCE6200139EB3 /* LocalDebugCacheInteractor.swift in Sources */, + 314B612223DCCE6100139EB3 /* SavedSearchesProtocol.swift in Sources */, + 314B613623DCCE6100139EB3 /* JsonDocumentFileCaching.swift in Sources */, + 314B757723DD1DEC00139EB3 /* MapDataPoolInteractor.swift in Sources */, + 314B61A623DCCE6100139EB3 /* ListObjectPresenter.swift in Sources */, + 31471FA724BA34E600057221 /* DictionaryInteractor.swift in Sources */, + 314B621A23DCCE6200139EB3 /* DataPoolInteractor.swift in Sources */, + 314B617223DCCE6100139EB3 /* JsonEndpointResolver.swift in Sources */, + 314B61AA23DCCE6100139EB3 /* ListPresenterManagerProtocol.swift in Sources */, + 312CE30C2630867E00C519C0 /* JsonKeychainCaching.swift in Sources */, + 314B61CE23DCCE6200139EB3 /* LoaderProvider.swift in Sources */, + 310C4F752533B57700DF1D62 /* StreamApi.swift in Sources */, + 314B619623DCCE6100139EB3 /* SelectionHandler.swift in Sources */, + 314B73CE23DD1C5000139EB3 /* MapProvider.swift in Sources */, + 314B61B223DCCE6100139EB3 /* XibAction.swift in Sources */, + 314B61B623DCCE6100139EB3 /* FilterEntity.swift in Sources */, + 314B61F623DCCE6200139EB3 /* ParticlesInjection.swift in Sources */, + 314B620A23DCCE6200139EB3 /* SelectableListInteractor.swift in Sources */, + 314B622A23DCCE6200139EB3 /* LocalMapCacheInteractor.swift in Sources */, + 314B61BA23DCCE6100139EB3 /* SavedSearchEntity.swift in Sources */, + 314B615623DCCE6100139EB3 /* WebApi.swift in Sources */, + 314B616223DCCE6100139EB3 /* ApiProtocol.swift in Sources */, + 314B615223DCCE6100139EB3 /* WebApiInjectionProtocols.swift in Sources */, + 314B612A23DCCE6100139EB3 /* IOProtocol.swift in Sources */, + 314B619E23DCCE6100139EB3 /* ObjectPresenter.swift in Sources */, + 314B61CA23DCCE6200139EB3 /* Loader.swift in Sources */, + 314B61DA23DCCE6200139EB3 /* DebugErrorLogging.swift in Sources */, + 314B61FE23DCCE6200139EB3 /* FilteredListInteractorProtocol.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 311C0FE621B0E9C4001775BA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 311C0FD921B0E9C4001775BA /* ParticlesKit */; + targetProxy = 311C0FE521B0E9C4001775BA /* PBXContainerItemProxy */; + }; + 313EB16C21BA246A00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 313EB16021BA246A00BEF926 /* ParticlesKitAppleTV */; + targetProxy = 313EB16B21BA246A00BEF926 /* PBXContainerItemProxy */; + }; + 313EB21921BA267E00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleTV; + targetProxy = 313EB21821BA267E00BEF926 /* PBXContainerItemProxy */; + }; + 313EB28021BA280100BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKitAppleTV; + targetProxy = 313EB27F21BA280100BEF926 /* PBXContainerItemProxy */; + }; + 319682C521B7968600AE0F28 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKitAppleWatch; + targetProxy = 319682C421B7968600AE0F28 /* PBXContainerItemProxy */; + }; + 319682C721B7968600AE0F28 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleWatch; + targetProxy = 319682C621B7968600AE0F28 /* PBXContainerItemProxy */; + }; + 31ACB70821B0ED4800391ADF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKit; + targetProxy = 31ACB70721B0ED4800391ADF /* PBXContainerItemProxy */; + }; + 31ACB70A21B0ED4800391ADF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 31ACB70921B0ED4800391ADF /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 311C0FEC21B0E9C4001775BA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 311C0FED21B0E9C4001775BA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 311C0FEF21B0E9C4001775BA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F4958EED19842A183A1F5D89 /* Pods-iOS-ParticlesKit.debug.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ParticlesKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.ParticlesKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _iOS"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 311C0FF021B0E9C4001775BA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F8B0D0DBC7474890A91F85F0 /* Pods-iOS-ParticlesKit.release.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ParticlesKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.ParticlesKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _iOS"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 311C0FF221B0E9C4001775BA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2A672EF94927348861EE35DA /* Pods-iOS-ParticlesKitTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = ParticlesKitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 311C0FF321B0E9C4001775BA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E42E061D721836FD4F3D1BB1 /* Pods-iOS-ParticlesKitTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = ParticlesKitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 313EB17221BA246B00BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ParticlesKitAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesKitAppleTV; + PRODUCT_NAME = ParticlesKit; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _tvOS"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313EB17321BA246B00BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ParticlesKitAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesKitAppleTV; + PRODUCT_NAME = ParticlesKit; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _tvOS"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 313EB17421BA246B00BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = ParticlesKitAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesKitAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313EB17521BA246B00BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = ParticlesKitAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesKitAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 319682B921B7967B00AE0F28 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ParticlesKitAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesKitAppleWatch; + PRODUCT_NAME = ParticlesKit; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _watchOS"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Debug; + }; + 319682BA21B7967B00AE0F28 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = ParticlesKitAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.ParticlesKitAppleWatch; + PRODUCT_NAME = ParticlesKit; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _watchOS"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 311C0FD421B0E9C4001775BA /* Build configuration list for PBXProject "ParticlesKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 311C0FEC21B0E9C4001775BA /* Debug */, + 311C0FED21B0E9C4001775BA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 311C0FEE21B0E9C4001775BA /* Build configuration list for PBXNativeTarget "ParticlesKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 311C0FEF21B0E9C4001775BA /* Debug */, + 311C0FF021B0E9C4001775BA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 311C0FF121B0E9C4001775BA /* Build configuration list for PBXNativeTarget "ParticlesKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 311C0FF221B0E9C4001775BA /* Debug */, + 311C0FF321B0E9C4001775BA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313EB18421BA246B00BEF926 /* Build configuration list for PBXNativeTarget "ParticlesKitAppleTV" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EB17221BA246B00BEF926 /* Debug */, + 313EB17321BA246B00BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313EB18521BA246B00BEF926 /* Build configuration list for PBXNativeTarget "ParticlesKitAppleTVTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EB17421BA246B00BEF926 /* Debug */, + 313EB17521BA246B00BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 319682C321B7967B00AE0F28 /* Build configuration list for PBXNativeTarget "ParticlesKitAppleWatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 319682B921B7967B00AE0F28 /* Debug */, + 319682BA21B7967B00AE0F28 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 311C0FD121B0E9C4001775BA /* Project object */; +} diff --git a/ParticlesKit/ParticlesKit.xcodeproj/xcshareddata/xcschemes/ParticlesKit.xcscheme b/ParticlesKit/ParticlesKit.xcodeproj/xcshareddata/xcschemes/ParticlesKit.xcscheme new file mode 100644 index 000000000..37d9617be --- /dev/null +++ b/ParticlesKit/ParticlesKit.xcodeproj/xcshareddata/xcschemes/ParticlesKit.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ParticlesKit/ParticlesKit.xcodeproj/xcshareddata/xcschemes/ParticlesKitTests.xcscheme b/ParticlesKit/ParticlesKit.xcodeproj/xcshareddata/xcschemes/ParticlesKitTests.xcscheme new file mode 100644 index 000000000..62af6259c --- /dev/null +++ b/ParticlesKit/ParticlesKit.xcodeproj/xcshareddata/xcschemes/ParticlesKitTests.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/ParticlesKit/ParticlesKit/Info.plist b/ParticlesKit/ParticlesKit/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/ParticlesKit/ParticlesKit/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/ParticlesKit/ParticlesKit/ParticilesKitConfig.swift b/ParticlesKit/ParticlesKit/ParticilesKitConfig.swift new file mode 100644 index 000000000..a67f4a1f1 --- /dev/null +++ b/ParticlesKit/ParticlesKit/ParticilesKitConfig.swift @@ -0,0 +1,13 @@ +// +// ParticilesKitConfig.swift +// ParticlesKit +// +// Created by Rui Huang on 8/15/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public struct ParticilesKitConfig { + public static var xibJson = "xib.json" +} diff --git a/ParticlesKit/ParticlesKit/ParticlesKit.h b/ParticlesKit/ParticlesKit/ParticlesKit.h new file mode 100644 index 000000000..cd05eeebf --- /dev/null +++ b/ParticlesKit/ParticlesKit/ParticlesKit.h @@ -0,0 +1,17 @@ +// +// ParticlesKit.h +// ParticlesKit +// +// Created by Qiang Huang on 11/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for ParticlesKit. +FOUNDATION_EXPORT double ParticlesKitVersionNumber; + +//! Project version string for ParticlesKit. +FOUNDATION_EXPORT const unsigned char ParticlesKitVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/ParticlesKit/ParticlesKit/Resources/Routing/Xib/Login.xib b/ParticlesKit/ParticlesKit/Resources/Routing/Xib/Login.xib new file mode 100644 index 000000000..4b496a36c --- /dev/null +++ b/ParticlesKit/ParticlesKit/Resources/Routing/Xib/Login.xib @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/ParticlesKit/ParticlesKit/Resources/Routing/Xib/Logout.xib b/ParticlesKit/ParticlesKit/Resources/Routing/Xib/Logout.xib new file mode 100644 index 000000000..ec3a28085 --- /dev/null +++ b/ParticlesKit/ParticlesKit/Resources/Routing/Xib/Logout.xib @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/ParticlesKit/ParticlesKit/_Api/ApiProtocol.swift b/ParticlesKit/ParticlesKit/_Api/ApiProtocol.swift new file mode 100644 index 000000000..a43c8a46e --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Api/ApiProtocol.swift @@ -0,0 +1,58 @@ +// +// ApiProtocol.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/26/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public typealias ApiCompletionHandler = (_ data: Any?, _ error: Error?) -> Void +// for upload or download +public typealias ApiProgressHandler = (_ progress: Float) -> Void + +public protocol ApiProtocol: IOProtocol { + func get(path: String, params: [String: Any]?, completion: @escaping ApiCompletionHandler) + func post(path: String, params: [String: Any]?, data: Any?, completion: @escaping ApiCompletionHandler) + func put(path: String, params: [String: Any]?, data: Any?, completion: @escaping ApiCompletionHandler) + func delete(path: String, params: [String: Any]?, completion: @escaping ApiCompletionHandler) +} + +extension ApiProtocol { + public func load(path: String, params: [String: Any]?, completion: @escaping IOReadCompletionHandler) { + get(path: path, params: params) { [weak self] data, error in + completion(data, nil, self?.priority ?? 10, error) + } + } + + public func save(path: String, params: [String: Any]?, data: Any?, completion: IOWriteCompletionHandler?) { + post(path: path, params: params, data: data) { data, error in + completion?(data, error) + } + } + + public func modify(path: String, params: [String: Any]?, data: Any?, completion: IOWriteCompletionHandler?) { + put(path: path, params: params, data: data) { data, error in + completion?(data, error) + } + } + + public func delete(path: String, params: [String: Any]?, completion: IODeleteCompletionHandler?) { + delete(path: path, params: params) { _, error in + completion?(error) + } + } +} + +extension ApiProtocol { + // implement later + public func post(path: String, params: [String: Any]?, data: Any?, completion: @escaping ApiCompletionHandler) { + } + + public func put(path: String, params: [String: Any]?, data: Any?, completion: @escaping ApiCompletionHandler) { + } + + public func delete(path: String, params: [String: Any]?, completion: @escaping ApiCompletionHandler) { + } +} diff --git a/ParticlesKit/ParticlesKit/_Api/_Endpoint/EndpointResolver.swift b/ParticlesKit/ParticlesKit/_Api/_Endpoint/EndpointResolver.swift new file mode 100644 index 000000000..308bf0c03 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Api/_Endpoint/EndpointResolver.swift @@ -0,0 +1,14 @@ +// +// EndpointResolver.swift +// ParticlesKit +// +// Created by Qiang Huang on 7/15/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public protocol EndpointResolverProtocol { + var host: String? { get } + func path(for action: String) -> String? +} diff --git a/ParticlesKit/ParticlesKit/_Api/_Endpoint/JsonEndpointResolver.swift b/ParticlesKit/ParticlesKit/_Api/_Endpoint/JsonEndpointResolver.swift new file mode 100644 index 000000000..420a37929 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Api/_Endpoint/JsonEndpointResolver.swift @@ -0,0 +1,43 @@ +// +// JsonEndpointResolver.swift +// ParticlesKit +// +// Created by Qiang Huang on 7/15/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +open class JsonEndpointResolver: NSObject & EndpointResolverProtocol { + public static var parserOverwrite: Parser? + + override open var parser: Parser { + return JsonEndpointResolver.parserOverwrite ?? super.parser + } + + private var entity: DictionaryEntity? + + public var host: String? { + if let host = parser.asString(parser.asDictionary(entity?.data)?["host"]) { + if let custom = parser.asString(DebugSettings.shared?.debug?[host]) { + return custom + } else { + return host + } + } + return nil + } + + public func path(for action: String) -> String? { + return parser.asString(parser.asDictionary(parser.asDictionary(entity?.data)?["path"])?[action]) + } + + public init(json: String) { + super.init() + + if let destinations = JsonLoader.load(bundles: Bundle.particles, fileName: json) as? [String: Any] { + entity = DictionaryEntity() + entity?.parse(dictionary: destinations) + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Api/_Stream/StreamApi.swift b/ParticlesKit/ParticlesKit/_Api/_Stream/StreamApi.swift new file mode 100644 index 000000000..f9f6b34fd --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Api/_Stream/StreamApi.swift @@ -0,0 +1,195 @@ +// +// StreamApi.swift +// ParticlesKit +// +// Created by Qiang Huang on 10/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Utilities +import Combine + +public typealias StreamingReadFunction = (_ messages: [String]) -> Void +public typealias DisconnectFuction = (_ error: Error?) -> Void + +@objc open class StreamApi: NSObject, CombineObserving { + public var cancellableMap = [AnyKeyPath : AnyCancellable]() + + private var appState: AppState? { + didSet { + changeObservation(from: oldValue, to: appState, keyPath: #keyPath(AppState.background)) { [weak self] _, _, _, animated in + self?.background = self?.appState?.background ?? false + } + } + } + + private var server: String? + private var port: Int? + private var reading: StreamingReadFunction? + private var disconnecting: DisconnectFuction? + + public lazy var session: URLSession = { + let config = URLSessionConfiguration.default + config.isDiscretionary = false + config.shouldUseExtendedBackgroundIdleMode = true + return URLSession(configuration: config, delegate: self, delegateQueue: nil) + }() + + @objc open dynamic var background: Bool = false { + willSet { + if background != newValue { + if newValue { + beginBackgroundTask() + } else { + endBackgroundTask() + } + } + } + } + + #if _iOS || _tvOS + private var backgroundTaskId: UIBackgroundTaskIdentifier = .invalid + #endif + + @objc open dynamic var task: URLSessionStreamTask? { + didSet { + if task !== oldValue { + isConnected = (task != nil) + if task != nil { + read() + } + } + } + } + + @objc public dynamic var isConnected: Bool = false + private var leftover: String? + + + override public init() { + super.init() + DispatchQueue.main.async {[weak self] in + self?.background = self?.appState?.background ?? false + } + } + + public init(server: String?, port: Int) { + super.init() + self.server = server + self.port = port + DispatchQueue.main.async {[weak self] in + self?.background = self?.appState?.background ?? false + } + } + + public func connect(server: String, port: Int, reading: StreamingReadFunction?, disconnecting: DisconnectFuction?) { + self.server = server + self.port = port + connect(reading: reading, disconnecting: disconnecting) + } + + open func connect(reading: StreamingReadFunction?, disconnecting: DisconnectFuction?) { + self.reading = reading + self.disconnecting = disconnecting + + if let server = server, let port = port { + let task = session.streamTask(withHostName: server, port: port) + task.resume() + self.task = task + } else { + task = nil + } + } + + open func disconnect() { + task?.closeWrite() + task?.closeRead() + task = nil + } + + private func read() { + task?.readData(ofMinLength: 0, maxLength: 16000, timeout: 0) { [weak self] data, _, _ in + if let self = self { + DispatchQueue.main.async { [weak self] in + if let self = self { + if let data = data, let message = String(data: data, encoding: .utf8) { + #if DEBUG + let starting = message.prefix(32) + Console.shared.log("Edge:Start: \(starting)") + let ending = message.suffix(32) + Console.shared.log("Edge:End: \(ending)") + #endif + let lines = message.components(separatedBy: "\r\n") + var messages = [String]() + for index in 0 ..< lines.count { + let line = lines[index] + if index == 0 { + if let leftover = self.leftover { + messages.append("\(leftover)\(line)") + self.leftover = nil + } else { + messages.append(String(line)) + } + } else if index == lines.count - 1 { + self.leftover = String(line) + } else { + messages.append(String(line)) + } + } + self.reading?(messages) + } + self.read() + } + } + } + } + } + + open func send(message: String) { + task?.write(message.data(using: .utf8)!, timeout: 0) { [weak self] error in + if let error = error { + DispatchQueue.main.async { [weak self] in + self?.disconnecting?(error) + } + Console.shared.log("Failed to send: \(String(describing: error))") + } else { + Console.shared.log("Sent!") + } + } + } + + private func beginBackgroundTask() { + #if _iOS || _tvOS + if backgroundTaskId == .invalid { + backgroundTaskId = UIApplication.shared.beginBackgroundTask { [weak self] in + if let self = self { + self.task?.suspend() +// self.task?.closeRead() +// self.task?.closeWrite() +// self.task = nil + self.endBackgroundTask() + } + } + } + #endif + } + + private func endBackgroundTask() { + #if _iOS || _tvOS + if backgroundTaskId != .invalid { + UIApplication.shared.endBackgroundTask(backgroundTaskId) + backgroundTaskId = .invalid + task?.resume() + } + #endif + } +} + +extension StreamApi: URLSessionDelegate { + public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { + DispatchQueue.main.async { [weak self] in + self?.disconnect() + self?.disconnecting?(error) + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Api/_Upload/UploadApi.swift b/ParticlesKit/ParticlesKit/_Api/_Upload/UploadApi.swift new file mode 100644 index 000000000..6568927bf --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Api/_Upload/UploadApi.swift @@ -0,0 +1,219 @@ +// +// UploadApi.swift +// ParticlesKit +// +// Created by Qiang Huang on 8/13/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +@objc open class UploadApi: HttpApi { + private var session: URLSession? + private var task: URLSessionUploadTask? { + didSet { + if task !== oldValue { + task?.resume() + } + } + } + + private var file: String? { + didSet { + uploadFile = multipart(file: file) + } + } + + private var responseData: [String: Data] = [:] + private var completion: ApiCompletionHandler? + private var progress: ApiProgressHandler? + + open var imageTag: String = "input_image" + + private var uploadFile: String? { + didSet { + if uploadFile != oldValue { + if let oldValue = oldValue { + File.delete(oldValue) + } + } + } + } + + deinit { + file = nil + } + + open func upload(path: String, identifier: String, params: [String: Any]?, body: [String: Any]?, file: String?, completion: @escaping ApiCompletionHandler, progress: ApiProgressHandler?) { + let className = String(describing: type(of: self)) + if let server = server { +// let config = URLSessionConfiguration.background(withIdentifier: identifier) + let config = URLSessionConfiguration.default + session = URLSession(configuration: config, delegate: self, delegateQueue: nil) + + let pathAndParams = url(server: server, path: path, params: params) + self.file = file + if let uploadFile = uploadFile, let url = url(path: pathAndParams.urlPath, params: pathAndParams.paramStrings) { + request(url: url, body: body) { [weak self] request in + if let self = self { + if let request = request { + let fileUrl = URL(fileURLWithPath: uploadFile) + self.completion = completion + self.progress = progress + self.task = self.session?.uploadTask(with: request, fromFile: fileUrl) + } else { + let error = NSError(domain: "\(className).request", code: 0, userInfo: nil) + ErrorLogging.shared?.log(error) + completion(nil, error) + } + } + } + } else { + let error = NSError(domain: "\(className).file", code: 0, userInfo: nil) + ErrorLogging.shared?.log(error) + completion(nil, error) + } + } else { + let error = NSError(domain: "\(className).server", code: 0, userInfo: nil) + ErrorLogging.shared?.log(error) + completion(nil, error) + } + } + + public func request(url: URL, body: Any?, completion: @escaping (_ request: URLRequest?) -> Void) { + var fileData = Data() + let boundary = UUID().uuidString + if let file = file, let uploadFile = uploadFile, let separator = "\r\n--\(boundary)\r\n".data(using: .utf8), let eof = "\r\n--\(boundary)--\r\n".data(using: .utf8) { + let fileUrl = URL(fileURLWithPath: file) + if let data = try? Data(contentsOf: fileUrl) { + if let body = body as? [String: Any] { + for (key, value) in body { + if let keyText = "Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8), let valueText = "\(value)".data(using: .utf8) { + fileData.append(separator) + fileData.append(keyText) + fileData.append(valueText) + } + } + } + fileData.append(separator) + fileData.append("Content-Disposition: form-data; name=\"\(imageTag)\"; filename=\"\(file.lastPathComponent)\"\r\n".data(using: .utf8)!) + if let contentType = "Content-Type: image/jpeg\r\n\r\n".data(using: .utf8) { + fileData.append(contentType) + } + fileData.append(data) + fileData.append(eof) + + try? fileData.write(to: URL(fileURLWithPath: uploadFile)) + + let verb: HttpVerb = .post + var request: URLRequest = URLRequest(url: url) + request.httpMethod = verb.rawValue + request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData + inject(request: request, verb: verb, index: 0) { /* [weak self] */ request in + var request = request + request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + completion(request) + } + } else { + completion(nil) + } + } else { + completion(nil) + } + } + + private func multipart(file: String?) -> String? { + if let file = file, let tempFolder = Directory.documentFolder("temp") { + let fileName = file.lastPathComponent + _ = Directory.ensure(tempFolder) + return tempFolder.stringByAppendingPathComponent(path: fileName) + } + return nil + } +} + +extension UploadApi: URLSessionDelegate { +} + +extension UploadApi: URLSessionDataDelegate { + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + var persist = responseData["\(dataTask.taskIdentifier)"] ?? Data() + persist.append(data) + responseData["\(dataTask.taskIdentifier)"] = persist + } +} + +extension UploadApi: URLSessionTaskDelegate { + public func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) { + task.cancel() + DispatchQueue.main.async { [weak self] in + if let self = self { + self.task = nil + let className = String(describing: type(of: self)) + let error = NSError(domain: "\(className).connection", code: 0, userInfo: nil) + self.completion?(nil, error) + } + } + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + DispatchQueue.main.async { [weak self] in + ErrorLogging.shared?.log(error) + if let self = self, let response = task.response { + self.file = nil + self.status = nil + + let raw = self.responseData["\(task.taskIdentifier)"] + let data = (raw != nil) ? try? JSONSerialization.jsonObject(with: raw!, options: []) : nil + if let responseInjections = self.responseInjections { + for responseInjection in responseInjections { + responseInjection.inject(response: response, data: data, verb: .post) + } + } + if let data = data as? [String: Any] { + Console.shared.log("Payload:\(data)\n") + let success = self.parser.asBoolean(data["success"])?.boolValue ?? false + if success { + self.completion?(data, error) + } else { + if let error = error { + self.completion?(nil, error) + } else { + let className = String(describing: type(of: self)) + let error = NSError(domain: "\(className).response.fail", code: 0, userInfo: data) + ErrorLogging.shared?.log(error) + self.completion?(nil, error) + } + } + } else { + if let error = error { + self.completion?(nil, error) + } else { + let className = String(describing: type(of: self)) + let error = NSError(domain: "\(className).response.invalid", code: 0, userInfo: nil) + ErrorLogging.shared?.log(error) + self.completion?(nil, error) + } + } + } else { + self?.completion?(nil, error) + } + } + } + + public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { + DispatchQueue.main.async { [weak self] in + if let self = self { + self.file = nil + ErrorLogging.shared?.log(error) + self.completion?(nil, error) + } + } + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { + DispatchQueue.main.async { [weak self] in + self?.progress?(Float(totalBytesSent) / Float(totalBytesExpectedToSend)) + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Api/_Upload/UploadService.swift b/ParticlesKit/ParticlesKit/_Api/_Upload/UploadService.swift new file mode 100644 index 000000000..881cd1162 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Api/_Upload/UploadService.swift @@ -0,0 +1,29 @@ +// +// UploadService.swift +// ParticlesKit +// +// Created by Qiang Huang on 8/13/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +@objc open class UploadService: NSObject, ProgressProtocol { + public static var shared: UploadService? + + @objc open dynamic var uploadApi: UploadApi? + + @objc public dynamic var started: Bool = false + + @objc public dynamic var error: Error? + + @objc public dynamic var progress: Float = 0.0 + + @objc public dynamic var text: String? + + open func upload() { + } + + open func upload(object: Any?) { + } +} diff --git a/ParticlesKit/ParticlesKit/_Api/_WebApi/HttpApi.swift b/ParticlesKit/ParticlesKit/_Api/_WebApi/HttpApi.swift new file mode 100644 index 000000000..b7787ccbf --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Api/_WebApi/HttpApi.swift @@ -0,0 +1,165 @@ +// +// HttpApi.swift +// ParticlesKit +// +// Created by Qiang Huang on 8/13/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities +import Combine + +public enum HttpVerb: String { + case get = "GET" + case post = "POST" + case put = "PUT" + case delete = "DELETE" +} + +@objc open class HttpApi: NSObject, CombineObserving { + public var cancellableMap = [AnyKeyPath : AnyCancellable]() + + private var appState: AppState? { + didSet { + changeObservation(from: oldValue, to: appState, keyPath: #keyPath(AppState.background)) {[weak self] observer, obj, change, animated in + self?.background = self?.appState?.background ?? false + } + } + } + + @objc public dynamic var background: Bool = false + public var endpointResolver: EndpointResolverProtocol? + public var server: String? + public var status: LoadingStatus? { + didSet { + if status !== oldValue { +// oldValue?.minus() +// status?.plus() + } + } + } + + public var requestInjections: [WebApiRequestInjectionProtocol]? + public var responseInjections: [WebApiResponseInjectionProtocol]? + + override public init() { + super.init() + DispatchQueue.main.async { [weak self] in + self?.appState = AppState.shared + } + } + + public init(server: String?) { + super.init() + self.server = server + DispatchQueue.main.async { [weak self] in + self?.appState = AppState.shared + } + } + + public init(endpointResolver: EndpointResolverProtocol?) { + super.init() + self.endpointResolver = endpointResolver + server = endpointResolver?.host + DispatchQueue.main.async { [weak self] in + self?.appState = AppState.shared + } + } + + deinit { + status = nil + } + + public func request(verb: HttpVerb, url: URL, body: Any?, completion: @escaping (_ request: URLRequest) -> Void) { + var request: URLRequest = URLRequest(url: url) + request.httpMethod = verb.rawValue + request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + request.addValue("application/json", forHTTPHeaderField: "Accept") + if let body = body { + if let bodyText = try? JSONSerialization.data(withJSONObject: body, options: []) { +// let string = String(data: bodyText, encoding: String.Encoding.utf8) +// Console.shared.log("Post Body \(string)") + request.httpBody = bodyText + } + } + inject(request: request, verb: verb, index: 0) { /* [weak self] */ request in + completion(request) + } + } + + public func inject(request: URLRequest, verb: HttpVerb, index: Int, completion: @escaping (_ request: URLRequest) -> Void) { + if let injections = requestInjections, index < injections.count { + let injection = injections[index] + injection.inject(request: request, verb: verb) { [weak self] request in + self?.inject(request: request, verb: verb, index: index + 1, completion: completion) + } + } else { + completion(request) + } + } + + open func url(server: String, path: String, params: [String: Any]?) -> (urlPath: String, paramStrings: [String]?) { + var urlPath = "\(server)\(path)" + var paramStrings: [String]? + if let paramsDictionary = params { + var leftover = [String: Any]() + for (key, value) in paramsDictionary { + let marker = "{\(key)}" + if urlPath.contains(marker) { + urlPath = urlPath.replacingOccurrences(of: marker, with: "\(value)") + } else { + leftover[key] = value + } + } + if leftover.count > 0 { + paramStrings = [String]() + for (key, value) in leftover { + // transform value into string + if value is String { + paramStrings?.append("\(key)=\(value)") + } else if let stringValue = parser.asString(value) { + paramStrings?.append("\(key)=\(stringValue)") + } + } + } + } + return (urlPath, paramStrings) + } + + public func url(path: String, params: [String]?) -> URL? { + var url = path + if let params = params { + if params.count > 0 { + url = path + "?" + params.joined(separator: "&") + } + } + if let encodedUrl = url.encodeUrl() { + return URL(string: encodedUrl) + } + return nil + } + + public func merge(_ params1: [String: Any]?, with params2: [String: Any]?) -> [String: Any] { + var merged: [String: Any] = [:] + if let params1 = params1 { + merged.merge(params1) { (_, second) -> Any in + second + } + } + if let params2 = params2 { + merged.merge(params2) { (_, second) -> Any in + second + } + } + return merged + } + + open func result(data: Any?) -> Any? { + return data + } + + open func meta(data: Any?) -> Any? { + return nil + } +} diff --git a/ParticlesKit/ParticlesKit/_Api/_WebApi/PagedWebApi.swift b/ParticlesKit/ParticlesKit/_Api/_WebApi/PagedWebApi.swift new file mode 100644 index 000000000..b44441306 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Api/_WebApi/PagedWebApi.swift @@ -0,0 +1,51 @@ +// +// PagedWebApi.swift +// WebApiLib +// +// Created by Qiang Huang on 11/2/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +open class PagedWebApi: NSObject, IOProtocol where WebApiClass: WebApi { + @objc public dynamic var isLoading: Bool = false + + public var priority: Int = 10 + public var page: Int + public var limit: Int + public var api: WebApiClass? + + public required init(page: Int, limit: Int) { + self.page = page + self.limit = limit + super.init() + } + + public func load(path: String, params: [String: Any]?, completion: @escaping IOReadCompletionHandler) { + isLoading = true + load(path: path, page: page, params: params) {[weak self] data, meta, priority, error in + self?.isLoading = false + completion(data, meta, priority, error) + } + } + + public func load(path: String, page: Int, params: [String: Any]?, completion: @escaping IOReadCompletionHandler) { + api = WebApiClass(priority: priority) + let merged = DictionaryUtils.merge(params, with: param(page: page)) + api?.load(path: path, params: merged, completion: completion) + } + + public func save(path: String, params: [String: Any]?, data: Any?, completion: IOWriteCompletionHandler?) { + } + + public func modify(path: String, params: [String: Any]?, data: Any?, completion: IOWriteCompletionHandler?) { + } + + public func delete(path: String, params: [String: Any]?, completion: IODeleteCompletionHandler?) { + } + + open func param(page: Int) -> [String: Any]? { + return nil + } +} diff --git a/ParticlesKit/ParticlesKit/_Api/_WebApi/ParallelWebApi.swift b/ParticlesKit/ParticlesKit/_Api/_WebApi/ParallelWebApi.swift new file mode 100644 index 000000000..6bccb27e6 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Api/_WebApi/ParallelWebApi.swift @@ -0,0 +1,103 @@ +// +// ParallelWebApi.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +open class ParallelWebApi: NSObject, IOProtocol where WebApiClass: WebApi, PagedWebApiClass: PagedWebApi { + @objc public dynamic var isLoading: Bool = false + + public var priority: Int = 10 + + public var limit: Int = 100 // items per page + public var lanes: Int = 100 + public var total: Int? + private var apis: [Int: PagedWebApiClass] = [Int: PagedWebApiClass]() + private var responses: [Int: Any] = [:] + private var completion: IOReadCompletionHandler? + + public required init(lanes: Int? = nil) { + super.init() + if let lanes = lanes { + self.lanes = lanes + } + } + + public func load(path: String, params: [String: Any]?, completion: @escaping IOReadCompletionHandler) { + apis.removeAll() + responses.removeAll() + self.completion = completion + + isLoading = true + for i in 0 ... lanes { + run(path: path, page: i, params: params) + } + } + + public func save(path: String, params: [String: Any]?, data: Any?, completion: IOWriteCompletionHandler?) { + } + + public func modify(path: String, params: [String: Any]?, data: Any?, completion: IOWriteCompletionHandler?) { + } + + public func delete(path: String, params: [String: Any]?, completion: IODeleteCompletionHandler?) { + } + + open func run(path: String, page: Int, params: [String: Any]?) { + var pass = true + if let total = total { + if page * limit >= total { + pass = false + } + } + if pass { + let api = PagedWebApiClass(page: page, limit: limit) + api.load(path: path, params: params) { [weak self] (data: Any?, meta: Any?, _: Int, _: Error?) in + if let self = self { + if let total = self.total(meta: meta) { + self.total = total + } + if let loaded = data as? [Any] { + if loaded.count != 0 { + self.responses[page] = loaded + self.run(path: path, page: api.page + self.lanes, params: params) + } + } else { + self.finishWhenDone() + } + self.apis.removeValue(forKey: api.page) + } + } + apis[page] = api + } else { + finishWhenDone() + } + } + + open func total(meta: Any?) -> Int? { + return nil + } + + internal func finishWhenDone() { + if let total = total { + if responses.count * limit >= total { + finish() + } + } + } + + open func finish() { + var completeResponses = [Any]() + for i in 0 ..< responses.count { + if let pagedResponses = responses[i] as? [Any] { + completeResponses.append(contentsOf: pagedResponses) + } + } + isLoading = false + completion?(completeResponses, nil, priority, nil) + } +} diff --git a/ParticlesKit/ParticlesKit/_Api/_WebApi/WebApi.swift b/ParticlesKit/ParticlesKit/_Api/_WebApi/WebApi.swift new file mode 100644 index 000000000..bd0b075a3 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Api/_WebApi/WebApi.swift @@ -0,0 +1,230 @@ +// +// WebApi.swift +// WebApiLib +// +// Created by John Huang on 10/11/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import RoutingKit +import Utilities + +open class WebApi: HttpApi, ApiProtocol { + @objc public dynamic var isLoading: Bool = false + public var priority: Int = 10 + public var retry: Int = 3 + public var retryCount: Int = 0 + + #if _iOS || _tvOS + private var backgroundTaskId: UIBackgroundTaskIdentifier = .invalid + #endif + private var dataTask: URLSessionDataTask? + + private var running = false { + didSet { + if running != oldValue { + if running { + LoadingStatus.shared.plus() + } else { + LoadingStatus.shared.minus() + } + } + } + } + + public required init(priority: Int = 10) { + super.init() + self.priority = priority + } + + public required init(server: String? = nil, priority: Int = 10) { + super.init(server: server) + self.priority = priority + } + + public required init(endpointResolver: EndpointResolverProtocol?, priority: Int = 100) { + super.init(endpointResolver: endpointResolver) + self.priority = priority + } + + deinit { + endBackgroundTask() + running = false + } + + public func load(path: String, params: [String: Any]?, completion: @escaping IOReadCompletionHandler) { + isLoading = true + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { + self.get(path: path, params: params) { [weak self] data, error in + if let self = self { + self.isLoading = false + let data = (error == nil) ? self.result(data: data) : nil + completion(data, self.meta(data: data), self.priority, error) + } + } + } + } + + open func get(path: String, params: [String: Any]?, completion: @escaping ApiCompletionHandler) { + run(verb: .get, path: path, params: params, body: nil, completion: completion) + } + + open func post(path: String, params: [String: Any]?, data: Any?, completion: @escaping ApiCompletionHandler) { + run(verb: .post, path: path, params: params, body: data, completion: completion) + } + + open func put(path: String, params: [String: Any]?, data: Any?, completion: @escaping ApiCompletionHandler) { + run(verb: .put, path: path, params: params, body: data, completion: completion) + } + + open func delete(path: String, params: [String: Any]?, completion: @escaping ApiCompletionHandler) { + run(verb: .delete, path: path, params: params, body: nil, completion: completion) + } + + open func run(verb: HttpVerb, path: String, params: [String: Any]?, body: Any?, completion: @escaping ApiCompletionHandler) { + if let server = server { + dataTask?.cancel() + + let pathAndParams = url(server: server, path: path, params: params) + if pathAndParams.urlPath.contains("{") { + // unresolved params + completion(nil, nil) + } else { + run(verb: verb, urlPath: pathAndParams.urlPath, paramStrings: pathAndParams.paramStrings, body: body, completion: completion) + } + } else { + completion(nil, nil) + } + } + + open func run(verb: HttpVerb, urlPath: String, paramStrings: [String]?, body: Any?, completion: @escaping ApiCompletionHandler) { + if let data = ApiReplayer.shared?.replay(urlPath: urlPath, params: paramStrings) { + completion(data, nil) + } else { + if let url = url(path: urlPath, params: paramStrings) { + request(verb: verb, url: url, body: body) { [weak self] request in + self?.run(request: request, urlPath: urlPath, paramStrings: paramStrings, completion: completion) + } + } + } + } + + open func run(request: URLRequest, urlPath: String, paramStrings: [String]?, completion: @escaping ApiCompletionHandler) { + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 30 + let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil) + + Console.shared.log("API:\(urlPath)\n") + status = LoadingStatus.shared + beginBackgroundTask() + dataTask = session.dataTask(with: request) { [weak self] (raw: Data?, response: URLResponse?, error: Error?) in + ErrorLogging.shared?.log(error) + if let self = self { + DispatchQueue.main.async { [weak self] in + self?.status = nil + } + self.endBackgroundTask() + DispatchQueue.main.async { [weak self] in + if let self = self { + let data = (raw != nil) ? try? JSONSerialization.jsonObject(with: raw!, options: []) : nil + + if let responseInjections = self.responseInjections { + for responseInjection in responseInjections { + responseInjection.inject(response: response, data: data, verb: HttpVerb(rawValue: request.httpMethod ?? "GET")) + } + } + let code = (response as? HTTPURLResponse)?.statusCode ?? 0 + if code == 204 { + self.fail(response: response, code: code, data: data, completion: completion) + } else if code == 401 { + Router.shared?.navigate(to: RoutingRequest(originalUrl: request.url?.absoluteString, path: "/action/logout", params: ["notify": true]), animated: true, completion: nil) + self.fail(response: response, code: code, data: data, completion: completion) + } else if (200 ... 299).contains(code) { + ApiReplayer.shared?.record(urlPath: urlPath, params: paramStrings, data: data) + self.success(response: response, code: code, data: data, completion: completion) + } else { + if request.httpMethod?.uppercased() == "GET", code != 403, self.retryCount < self.retry { + self.retryCount += 1 + DispatchQueue.main.asyncAfter(deadline: .now() + ((code == 429) ? 3.0 : 0.1)) {[weak self] in + self?.run(request: request, urlPath: urlPath, paramStrings: paramStrings, completion: completion) + } + } else { + self.fail(response: response, code: code, data: data, completion: completion) + } + } + } + } + } + } + dataTask?.resume() + } + + public func fail(response: URLResponse?, code: Int, data: Any?, completion: @escaping ApiCompletionHandler) { + let className = String(describing: type(of: self)) + let error = NSError(domain: "\(className).response.fail", code: code, userInfo: data as? [String: Any]) + ErrorLogging.shared?.log(error) + completion(data, error) + } + + public func success(response: URLResponse?, code: Int, data: Any?, completion: @escaping ApiCompletionHandler) { + completion(data, nil) + } + + public func messageForError(error: Error?) -> String? { + var msg: String? + if let userInfo = (error as NSError?)?.userInfo { + if let error = userInfo["error"] { + msg = messageForErrorPayload(error: error) + } else if let errors = userInfo["errors"] as? [[String: Any]], let error = errors.first { + msg = messageForErrorPayload(error: error) + } + } + if msg == nil { + msg = messageForCode(code: (error as NSError?)?.code) + } + return msg + } + + private func messageForErrorPayload(error: Any) -> String? { + if let text = error as? String { + return text + } else if let info = error as? [String: Any], let text = (info["msg"] as? String) ?? (info["message"] as? String) { + return text + } else { + return nil + } + } + + public func messageForCode(code: Int?) -> String? { + switch code { + case 504: + fallthrough + case 500: + return "Internal Server Error" + + default: + return nil + } + } + + private func beginBackgroundTask() { + #if _iOS || _tvOS + if backgroundTaskId == .invalid { + running = true + backgroundTaskId = UIApplication.shared.beginBackgroundTask { [weak self] in + self?.endBackgroundTask() + } + } + #endif + } + + private func endBackgroundTask() { + #if _iOS || _tvOS + if backgroundTaskId != .invalid { + UIApplication.shared.endBackgroundTask(backgroundTaskId) + backgroundTaskId = .invalid + running = false + } + #endif + } +} diff --git a/ParticlesKit/ParticlesKit/_Api/_WebApi/WebApiInjectionProtocols.swift b/ParticlesKit/ParticlesKit/_Api/_WebApi/WebApiInjectionProtocols.swift new file mode 100644 index 000000000..3d2a1b956 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Api/_WebApi/WebApiInjectionProtocols.swift @@ -0,0 +1,18 @@ +// +// WebApiRequestInjectionProtocol.swift +// ParticlesKit +// +// Created by Qiang Huang on 5/15/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public protocol WebApiRequestInjectionProtocol: NSObjectProtocol { + func inject(request: URLRequest, verb: HttpVerb, completion: @escaping (_ request: URLRequest) -> Void) + func cookies(completion: @escaping ([String: String]?) -> Void) +} + +public protocol WebApiResponseInjectionProtocol: NSObjectProtocol { + func inject(response: URLResponse?, data: Any?, verb: HttpVerb?) +} diff --git a/ParticlesKit/ParticlesKit/_Api/_WebApi/WebApiRequestHeaderInjection.swift b/ParticlesKit/ParticlesKit/_Api/_WebApi/WebApiRequestHeaderInjection.swift new file mode 100644 index 000000000..fdac184d1 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Api/_WebApi/WebApiRequestHeaderInjection.swift @@ -0,0 +1,35 @@ +// +// WebApiRequestHeaderInjection.swift +// ParticlesKit +// +// Created by Qiang Huang on 8/8/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +public class WebApiRequestHeaderInjection: NSObject, WebApiRequestInjectionProtocol { + var headers: [String: String]? + public init(headers: [String: String]?) { + super.init() + self.headers = headers + } + + public func inject(request: URLRequest, verb: HttpVerb, completion: @escaping (_ request: URLRequest) -> Void) { + if let headers = headers { + var request = request + var httpHeaders: [String: String] = request.allHTTPHeaderFields ?? [:] + for (key, value) in headers { + httpHeaders[key] = value + } + request.allHTTPHeaderFields = httpHeaders + completion(request) + } else { + completion(request) + } + } + + public func cookies(completion: @escaping ([String: String]?) -> Void) { + completion(nil) + } +} diff --git a/ParticlesKit/ParticlesKit/_Auth/AuthLoginAction.swift b/ParticlesKit/ParticlesKit/_Auth/AuthLoginAction.swift new file mode 100644 index 000000000..90f71d971 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Auth/AuthLoginAction.swift @@ -0,0 +1,26 @@ +// +// AuthLoginAction.swift +// ParticlesKit +// +// Created by Qiang Huang on 9/15/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import RoutingKit +import Utilities + +open class AuthLoginAction: NSObject, NavigableProtocol { + open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if request?.path == "/action/login" || request?.path == "/login" { + if let provider = AuthService.shared.provider { + provider.login { /* [weak self] */ successful in + completion?(nil, successful) + } + } else { + completion?(nil, false) + } + } else { + completion?(nil, false) + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Auth/AuthLogoutAction.swift b/ParticlesKit/ParticlesKit/_Auth/AuthLogoutAction.swift new file mode 100644 index 000000000..c1c519f1f --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Auth/AuthLogoutAction.swift @@ -0,0 +1,30 @@ +// +// AuthLogoutAction.swift +// ParticlesKit +// +// Created by Qiang Huang on 9/15/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import RoutingKit +import Utilities + +open class AuthLogoutAction: NSObject, NavigableProtocol { + open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if request?.path == "/action/logout" || request?.path == "/logout" { + if let provider = AuthService.shared.provider { + if let token = provider.token { + provider.logout(token: token) { /* [weak self] */ successful in + completion?(nil, successful) + } + } else { + completion?(nil, true) + } + } else { + completion?(nil, true) + } + } else { + completion?(nil, false) + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Badging/UrlBadgingInteractor.swift b/ParticlesKit/ParticlesKit/_Badging/UrlBadgingInteractor.swift new file mode 100644 index 000000000..90e496840 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Badging/UrlBadgingInteractor.swift @@ -0,0 +1,19 @@ +// +// UrlBadgingInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import RoutingKit + +@objc public class UrlBadgingInteractor: DictionaryInteractor, UrlBadgingProtocol { + @objc public func badge(url: String, value: String?) { + set(value, for: url) + } + + @objc public func badge(for url: String) -> String? { + return value(forKey: url) as? String + } +} diff --git a/ParticlesKit/ParticlesKit/_Cache/IOProtocol.swift b/ParticlesKit/ParticlesKit/_Cache/IOProtocol.swift new file mode 100644 index 000000000..c7fe20dcd --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Cache/IOProtocol.swift @@ -0,0 +1,22 @@ +// +// IOProtocol.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public typealias IOReadCompletionHandler = (_ data: Any?, _ meta: Any?, _ priority: Int, _ error: Error?) -> Void +public typealias IOWriteCompletionHandler = (_ data: Any?, _ error: Error?) -> Void +public typealias IODeleteCompletionHandler = (_ error: Error?) -> Void + +public protocol IOProtocol: NSObjectProtocol { + var priority: Int { get set } + var isLoading: Bool { get set } + func load(path: String, params: [String: Any]?, completion: @escaping IOReadCompletionHandler) + func save(path: String, params: [String: Any]?, data: Any?, completion: IOWriteCompletionHandler?) + func modify(path: String, params: [String: Any]?, data: Any?, completion: IOWriteCompletionHandler?) + func delete(path: String, params: [String: Any]?, completion: IODeleteCompletionHandler?) +} diff --git a/ParticlesKit/ParticlesKit/_Cache/_Json/JsonCachingProtocol.swift b/ParticlesKit/ParticlesKit/_Cache/_Json/JsonCachingProtocol.swift new file mode 100644 index 000000000..751e984e4 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Cache/_Json/JsonCachingProtocol.swift @@ -0,0 +1,50 @@ +// +// JsonCachingProtocol.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/26/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public typealias JsonReadCompletionHandler = (_ data: Any?, _ error: Error?) -> Void +public typealias JsonWriteCompletionHandler = (_ error: Error?) -> Void + +public protocol JsonCachingProtocol: IOProtocol { + func read(path: String, completion: @escaping JsonReadCompletionHandler) + func write(path: String, data: Any?, completion: JsonWriteCompletionHandler?) +} + +extension JsonCachingProtocol { + public func load(path: String, params: [String: Any]?, completion: @escaping IOReadCompletionHandler) { + var path = path + if let params = params { + for (_, value) in params { + if let string = value as? String { + path = path.stringByAppendingPathComponent(path: string) + } + } + } + isLoading = true + read(path: path) { [weak self] data, error in + self?.isLoading = false + completion(data, nil, self?.priority ?? 0, error) + } + } + + public func save(path: String, params: [String: Any]?, data: Any?, completion: IOWriteCompletionHandler?) { + write(path: path, data: data) { error in + completion?(data, error) + } + } + + public func modify(path: String, params: [String: Any]?, data: Any?, completion: IOWriteCompletionHandler?) { + write(path: path, data: data) { error in + completion?(data, error) + } + } + + public func delete(path: String, params: [String: Any]?, completion: IODeleteCompletionHandler?) { + } +} diff --git a/ParticlesKit/ParticlesKit/_Cache/_Json/JsonDocumentFileAsyncCaching.swift b/ParticlesKit/ParticlesKit/_Cache/_Json/JsonDocumentFileAsyncCaching.swift new file mode 100644 index 000000000..b09ad10b1 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Cache/_Json/JsonDocumentFileAsyncCaching.swift @@ -0,0 +1,20 @@ +// +// JsonDocumentFileAsyncCaching.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +open class JsonDocumentFileAsyncCaching: JsonDocumentFileCaching { + open override func read(path: String, completion: @escaping JsonReadCompletionHandler) { + DispatchQueue.global().async {[weak self] in + let object = self?.read(path: path) + DispatchQueue.main.async { + completion(object, nil) + } + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Cache/_Json/JsonDocumentFileCaching.swift b/ParticlesKit/ParticlesKit/_Cache/_Json/JsonDocumentFileCaching.swift new file mode 100644 index 000000000..1c0bba838 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Cache/_Json/JsonDocumentFileCaching.swift @@ -0,0 +1,73 @@ +// +// JsonDocumentFileCaching.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/26/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +open class JsonDocumentFileCaching: NSObject, JsonCachingProtocol { + @objc public dynamic var isLoading: Bool = false + + public var priority: Int = 0 + + public var debouncer: Debouncer = Debouncer() + public var defaultFile: String? + public var folder: String? + + public init(priority: Int = 0, defaultFile: String? = nil) { + super.init() + self.priority = priority + self.defaultFile = defaultFile + } + + public func file(path: String) -> String? { + if folder == nil { + folder = FolderService.shared?.documents() + } + return folder?.stringByAppendingPathComponent(path: path).stringByAppendingPathComponent(path: "data.json") + } + + public func defaultFile(path: String?) -> String? { + if let path = path { + return Bundle.main.bundlePath.stringByAppendingPathComponent(path: path) + } else { + return nil + } + } + + open func read(path: String, completion: @escaping JsonReadCompletionHandler) { + let object = read(path: path) + completion(object, nil) + } + + open func read(path: String) -> Any? { + if let file = file(path: path) { + var object = JsonLoader.load(file: file) + if object == nil { + if let defaultFile = self.defaultFile(path: defaultFile) { + object = JsonLoader.load(file: defaultFile) + } + } + return object + } else { + return nil + } + } + + open func write(path: String, data: Any?, completion: JsonWriteCompletionHandler?) { + if let file = file(path: path) { + if let handler = debouncer.debounce() { + handler.run(background: { + JsonWriter.write(data, to: file) + }, final: { + completion?(nil) + }, delay: 0.5) + } + } else { + completion?(NSError(domain: "file", code: 0, userInfo: ["message": "document folder error"])) + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Cache/_Keychain/JsonKeychainCaching.swift b/ParticlesKit/ParticlesKit/_Cache/_Keychain/JsonKeychainCaching.swift new file mode 100644 index 000000000..92752c3f5 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Cache/_Keychain/JsonKeychainCaching.swift @@ -0,0 +1,43 @@ +// +// JsonKeychainCaching.swift +// ParticlesKit +// +// Created by Qiang Huang on 4/21/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import SimpleKeychain +import Utilities + +open class JsonKeychainCaching: NSObject, JsonCachingProtocol { + @objc public dynamic var isLoading: Bool = false + + private static let keychain = A0SimpleKeychain() + public var priority: Int = 0 + + public init(priority: Int = 0) { + super.init() + self.priority = priority + } + + open func read(path: String, completion: @escaping JsonReadCompletionHandler) { + if let data = JsonKeychainCaching.keychain.data(forKey: path), let object = try? JSONSerialization.jsonObject(with: data, options: []) { + completion(object, nil) + } else { + completion(nil, nil) + } + } + + open func write(path: String, data: Any?, completion: JsonWriteCompletionHandler?) { + do { + if let data = data { + let json = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted) + JsonKeychainCaching.keychain.setData(json, forKey: path) + } else { + JsonKeychainCaching.keychain.deleteEntry(forKey: path) + } + } catch { + } + completion?(nil) + } +} diff --git a/ParticlesKit/ParticlesKit/_Cache/_Protocols/LikedObjectsProtocol.swift b/ParticlesKit/ParticlesKit/_Cache/_Protocols/LikedObjectsProtocol.swift new file mode 100644 index 000000000..75da56cda --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Cache/_Protocols/LikedObjectsProtocol.swift @@ -0,0 +1,98 @@ +// +// LikedObjectsProtocol.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/19/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol LikedObjectsProtocol: NSObjectProtocol { + @objc var liked: [String]? { get set } + @objc var disliked: [String]? { get set } +} + +public extension LikedObjectsProtocol { + func addLike(key: String?) { + removeDislike(key: key) + if let key = key, let liked = liked { + if liked.firstIndex(of: key) == nil { + var modified = liked + modified.append(key) + self.liked = modified + } + } + } + + func removeLike(key: String?) { + if let key = key, let liked = liked { + if let index = liked.firstIndex(of: key) { + var modified = liked + modified.remove(at: index) + self.liked = modified + } + } + } + + func toggleLike(key: String?) { + if let key = key, let liked = liked { + var modified = liked + if let index = liked.firstIndex(of: key) { + modified.remove(at: index) + } else { + removeDislike(key: key) + modified.append(key) + } + self.liked = modified + } + } + + func liked(key: String?) -> Bool { + if let key = key, let liked = liked { + return liked.firstIndex(of: key) != nil + } + return false + } + + func addDislike(key: String?) { + removeLike(key: key) + if let key = key, let disliked = disliked { + if disliked.firstIndex(of: key) == nil { + var modified = disliked + modified.append(key) + self.disliked = modified + } + } + } + + func removeDislike(key: String?) { + if let key = key, let disliked = disliked { + if let index = disliked.firstIndex(of: key) { + var modified = disliked + modified.remove(at: index) + self.disliked = modified + } + } + } + + func toggleDislike(key: String?) { + if let key = key, let disliked = disliked { + var modified = disliked + if let index = disliked.firstIndex(of: key) { + modified.remove(at: index) + } else { + removeLike(key: key) + modified.append(key) + } + self.disliked = modified + } + } + + func disliked(key: String?) -> Bool { + if let key = key, let disliked = disliked { + return disliked.firstIndex(of: key) != nil + } + return false + } +} diff --git a/ParticlesKit/ParticlesKit/_Cache/_Protocols/SavedSearchesProtocol.swift b/ParticlesKit/ParticlesKit/_Cache/_Protocols/SavedSearchesProtocol.swift new file mode 100644 index 000000000..34fde2a1b --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Cache/_Protocols/SavedSearchesProtocol.swift @@ -0,0 +1,15 @@ +// +// SavedSearchesProtocol.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/19/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol SavedSearchesProtocol: NSObjectProtocol { + @objc var savedSearches: [SavedSearchEntity]? { get set } + func add(name: String, search: String?, filters: [String: Any]?) + func remove(savedSearch: SavedSearchEntity) +} diff --git a/ParticlesKit/ParticlesKit/_Common/AppInfo.swift b/ParticlesKit/ParticlesKit/_Common/AppInfo.swift new file mode 100644 index 000000000..1461c3608 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Common/AppInfo.swift @@ -0,0 +1,42 @@ +// +// AppInfo.swift +// ParticlesKit +// +// Created by Qiang Huang on 8/28/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +@objc public final class AppInfo: NSObject, ModelObjectProtocol, SingletonProtocol { + public static var shared: AppInfo = AppInfo() + + @objc public dynamic var name: String? + @objc public dynamic var version: String? + + public var fullName: String? { + if let name = name { + if let version = version { + return "\(name) v\(version)" + } else { + return name + } + } else { + return nil + } + } + + override public init() { + super.init() + + name = (Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String) ?? (Bundle.main.infoDictionary?["CFBundleName"] as? String) + + if let version = Bundle.main.version { + if let build = Bundle.main.build { + self.version = "\(version).\(build)" + } else { + self.version = "\(version)" + } + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Credentials/JsonCredentialsProvider.swift b/ParticlesKit/ParticlesKit/_Credentials/JsonCredentialsProvider.swift new file mode 100644 index 000000000..91db462a9 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Credentials/JsonCredentialsProvider.swift @@ -0,0 +1,46 @@ +// +// CredentialsProvider.swift +// ParticlesKit +// +// Created by John Huang on 7/16/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +open class JsonCredentialsProvider: NSObject { + public static var parserOverwrite: Parser? + + override open var parser: Parser { + return JsonEndpointResolver.parserOverwrite ?? super.parser + } + + private var entity: DictionaryEntity? + + public func credential(for lookupKey: String) -> String? { + let content = entity?.data?[lookupKey] + if let contentDict = parser.asDictionary(content) { + let value = parser.asString(contentDict["value"]) + if value?.isNotEmpty ?? false { + return value + } else { + return nil + } + } else { + return parser.asString(content) + } + } + + override public init() { + super.init() + + if let credentials = JsonLoader.load(bundles: Bundle.particles, fileName: "credentials.json") as? [String: Any] { + entity = DictionaryEntity() + entity?.parse(dictionary: credentials) + } + } +} + +public class CredientialConfig { + public static var shared = JsonCredentialsProvider() +} diff --git a/ParticlesKit/ParticlesKit/_Entity/_Common/FilterEntity.swift b/ParticlesKit/ParticlesKit/_Entity/_Common/FilterEntity.swift new file mode 100644 index 000000000..13355fd89 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Entity/_Common/FilterEntity.swift @@ -0,0 +1,49 @@ +// +// FilterEntity.swift +// EntityLib +// +// Created by Qiang Huang on 10/23/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +open class FilterEntity: DictionaryEntity { + private var key: String? + private var preferenceKey: String? { + if let key = key { + return "\(type(of: self)).filters.\(key)" + } + return nil + } + + open override var data: [String: Any]? { + didSet { + if let preferenceKey = preferenceKey { + UserDefaults.standard.set(data, forKey: preferenceKey) + } + } + } + + public required init() { + super.init() + } + + public init(key: String) { + self.key = key + super.init() + + if let preferenceKey = preferenceKey { + data = UserDefaults.standard.dictionary(forKey: preferenceKey) ?? [String: Any]() + } + } + + public init(copy entity: FilterEntity) { + super.init() + data = entity.data + } + + public func apply(to entity: FilterEntity) { + entity.data = data + } +} diff --git a/ParticlesKit/ParticlesKit/_Entity/_Common/SavedSearchEntity.swift b/ParticlesKit/ParticlesKit/_Entity/_Common/SavedSearchEntity.swift new file mode 100644 index 000000000..3c30b0289 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Entity/_Common/SavedSearchEntity.swift @@ -0,0 +1,41 @@ +// +// SavedSearch.swift +// EntityLib +// +// Created by Qiang Huang on 10/30/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +open class SavedSearchEntity: DictionaryEntity { + open var name: String? { + get { return parser.asString(data?["name"]) } + set { + if data == nil { + data = [String: Any]() + } + data?["name"] = newValue + } + } + + open var text: String? { + get { return parser.asString(data?["text"]) } + set { + if data == nil { + data = [String: Any]() + } + data?["text"] = newValue + } + } + + open var filters: [String: Any]? { + get { return parser.asDictionary(data?["fitlers"]) } + set { + if data == nil { + data = [String: Any]() + } + data?["filters"] = newValue + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Entity/_Common/XibAction.swift b/ParticlesKit/ParticlesKit/_Entity/_Common/XibAction.swift new file mode 100644 index 000000000..24e763a2d --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Entity/_Common/XibAction.swift @@ -0,0 +1,60 @@ +// +// XibAction.swift +// ParticlesKit +// +// Created by Qiang Huang on 8/12/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import RoutingKit +import Utilities + +@objc open class XibAction: NSObject, XibProviderProtocol, ModelObjectProtocol, SelectableProtocol, ParsingProtocol, RoutingOriginatorProtocol { + @objc public dynamic var isSelected: Bool = false + @objc public dynamic var xib: String? + @objc public dynamic var title: String? + @objc public dynamic var text: String? + @objc public dynamic var image: String? + @objc public dynamic var color: String? + @objc public dynamic var request: RoutingRequest? + + public func routingRequest() -> RoutingRequest? { + return request + } + + public static func load(file: String) -> [XibAction]? { + var actions: [XibAction]? + let bundles = Bundle.particles + for bundle in bundles { + actions = load(file: file, bundle: bundle) + if actions != nil { + break + } + } + return actions + } + + public static func load(file: String, bundle: Bundle) -> [XibAction]? { + if let data = JsonLoader.load(bundle: bundle, fileName: file) as? [[String: Any]] { + var objects = [XibAction]() + for item in data { + let object = XibAction() + object.parse(dictionary: item) + objects.append(object) + } + return objects.count > 0 ? objects : nil + } + return nil + } + + public func parse(dictionary: [String: Any]) { + xib = parser.asString(dictionary["xib"]) + title = parser.asString(dictionary["title"]) + text = parser.asString(dictionary["text"]) + image = parser.asString(dictionary["image"]) + color = parser.asString(dictionary["color"]) + if let url = parser.asString(dictionary["url"]) { + request = RoutingRequest(url: url) + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Entity/_Entity/DictionaryEntity.swift b/ParticlesKit/ParticlesKit/_Entity/_Entity/DictionaryEntity.swift new file mode 100644 index 000000000..d9dd29372 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Entity/_Entity/DictionaryEntity.swift @@ -0,0 +1,136 @@ +// +// DictionaryEntity.swift +// EntityLib +// +// Created by John Huang on 10/11/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +open class DictionaryEntity: NSObject, ModelObjectProtocol, ParsingProtocol, JsonPersistable, DirtyProtocol { + @objc open dynamic var data: [String: Any]? + + public var dirty_time: Date? { + get { + return parser.asDate(data?["dirty_time"]) + } + set { + let since1970 = parser.asInt(newValue) + + if dirty_time != parser.asDate(since1970) { + let triggerDirty = (dirty_time == nil && newValue != nil) || (dirty_time != nil && newValue == nil) + if triggerDirty { + willChangeValue(forKey: "dirty") + } + willChangeValue(forKey: "dirty_time") + force.data?["dirty_time"] = since1970 + didChangeValue(forKey: "dirty_time") + if triggerDirty { + didChangeValue(forKey: "dirty") + } + } + } + } + + public var dirty: Bool { + get { + return dirty_time != nil + } + set { + if newValue { + dirty_time = Date() + } else { + dirty_time = nil + } + } + } + + public var force: DictionaryEntity { + if data == nil { + data = [:] + } + return self + } + + open var json: [String: Any]? { + get { return data } + set { + if let dictionary = newValue { + parse(dictionary: dictionary) + } + } + } + + open var thinned: [String: Any]? { + return json + } + + open func parse(dictionary: [String: Any]) { + if !((data as NSDictionary?)?.isEqual(to: dictionary) ?? false) { + var keys = Set(dictionary.keys) + if let data = data { + keys = keys.union(data.keys) + } + + for key in keys { + willChangeValue(forKey: key) + } + data = DictionaryUtils.merge(data, with: dictionary)?.filter { + !($0.value is NSNull) + } + for key in keys { + didChangeValue(forKey: key) + } + } + } + + open var displayTitle: String? { + return parser.asString(self.data?["title"]) + } + + override public required init() { + super.init() + } + + override open func value(forUndefinedKey key: String) -> Any? { + return data?[key] + } + + override open func setValue(_ value: Any?, forUndefinedKey key: String) { + willChangeValue(forKey: key) + willChangeValue(forKey: "data") + force.data?[key] = value + didChangeValue(forKey: "data") + didChangeValue(forKey: key) + } + + override open func isEqual(_ object: Any?) -> Bool { + if let entity = object as? DictionaryEntity { + if let data = data { + if let data2 = entity.data { + return NSDictionary(dictionary: data).isEqual(to: data2) + } else { + return false + } + } else { + return entity.data == nil + } + } + return false + } + + open func copy() -> Self { + let copy = Self() + copy.data = data + return copy + } +} + +public extension DictionaryEntity { + func set(key: String, value: Any?, notify: String? = nil) { + run({ [weak self] in + self?.force.data?[key] = value + }, notify: notify ?? key) + } +} diff --git a/ParticlesKit/ParticlesKit/_Entity/_Entity/TimeCounter+Particle.swift b/ParticlesKit/ParticlesKit/_Entity/_Entity/TimeCounter+Particle.swift new file mode 100644 index 000000000..f238db736 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Entity/_Entity/TimeCounter+Particle.swift @@ -0,0 +1,12 @@ +// +// TimerCounter+Particle.swift +// ParticlesKit +// +// Created by Qiang Huang on 5/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +extension TimeCounter: ModelObjectProtocol { +} diff --git a/ParticlesKit/ParticlesKit/_Entity/_Model/ModelProtocol.swift b/ParticlesKit/ParticlesKit/_Entity/_Model/ModelProtocol.swift new file mode 100644 index 000000000..c1681db62 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Entity/_Model/ModelProtocol.swift @@ -0,0 +1,176 @@ +// +// ModelProtocol.swift +// EntityLib +// +// Created by John Huang on 11/19/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import CoreLocation +import Utilities + +@objc public protocol ModelObjectProtocol: NSObjectProtocol { + @objc optional weak var parent: ModelObjectProtocol? { get set } + @objc optional var index: Int { get set } + @objc optional var key: String? { get } + @objc optional var displayTitle: String? { get } + @objc optional var displaySubtitle: String? { get } + @objc optional var displayImageUrl: String? { get } +// @objc optional var isObjectSelected: Bool { get set } + + @objc optional func children(tag: String?) -> [ModelObjectProtocol]? + @objc optional func order(ascending another: ModelObjectProtocol?) -> Bool +} + +@objc public protocol GraphingObjectProtocol: ModelObjectProtocol { + var graphingX: NSNumber? { get } +} + +@objc public protocol BarGraphingObjectProtocol: GraphingObjectProtocol { + var barY: NSNumber? { get } +} + +@objc public protocol LinearGraphingObjectProtocol: GraphingObjectProtocol { + var lineY: NSNumber? { get } +} + +@objc public protocol PieGraphingObjectProtocol: ModelObjectProtocol { + var pieLabel: String? { get } + var pieY: NSNumber? { get } + var pieColor: String? { get } +} + +@objc public protocol CandleGraphingObjectProtocol: GraphingObjectProtocol { + var candleLabel: String? { get } + var candleOpen: NSNumber? { get } + var candleClose: NSNumber? { get } + var candleHigh: NSNumber? { get } + var candleLow: NSNumber? { get } +} + +@objc public protocol FilterableProtocol: NSObjectProtocol { + @objc func filter(lowercased: String?) -> Bool +} + +@objc public protocol ClusteredModelObjectProtocol: ModelObjectProtocol { + @objc var cluster: [ModelObjectProtocol]? { get } +} + +@objc public protocol DirtyProtocol: NSObjectProtocol { + @objc var dirty_time: Date? { get set } + @objc var dirty: Bool { get set } +} + +@objc public protocol ModelListProtocol: ModelObjectProtocol { + var list: [ModelObjectProtocol]? { get set } +} + +@objc public protocol ModelGridProtocol: ModelObjectProtocol { + var grid: [[ModelObjectProtocol]]? { get set } + var width: Int { get } + var height: Int { get } +} + +public protocol DateModelObjectProtocol: ModelObjectProtocol { + var date: Date? { get } +} + +public protocol JsonPersistable { + var json: [String: Any]? { get set } + var thinned: [String: Any]? { get } +} + +public protocol LocalCacheProtocol: NSObjectProtocol { + func entity(from data: [String: Any]?) -> ModelObjectProtocol? +} + +@objc public enum AnnotationInclusion: Int { + case none // do not zoom to annotation + case ifNone // only zoom to annotation if there are no annotation previously + case always // always zoom to annotation +} + +public protocol AnnotationProtocol: NSObjectProtocol { + var annotationCoordinate: CLLocationCoordinate2D { get } + var annotationTitle: String? { get } + var annotationSubtitle: String? { get } + var inclusion: AnnotationInclusion { get } + var preferedContent: Bool { get } +} + +public extension ModelObjectProtocol { + func stringAscending(string: String?, another: String?) -> Bool? { + if let string1 = string { + if let string2 = another { + switch string1.compare(string2) { + case .orderedAscending: + return true + + case .orderedDescending: + return false + + default: + return nil + } + } else { + return false + } + } else { + if let _ = another { + return true + } else { + return nil + } + } + } + + func datetimeAscending(date: Date?, another: Date?) -> Bool? { + if let date1 = date { + if let date2 = another { + switch date1.compare(date2) { + case .orderedAscending: + return true + + case .orderedDescending: + return false + + default: + return nil + } + } else { + return false + } + } else { + if let _ = another { + return true + } else { + return nil + } + } + } + + func numberAscending(number: NSNumber?, another: NSNumber?) -> Bool? { + if let number1 = number { + if let number2 = another { + switch number1.compare(number2) { + case .orderedAscending: + return true + + case .orderedDescending: + return false + + default: + return nil + } + } else { + return false + } + } else { + if let _ = another { + return true + } else { + return nil + } + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Error/_Log/CompositeErrorLogging.swift b/ParticlesKit/ParticlesKit/_Error/_Log/CompositeErrorLogging.swift new file mode 100644 index 000000000..bb7683c2e --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Error/_Log/CompositeErrorLogging.swift @@ -0,0 +1,37 @@ +// +// CompositeTracking.swift +// TrackingKit +// +// Created by Qiang Huang on 10/9/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public class CompositeErrorLogging: NSObject & ErrorLoggingProtocol { + private var loggings: [ErrorLoggingProtocol] = [ErrorLoggingProtocol]() + + public func add(_ logging: ErrorLoggingProtocol?) { + if let aLogging = logging { + loggings.append(aLogging) + } + } + + public func log(_ error: Error?) { + for logging: ErrorLoggingProtocol in loggings { + logging.log(error) + } + } + + public func e(tag: String, message: String) { + for logging in loggings { + logging.e(tag: tag, message: message) + } + } + + public func d(tag: String, message: String) { + for logging in loggings { + logging.d(tag: tag, message: message) + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Error/_Log/DebugErrorLogging.swift b/ParticlesKit/ParticlesKit/_Error/_Log/DebugErrorLogging.swift new file mode 100644 index 000000000..b6b4e8742 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Error/_Log/DebugErrorLogging.swift @@ -0,0 +1,25 @@ +// +// DebugTracking.swift +// ParticlesKit +// +// Created by John Huang on 12/20/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +public class DebugErrorLogging: NSObject & ErrorLoggingProtocol { + public func e(tag: String, message: String) { + Console.shared.log("Error: \(tag) \(message)") + } + + public func d(tag: String, message: String) { + Console.shared.log("Debug: \(tag) \(message)") + } + + public func log(_ error: Error?) { + if let error = error { + Console.shared.log("Error:\(error)") + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Error/_Log/ErrorLogging.swift b/ParticlesKit/ParticlesKit/_Error/_Log/ErrorLogging.swift new file mode 100644 index 000000000..da6f32712 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Error/_Log/ErrorLogging.swift @@ -0,0 +1,18 @@ +// +// AnalyticsProtocol.swift +// TrackingKit +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +import Utilities + +public protocol ErrorLoggingProtocol: NSObjectProtocol, Logging { + func log(_ error: Error?) +} + +public class ErrorLogging { + public static var shared: ErrorLoggingProtocol? +} diff --git a/ParticlesKit/ParticlesKit/_Formatter/AmountValueFormatter.swift b/ParticlesKit/ParticlesKit/_Formatter/AmountValueFormatter.swift new file mode 100644 index 000000000..c3ca8ff9b --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Formatter/AmountValueFormatter.swift @@ -0,0 +1,42 @@ +// +// AmountValueFormatter.swift +// ParticlesKit +// +// Created by Qiang Huang on 6/10/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Utilities + +@objc public class AmountValueFormatter: NSObject, ValueFormatterProtocol { + private static var formatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.minimumIntegerDigits = 1 + formatter.minimumFractionDigits = 2 + formatter.maximumFractionDigits = 6 + formatter.minimumSignificantDigits = 1 + formatter.maximumSignificantDigits = 5 + return formatter + }() + + public func text(value: Any?) -> String? { + if let value = parser.asDecimal(value) { + let postfix = ["", "K", "M", "B", "T"] + var decimal = value.decimalValue + var index = 0 + while decimal > 1000.0 && index < (postfix.count - 1) { + decimal = decimal / 1000.0 + index += 1 + } + if let numberString = type(of: self).formatter.string(from: NSDecimalNumber(decimal: decimal)) { + return "\(numberString)\(postfix[index])" + } + } + return nil + } + + public func value(text: String?) -> Any? { + return parser.asDecimal(text?.replacingOccurrences(of: "T", with: "").replacingOccurrences(of: "B", with: "").replacingOccurrences(of: "M", with: "").replacingOccurrences(of: "K", with: "").replacingOccurrences(of: ",", with: "")) + } +} diff --git a/ParticlesKit/ParticlesKit/_Formatter/DollarValueFormatter.swift b/ParticlesKit/ParticlesKit/_Formatter/DollarValueFormatter.swift new file mode 100644 index 000000000..d5a4e4f25 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Formatter/DollarValueFormatter.swift @@ -0,0 +1,30 @@ +// +// DollarValueFormatter.swift +// ParticlesKit +// +// Created by Qiang Huang on 6/10/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Utilities + +@objc public class DollarValueFormatter: NSObject, ValueFormatterProtocol { + private static var formatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.locale = Locale(identifier: "en_US") + formatter.numberStyle = .currency + return formatter + }() + + public func text(value: Any?) -> String? { + if let value = parser.asDecimal(value) { + return type(of: self).formatter.string(from: value) + } else { + return nil + } + } + + public func value(text: String?) -> Any? { + return parser.asDecimal(text?.replacingOccurrences(of: "$", with: "").replacingOccurrences(of: ",", with: "")) + } +} diff --git a/ParticlesKit/ParticlesKit/_Formatter/FormatterProtocols.swift b/ParticlesKit/ParticlesKit/_Formatter/FormatterProtocols.swift new file mode 100644 index 000000000..04e5e20c8 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Formatter/FormatterProtocols.swift @@ -0,0 +1,14 @@ +// +// FormatterProtocols.swift +// ParticlesKit +// +// Created by Qiang Huang on 6/10/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Utilities + +@objc public protocol ValueFormatterProtocol { + func text(value: Any?) -> String? + func value(text: String?) -> Any? +} diff --git a/ParticlesKit/ParticlesKit/_Formatter/LeverageValueFormatter.swift b/ParticlesKit/ParticlesKit/_Formatter/LeverageValueFormatter.swift new file mode 100644 index 000000000..4c8b77af1 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Formatter/LeverageValueFormatter.swift @@ -0,0 +1,34 @@ +// +// PercentValueFormatter.swift +// ParticlesKit +// +// Created by Qiang Huang on 6/10/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Utilities + +@objc public class LeverageValueFormatter: NSObject, ValueFormatterProtocol { + private static var formatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.minimumIntegerDigits = 1 + formatter.minimumFractionDigits = 2 + formatter.minimumSignificantDigits = 2 + formatter.maximumSignificantDigits = 3 + return formatter + }() + + public func text(value: Any?) -> String? { + if let value = parser.asDecimal(value) { + if let string = type(of: self).formatter.string(from: value) { + return "\(string)x" + } + } + return nil + } + + public func value(text: String?) -> Any? { + return parser.asDecimal(text) + } +} diff --git a/ParticlesKit/ParticlesKit/_Formatter/PercentValueFormatter.swift b/ParticlesKit/ParticlesKit/_Formatter/PercentValueFormatter.swift new file mode 100644 index 000000000..e75dd7b04 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Formatter/PercentValueFormatter.swift @@ -0,0 +1,40 @@ +// +// PercentValueFormatter.swift +// ParticlesKit +// +// Created by Qiang Huang on 6/10/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Utilities + +@objc public class PercentValueFormatter: NSObject, ValueFormatterProtocol { + private static var formatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .percent +// formatter.minimumIntegerDigits = 1 +// formatter.minimumFractionDigits = 2 +// formatter.maximumFractionDigits = 2 +// formatter.minimumSignificantDigits = 1 +// formatter.maximumSignificantDigits = 3 + + formatter.minimumIntegerDigits = 1 + formatter.minimumFractionDigits = 2 + formatter.maximumFractionDigits = 2 + formatter.minimumSignificantDigits = 3 + formatter.maximumSignificantDigits = 5 + return formatter + }() + + public func text(value: Any?) -> String? { + if let value = parser.asDecimal(value) { + return type(of: self).formatter.string(from: value) + } else { + return nil + } + } + + public func value(text: String?) -> Any? { + return parser.asDecimal(text?.replacingOccurrences(of: "%", with: ""))?.dividing(by: NSDecimalNumber(value: 100)) + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/BaseInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/BaseInteractor.swift new file mode 100644 index 000000000..a9cba135f --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/BaseInteractor.swift @@ -0,0 +1,15 @@ +// +// BaseInteractor.swift +// ParticlesKit +// +// Created by Rui Huang on 5/7/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import Foundation +import Utilities +import Combine + +open class BaseInteractor: NSObject, CombineObserving { + public var cancellableMap = [AnyKeyPath : AnyCancellable]() +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/LocalJsonCacheInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/LocalJsonCacheInteractor.swift new file mode 100644 index 000000000..80c587f4b --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/LocalJsonCacheInteractor.swift @@ -0,0 +1,86 @@ +// +// LocalJsonCacheInteractor.swift +// InteractorLib +// +// Created by Qiang Huang on 11/10/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +@objc open class LocalJsonCacheInteractor: BaseInteractor, LocalCacheProtocol { + + @objc open var key: String? + open var defaultJson: String? + private var _loader: LoaderProtocol? + public var loader: LoaderProtocol? { + if _loader == nil { + _loader = createLoader() + } + return _loader + } + + public var path: String? { + if let key = key { + return "\(String(describing: type(of: self))).persist.\(key)" + } + return nil + } + + open var loadingParams: [String: Any]? { + return nil + } + + private var loadDebouncer = Debouncer() + + override public init() { + super.init() + } + + public init(key: String? = nil, default defaultJson: String? = nil) { + super.init() + self.key = key + self.defaultJson = defaultJson + load() + } + + open func createLoader() -> LoaderProtocol? { + return nil + } + + open func load() { + loadDebouncer.debounce()?.run({[weak self] in + self?.loadSelf() + }, delay: nil) + } + + open func loadSelf() { + loader?.load(params: loadingParams, completion: { [weak self] (io: IOProtocol?, object: Any?, loadTime: Date?, error: Error?) in + if error == nil { + self?.receive(io:io, object: object, loadTime: loadTime, error: error) + } else { + self?.loadDefaults() + } + }) + } + + open func loadDefaults() { + if let defaultJson = defaultJson, let defaultPayload = JsonLoader.load(bundles: Bundle.particles, fileName: defaultJson) as? [String: Any] { + let entity = entity(from: defaultPayload) + (entity as? ParsingProtocol)?.parse?(dictionary: defaultPayload) + receive(io: nil, object: entity, loadTime: nil, error: nil) + } else { + //receive(io: nil, object: nil, loadTime: nil, error: nil) + } + } + + open func entity(from data: [String: Any]?) -> ModelObjectProtocol? { + return nil + } + + open func receive(io: IOProtocol?, object: Any?, loadTime: Date?, error: Error?) { + } + + open func save() { + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_Dictionary/DictionaryInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_Dictionary/DictionaryInteractor.swift new file mode 100644 index 000000000..ec19f75e8 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_Dictionary/DictionaryInteractor.swift @@ -0,0 +1,43 @@ +// +// DictionaryInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +@objc open class DictionaryInteractor: BaseInteractor { + @objc public dynamic var dictionary: [String: Any] = [:] + + public func set(_ value: Any?, for key: String) { + let _key = "dictionary" + let oldValue = dictionary[key] + if let value = value { + if let oldValue = oldValue { + if let object = value as? NSObject, let oldObject = oldValue as? NSObject { + if object !== oldObject { + willChangeValue(forKey: _key) + dictionary[key] = value + didChangeValue(forKey: _key) + } + } else { + willChangeValue(forKey: _key) + dictionary[key] = value + didChangeValue(forKey: _key) + } + } else { + willChangeValue(forKey: _key) + dictionary[key] = value + didChangeValue(forKey: _key) + } + } else { + if let _ = oldValue { + willChangeValue(forKey: _key) + dictionary[key] = value + didChangeValue(forKey: _key) + } + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_Grid/GridInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_Grid/GridInteractor.swift new file mode 100644 index 000000000..7a6dfc31d --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_Grid/GridInteractor.swift @@ -0,0 +1,68 @@ +// +// GridInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 1/16/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +@objc open class GridInteractor: BaseInteractor, InteractorProtocol, ModelGridProtocol { + public var width: Int { + return grid?.count ?? 0 + } + + public var height: Int { + return grid?.first?.count ?? 0 + } + + @objc open dynamic var entity: ModelObjectProtocol? + + @objc open dynamic var loading: Bool = false + + open var displayTitle: String? { + return entity?.displayTitle ?? nil + } + + open var displaySubtitle: String? { + return entity?.displaySubtitle ?? nil + } + + open var displayImageUrl: String? { + return entity?.displayImageUrl ?? nil + } + + @objc public dynamic var grid: [[ModelObjectProtocol]]? + + private func indexOf(_ sourceObject: ModelObjectProtocol?, in destination: NSArray?, startingAt startIndex: Int) -> Int? { + let index = destination?.indexOfObject(passingTest: { (object: Any, index: Int, stop: UnsafeMutablePointer) -> Bool in + if index < startIndex { + return false + } + if self.compare(object as? (ModelObjectProtocol), with: sourceObject) { + stop.pointee = true + return true + } + return false + }) + if index != NSNotFound { + return index + } + return nil + } + + private func compare(_ destination: ModelObjectProtocol?, with source: ModelObjectProtocol?) -> Bool { + if destination === source { + return true + } else { + return destination?.isEqual(source) ?? false + } + } + + open func sync(_ grid: [[ModelObjectProtocol]]?) { + DispatchQueue.runInMainThread { [weak self] in + self?.grid = grid + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectCompositeLineInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectCompositeLineInteractor.swift new file mode 100644 index 000000000..51acc374e --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectCompositeLineInteractor.swift @@ -0,0 +1,55 @@ +// +// ObjectCompositeLineInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 6/14/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Utilities + +@objc open class ObjectCompositeLineInteractor: ObjectLineInteractor { + @objc open dynamic var lineValue1: Any? + @objc open dynamic var lineValue2: Any? + + public var interactor1: ObjectLineInteractor? { + didSet { + didSetInteractor1(oldValue: oldValue) + } + } + + public var interactor2: ObjectLineInteractor? { + didSet { + didSetInteractor2(oldValue: oldValue) + } + } + + public static func interactor(entity: ModelObjectProtocol?, title: String?, interactor1: ObjectLineInteractor, interactor2: ObjectLineInteractor, xib: String? = nil) -> ObjectCompositeLineInteractor { + let interactor = ObjectCompositeLineInteractor() + interactor.title = title + interactor.xib = xib + interactor.entity = entity + interactor.interactor1 = interactor1 + interactor.interactor2 = interactor2 + return interactor + } + + open override func didSetEntity(oldValue: ModelObjectProtocol?) { + interactor1?.entity = entity + interactor2?.entity = entity + } + + private func didSetInteractor1(oldValue: ObjectLineInteractor?) { + interactor1?.entity = entity + changeObservation(from: oldValue, to: interactor1, keyPath: #keyPath(ObjectValueLineInteractor.lineValue)) { [weak self] _, _, _, _ in + self?.lineValue1 = (self?.interactor1 as? ObjectValueLineInteractor)?.lineValue + } + } + + private func didSetInteractor2(oldValue: ObjectLineInteractor?) { + interactor2?.entity = entity + changeObservation(from: oldValue, to: interactor2, keyPath: #keyPath(ObjectValueLineInteractor.lineValue)) { [weak self] _, _, _, _ in + self?.lineValue2 = (self?.interactor2 as? ObjectValueLineInteractor)?.lineValue + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectLineInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectLineInteractor.swift new file mode 100644 index 000000000..097ca5ff8 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectLineInteractor.swift @@ -0,0 +1,25 @@ +// +// ObjectLineInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 6/10/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Utilities + +@objc open class ObjectLineInteractor: BaseInteractor, InteractorProtocol, XibProviderProtocol { + @IBInspectable open dynamic var title: String? + @IBInspectable open dynamic var xib: String? + + @objc public dynamic var formatter: ValueFormatterProtocol? + + @objc open dynamic var entity: ModelObjectProtocol? { + didSet { + didSetEntity(oldValue: oldValue) + } + } + + open func didSetEntity(oldValue: ModelObjectProtocol?) { + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectLinesInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectLinesInteractor.swift new file mode 100644 index 000000000..6968ad346 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectLinesInteractor.swift @@ -0,0 +1,180 @@ +// +// ObjectLinesInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 6/21/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Utilities + +@objc open class ObjectLinesInteractor: BaseInteractor, InteractorProtocol { + @objc public dynamic var entity: ModelObjectProtocol? { + didSet { + if entity !== oldValue { + didSetEntity(oldValue: oldValue) + } + } + } + + @objc public dynamic var lines: ListInteractor? + + private lazy var dollarFormatter: ValueFormatterProtocol = DollarValueFormatter() + private lazy var leverageFormatter: ValueFormatterProtocol = LeverageValueFormatter() + private lazy var amountFormatter: ValueFormatterProtocol = AmountValueFormatter() + private lazy var percentFormatter: ValueFormatterProtocol = PercentValueFormatter() + + @objc open dynamic var fields: [String: ObjectLineInteractor]? { + didSet { + didSetFields(oldValue: oldValue) + } + } + + open var jsonFile: String? { + return nil + } + + open func didSetEntity(oldValue: ModelObjectProtocol?) { + if entity !== oldValue { + fields = load() + } + } + + open func load() -> [String: ObjectLineInteractor]? { + if let entity = entity, let jsonFile = jsonFile, let json = JsonLoader.load(bundles: Bundle.particles, fileName: jsonFile) as? [String: Any] { + var fields = [String: ObjectLineInteractor]() + for (key, value) in json { + if let fieldJson = value as? [String: Any], let oneField = field(entity: entity, json: fieldJson) { + fields[key] = oneField + } + } + return fields + } else { + return nil + } + } + + open func field(entity: ModelObjectProtocol, json: [String: Any]) -> ObjectLineInteractor? { + let title = parser.asString(json["title"]) + let xib = parser.asString(json["xib"]) + switch parser.asString(json["type"]) { + case "options": + if let strings = json["strings"] as? [String], let options = json["options"] as? [String], let valueField = parser.asString(json["value"]) { + let objectField = parser.asString(json["object"]) + return self.options(entity: entity, title: title, strings: strings, options: options, objectField: objectField, valueField: valueField, xib: xib) + } else { + return nil + } + + case "bool": + if let valueField = parser.asString(json["value"]) { + let objectField = parser.asString(json["object"]) + return boolean(entity: entity, title: title, objectField: objectField, valueField: valueField, xib: xib) + } else { + return nil + } + + case "decimal": + if let valueField = parser.asString(json["value"]) { + let format = parser.asString(json["format"]) + let objectField = parser.asString(json["object"]) + return decimal(entity: entity, title: title, formatter: formatter(format: format), objectField: objectField, valueField: valueField, xib: xib) + } else { + return nil + } + + case "string": + if let valueField = parser.asString(json["value"]) { + let objectField = parser.asString(json["object"]) + return string(entity: entity, title: title, objectField: objectField, valueField: valueField, xib: xib) + } else { + return nil + } + + case "int": + if let valueField = parser.asString(json["value"]) { + let objectField = parser.asString(json["object"]) + return int(entity: entity, title: title, objectField: objectField, valueField: valueField, xib: xib) + } else { + return nil + } + + case "composit": + if let objectField1 = parser.asString(json["object1"]), let objectField2 = parser.asString(json["object2"]), let valueField1 = parser.asString(json["value1"]), let valueField2 = parser.asString(json["value2"]) { + let format = parser.asString(json["format"]) + return composit(entity: entity, title: title, formatter: formatter(format: format), objectField1: objectField1, valueField1: valueField1, objectField2: objectField2, valueField2: valueField2, xib: xib) + } else { + return nil + } + + default: + return nil + } + } + + open func formatter(format: String?) -> ValueFormatterProtocol? { + switch format { + case "dollar": + return dollarFormatter + + case "percent": + return percentFormatter + + case "leverage": + return leverageFormatter + + case "amount": + return amountFormatter + + default: + return nil + } + } + + private func composit(entity: ModelObjectProtocol?, title: String?, formatter: ValueFormatterProtocol?, objectField1: String?, valueField1: String, objectField2: String?, valueField2: String, xib: String? = nil) -> ObjectLineInteractor { + let line1 = decimal(entity: entity, title: nil, formatter: formatter, objectField: objectField1, valueField: valueField1, xib: nil) + let line2 = decimal(entity: entity, title: nil, formatter: formatter, objectField: objectField2, valueField: valueField2, xib: nil) + return ObjectCompositeLineInteractor.interactor(entity: entity, title: title, interactor1: line1, interactor2: line2, xib: xib) + } + + private func options(entity: ModelObjectProtocol?, title: String?, strings: [String], options: [String], objectField: String?, valueField: String, xib: String? = nil) -> ObjectLineInteractor { + return ObjectOptionsLineInteractor.interactor(entity: entity, title: title, strings: strings, options: options, objectField: objectField, valueField: valueField) + } + + private func decimal(entity: ModelObjectProtocol?, title: String?, formatter: ValueFormatterProtocol? = nil, objectField: String?, valueField: String, xib: String? = nil) -> ObjectLineInteractor { + return ObjectDecimalLineInteractor.interactor(entity: entity, title: title, formatter: formatter, objectField: objectField, valueField: valueField, xib: xib) + } + + private func boolean(entity: ModelObjectProtocol?, title: String?, objectField: String?, valueField: String, xib: String? = nil) -> ObjectLineInteractor { + return ObjectBooleanLineInteractor.interactor(entity: entity, title: title, objectField: objectField, valueField: valueField, xib: xib) + } + + private func string(entity: ModelObjectProtocol?, title: String?, objectField: String?, valueField: String, xib: String? = nil) -> ObjectLineInteractor { + return ObjectStringLineInteractor.interactor(entity: entity, title: title, objectField: objectField, valueField: valueField, xib: xib) + } + + private func int(entity: ModelObjectProtocol?, title: String?, objectField: String?, valueField: String, xib: String? = nil) -> ObjectLineInteractor { + return ObjectIntLineInteractor.interactor(entity: entity, title: title, objectField: objectField, valueField: valueField, xib: xib) + } + + open func didSetFields(oldValue: [String: ObjectLineInteractor]?) { + updateList() + } + + public let updateDebouncer = Debouncer() + open func updateList() { + let handler = updateDebouncer.debounce() + handler?.run({ [weak self] in + self?.reallyUpdateList() + }, delay: 0.0) + } + + open func reallyUpdateList() { + } + + open func add(to: inout [ObjectLineInteractor], key: String) { + if let field = fields?[key] { + to.append(field) + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectOptionLineInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectOptionLineInteractor.swift new file mode 100644 index 000000000..325032142 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectOptionLineInteractor.swift @@ -0,0 +1,55 @@ +// +// ObjectOptionLineInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 6/11/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Utilities + +@objc public class ObjectOptionsLineInteractor: ObjectValueLineInteractor { + public static func interactor(entity: ModelObjectProtocol?, title: String?, strings: [String], options: [String], objectField: String?, valueField: String, xib: String? = nil) -> ObjectOptionsLineInteractor { + let interactor = ObjectOptionsLineInteractor() + interactor.set(entity: entity, title: title, formatter: nil, objectField: objectField, valueField: valueField, xib: xib) + interactor.strings = strings + interactor.options = options + return interactor + } + + public var strings: [String]? + private var options: [String]? { + didSet { + updateLineValue() + } + } + + public var selectionIndex: Int? { + get { + return lineValue as? Int + } + set { + if let newValue = newValue, let options = options, let valueField = valueField, newValue < options.count, newValue != selectionIndex { + let option = options[newValue] + obj?.setValue(option, forKey: valueField) + } + } + } + + override open func updateLineValue() { + if let valueField = valueField { + if let index = index(of: parser.asString(obj?.value(forKey: valueField)), in: options), index != lineValue as? Int { + lineValue = index + } + } else { + lineValue = nil + } + } + + open func index(of value: String?, in options: [String]?) -> Int? { + if let value = value, let options = options { + return options.firstIndex(of: value) + } + return nil + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectValueLineInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectValueLineInteractor.swift new file mode 100644 index 000000000..8c51c27b7 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_Lines/ObjectValueLineInteractor.swift @@ -0,0 +1,189 @@ +// +// ObjectValueLineInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 6/10/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Utilities + +@objc open class ObjectValueLineInteractor: ObjectLineInteractor { + @IBInspectable open dynamic var objectField: String? { + didSet { + if objectField != oldValue { + if let oldValue = oldValue { + changeObservation(from: entity, to: nil, keyPath: oldValue) { _, _, _, _ in + } + } + if let objectField = objectField { + changeObservation(from: nil, to: entity, keyPath: objectField) { [weak self] _, _, _, _ in + self?.updateObject() + } + } + } + } + } + + @IBInspectable open dynamic var object2Field: String? { + didSet { + if object2Field != oldValue { + if let oldValue = oldValue { + changeObservation(from: obj, to: nil, keyPath: oldValue) { _, _, _, _ in + } + } + if let object2Field = object2Field { + changeObservation(from: nil, to: obj, keyPath: object2Field) { [weak self] _, _, _, _ in + self?.updateObject2() + } + } + } + } + } + + @IBInspectable open dynamic var valueField: String? { + didSet { + if valueField != oldValue { + if let oldValue = oldValue { + changeObservation(from: obj2, to: nil, keyPath: oldValue) { _, _, _, _ in + } + } + if let valueField = valueField { + changeObservation(from: nil, to: obj2, keyPath: valueField) { [weak self] _, _, _, _ in + self?.updateLineValue() + } + } + } + } + } + + @objc open dynamic var obj: NSObject? { + didSet { + if let object2Field = object2Field { + changeObservation(from: oldValue, to: obj, keyPath: object2Field) { [weak self] _, _, _, _ in + self?.updateObject2() + } + } else { + updateObject2() + } + } + } + + @objc open dynamic var obj2: NSObject? { + didSet { + if let valueField = valueField { + changeObservation(from: oldValue, to: obj, keyPath: valueField) { [weak self] _, _, _, _ in + self?.updateLineValue() + } + } else { + updateLineValue() + } + } + } + + @objc open dynamic var lineValue: Any? + + override open func didSetEntity(oldValue: ModelObjectProtocol?) { + super.didSetEntity(oldValue: oldValue) + if let objectField = objectField { + changeObservation(from: oldValue, to: entity, keyPath: objectField) { [weak self] _, _, _, _ in + self?.updateObject() + } + } else { + updateObject() + } + } + + open func updateObject() { + if let objectField = objectField, objectField != "self" { + obj = (entity as? NSObject)?.value(forKey: objectField) as? NSObject + } else { + obj = entity as? NSObject + } + } + + open func updateObject2() { + if let object2Field = object2Field, object2Field != "self" { + obj2 = obj?.value(forKey: object2Field) as? NSObject + } else { + obj2 = obj + } + } + + open func updateLineValue() { + if let valueField = valueField { + lineValue = obj?.value(forKey: valueField) + } else { + lineValue = nil + } + } + + public func set(entity: ModelObjectProtocol?, title: String?, formatter: ValueFormatterProtocol?, objectField: String?, valueField: String, xib: String? = nil) { + self.title = title + self.formatter = formatter + self.objectField = objectField + self.valueField = valueField + self.xib = xib + self.entity = entity + } +} + +@objc public class ObjectDecimalLineInteractor: ObjectValueLineInteractor { + public static func interactor(entity: ModelObjectProtocol?, title: String?, formatter: ValueFormatterProtocol? = nil, objectField: String?, valueField: String, xib: String? = nil) -> ObjectDecimalLineInteractor { + let interactor = ObjectDecimalLineInteractor() + interactor.set(entity: entity, title: title, formatter: formatter, objectField: objectField, valueField: valueField, xib: xib) + return interactor + } + + public var decimal: NSNumber? { + return parser.asDecimal(lineValue) + } +} + +@objc public class ObjectIntLineInteractor: ObjectValueLineInteractor { + public static func interactor(entity: ModelObjectProtocol?, title: String?, objectField: String?, valueField: String, xib: String? = nil) -> ObjectIntLineInteractor { + let interactor = ObjectIntLineInteractor() + interactor.set(entity: entity, title: title, formatter: nil, objectField: objectField, valueField: valueField, xib: xib) + return interactor + } + + public var int: NSNumber? { + return parser.asNumber(lineValue) + } +} + +@objc public class ObjectBooleanLineInteractor: ObjectValueLineInteractor { + public static func interactor(entity: ModelObjectProtocol?, title: String?, objectField: String?, valueField: String, xib: String? = nil) -> ObjectBooleanLineInteractor { + let interactor = ObjectBooleanLineInteractor() + interactor.set(entity: entity, title: title, formatter: nil, objectField: objectField, valueField: valueField, xib: xib) + return interactor + } + + public var boolean: NSNumber? { + return parser.asBoolean(lineValue) + } +} + +@objc public class ObjectStringLineInteractor: ObjectValueLineInteractor { + public static func interactor(entity: ModelObjectProtocol?, title: String?, objectField: String?, valueField: String, xib: String? = nil) -> ObjectStringLineInteractor { + let interactor = ObjectStringLineInteractor() + interactor.set(entity: entity, title: title, formatter: nil, objectField: objectField, valueField: valueField, xib: xib) + return interactor + } + + public var string: String? { + return parser.asString(lineValue) + } +} + +@objc public class ObjectWebLineInteractor: ObjectValueLineInteractor { + public static func interactor(entity: ModelObjectProtocol?, title: String?, objectField: String?, valueField: String, xib: String? = nil) -> ObjectWebLineInteractor { + let interactor = ObjectWebLineInteractor() + interactor.set(entity: entity, title: title, formatter: nil, objectField: objectField, valueField: valueField, xib: xib) + return interactor + } + + public var url: String? { + return parser.asString(lineValue) + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_List/DataListInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_List/DataListInteractor.swift new file mode 100644 index 000000000..0dfa8da0d --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_List/DataListInteractor.swift @@ -0,0 +1,25 @@ +// +// DataListInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/27/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +@objc open class DataListInteractor: LocalJsonCacheInteractor { + @objc open dynamic var data: [ModelObjectProtocol]? + + override open func receive(io: IOProtocol?, object: Any?, loadTime: Date?, error: Error?) { + if error == nil { + if var data = data { + if let newObjects = object as? [ModelObjectProtocol] { + data.append(contentsOf: newObjects) + } + } else { + data = object as? [ModelObjectProtocol] + } + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_List/DataPoolInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_List/DataPoolInteractor.swift new file mode 100644 index 000000000..56a86ea8f --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_List/DataPoolInteractor.swift @@ -0,0 +1,269 @@ +// +// DataPoolInteractor.swift +// InteractorLib +// +// Created by John Huang on 11/10/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +@objc open class DataPoolInteractor: LocalJsonCacheInteractor { + @IBInspectable open var autoReload: Bool = false { + didSet { + didSetAutoReload(oldValue: oldValue) + } + } + + @objc open dynamic var appState: AppState? { + didSet { + didSetAppState(oldValue: oldValue) + } + } + + @objc open dynamic var network: NetworkConnection? { + didSet { + didSetNetwork(oldValue: oldValue) + } + } + + @objc open dynamic var data: [String: ModelObjectProtocol]? { + didSet { + didSetData(oldValue: oldValue) + } + } + + @objc open dynamic var transformed: [String: ModelObjectProtocol]? { + didSet { + didSetTransformed(oldValue: oldValue) + } + } + + @objc open dynamic var sequence: [ModelObjectProtocol]? { + didSet { + didSetSequence(oldValue: oldValue) + } + } + + @objc open dynamic var sequenceTransformed: [ModelObjectProtocol]? { + didSet { + didSetSequenceTransformed(oldValue: oldValue) + } + } + + public var sequential: Bool { + return false + } + + @objc open dynamic var isLoading: Bool = false + + internal var saveDebouncer: Debouncer = Debouncer() + internal var isLoadingDebouncer: Debouncer = Debouncer() + + open var lastLoadTime: Date? + + open func didSetAutoReload(oldValue: Bool) { + if autoReload != oldValue { + if autoReload { + appState = AppState.shared + network = NetworkConnection.shared + } else { + appState = nil + network = nil + } + } + } + + override open func loadSelf() { + isLoading = true + super.loadSelf() + } + + open func didSetAppState(oldValue: AppState?) { + changeObservation(from: oldValue, to: appState, keyPath: #keyPath(AppState.background)) { [weak self] _, _, _, animated in + if animated { + self?.maybeAutoReload() + } + } + } + + open func didSetNetwork(oldValue: NetworkConnection?) { + changeObservation(from: oldValue, to: network, keyPath: #keyPath(NetworkConnection.connected)) { [weak self] _, _, _, animated in + if animated { + self?.maybeAutoReload() + } + } + } + + private func maybeAutoReload() { + if autoReload, appState?.background == false, network?.connected == true { + if let lastLoadTime = lastLoadTime { + if Date().timeIntervalSince(lastLoadTime) > 30.0 { + doAutoReload() + } + } else { + doAutoReload() + } + } + } + + open func doAutoReload() { + load() + } + + open func didSetData(oldValue: [String: ModelObjectProtocol]?) { + transformed = transform(data: data) + sequence = sequence(data: data) + } + + open func didSetSequence(oldValue: [ModelObjectProtocol]?) { + sequenceTransformed = transform(sequence: sequence) + } + + open func transform(data: [String: ModelObjectProtocol]?) -> [String: ModelObjectProtocol]? { + return data + } + + open func transform(sequence: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + return sequence + } + + open func didSetTransformed(oldValue: [String: ModelObjectProtocol]?) { + } + + open func didSetSequenceTransformed(oldValue: [ModelObjectProtocol]?) { + } + + override open func receive(io: IOProtocol?, object: Any?, loadTime: Date?, error: Error?) { + if let error = error { + if (error as NSError).code == 403 || (error as NSError).code == 204 { + receive(io: io, parsedSequence: nil) + } else { + let temp = data + data = temp + } + } else { + if let entities = object as? [ModelObjectProtocol] { + receive(io: io, parsedSequence: entities) + } else { + receive(io: io, parsedSequence: nil) + } + lastLoadTime = Date() + } + checkLoading() + } + +// open func receive(io: IOProtocol?, parsedData: [String: ModelObjectProtocol]?) { +// data = parsedData +// save() +// } + + open func receive(io: IOProtocol?, parsedSequence: [ModelObjectProtocol]?) { + let sequence = sequence(sequence: ordered(sequence: parsedSequence)) + data = map(sequence: sequence) + self.sequence = sequence + save() + } + + open func sequence(sequence: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + return sequence + } + + open func ordered(sequence: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + return sequence + } + + open func sequence(data: [String: ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + if let data = data { + return Array(data.values) + } else { + return nil + } + } + + open func map(sequence: [ModelObjectProtocol]?) -> [String: ModelObjectProtocol]? { + if let entities = sequence { + var parsed = [String: ModelObjectProtocol]() + for entity in entities { + if let key = entity.key { + if let key = key { + parsed[key] = entity + } + } + } + return parsed + } else { + return nil + } + } + + override open func save() { + if let handler = saveDebouncer.debounce() { + handler.run({ [weak self] in + self?.loader?.save(object: self?.sequence) + }, delay: 1) + } + } + + open func checkLoading() { + isLoadingDebouncer.debounce()?.run({ [weak self] in + self?.reallyCheckLoading() + }, delay: 0.01) + } + + open func reallyCheckLoading() { + isLoading = loader?.isLoading ?? false + } +} + +@objc public protocol RangeProtocol: NSObjectProtocol { + var low: NSNumber? { get set } + var high: NSNumber? { get set } + + func lowOf(obj: ModelObjectProtocol) -> NSNumber? + func highOf(obj: ModelObjectProtocol) -> NSNumber? +} + +extension RangeProtocol { + public func low(number1: NSNumber?, number2: NSNumber?) -> NSNumber? { + if let number1 = number1 { + if let number2 = number2 { + return (number2.doubleValue < number1.doubleValue) ? number2 : number1 + } else { + return number1 + } + } else { + return number2 + } + } + + public func high(number1: NSNumber?, number2: NSNumber?) -> NSNumber? { + if let number1 = number1 { + if let number2 = number2 { + return (number2.doubleValue > number1.doubleValue) ? number2 : number1 + } else { + return number1 + } + } else { + return number2 + } + } + + public func range(debouncer: Debouncer, data: [String: ModelObjectProtocol]?) { + if let data = data { + var low = low + var high = high + debouncer.debounce()?.run(background: { [weak self] in + if let self = self { + for (_, value) in data { + low = self.low(number1: low, number2: self.lowOf(obj: value)) + high = self.high(number1: high, number2: self.highOf(obj: value)) + } + } + }, final: { [weak self] in + self?.low = low + self?.high = high + }, delay: 0.0) + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_List/KeyedDataInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_List/KeyedDataInteractor.swift new file mode 100644 index 000000000..ff2ffc04c --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_List/KeyedDataInteractor.swift @@ -0,0 +1,70 @@ +// +// KeyedDataInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 10/9/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Utilities + +@objc open class KeyedDataInteractor: DataPoolInteractor { + @objc public dynamic var filter: String? { + didSet { + if filter != oldValue { + filterChanged() + } + } + } + + @objc open dynamic var list: ListInteractor? + + var transformDebouncer: Debouncer = Debouncer() + + open func filterChanged() { + transformedChanged() + } + + open func transformedChanged() { + let transformed = self.transformed + var filtered: [String: ModelObjectProtocol]? + var sorted: [ModelObjectProtocol]? + + let handler = transformDebouncer.debounce() + handler?.run(background: { [weak self] in + if let self = self { + filtered = self.filter(objects: transformed, filter: self.filter) + } + }, then: { [weak self] in + if let self = self { + sorted = self.sort(objects: filtered) + } + }, final: { [weak self] in + if let self = self { + if let sorted = sorted { let list = self.list ?? ListInteractor() + list.sync(sorted) + self.list = list + } else { + self.list = nil + } + } + }, delay: 0.1) + } + + open func filter(objects: [String: ModelObjectProtocol]?, filter: String?) -> [String: ModelObjectProtocol]? { + let filter = filter?.lowercased().trim() + return transformed?.compactMapValues({ (object) -> ModelObjectProtocol? in + self.filter(object: object, lowercased: filter) ? object : nil + }) + } + + open func sort(objects: [String: ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + return objects?.values.sorted(by: { (object1, object2) -> Bool in + object1.order?(ascending: object2) ?? true + }) + } + + open func filter(object: ModelObjectProtocol, lowercased filter: String?) -> Bool { + return (object as? FilterableProtocol)?.filter(lowercased: filter) ?? false + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_List/ListInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_List/ListInteractor.swift new file mode 100644 index 000000000..82e342245 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_List/ListInteractor.swift @@ -0,0 +1,81 @@ +// +// ListInteractor.swift +// InteractorLib +// +// Created by John Huang on 10/9/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +import Utilities + +@objc open class ListInteractor: BaseInteractor, ModelListProtocol { + + @objc open dynamic var loading: Bool = false + @objc open dynamic var title: String? + open var parent: ModelObjectProtocol? + + open var displayTitle: String? { + return title ?? parent?.displayTitle ?? nil + } + + open var displaySubtitle: String? { + return parent?.displaySubtitle ?? nil + } + + open var displayImageUrl: String? { + return parent?.displayImageUrl ?? nil + } + + @objc open dynamic var list: [ModelObjectProtocol]? + @objc open dynamic var prefix: ModelObjectProtocol? + @objc open dynamic var postfix: ModelObjectProtocol? + @objc open var count: Int { + return list?.count ?? 0 + } + + private func indexOf(_ sourceObject: ModelObjectProtocol?, in destination: NSArray?, startingAt startIndex: Int) -> Int? { + let index = destination?.indexOfObject(passingTest: { (object: Any, index: Int, stop: UnsafeMutablePointer) -> Bool in + if index < startIndex { + return false + } + if self.compare(object as? (ModelObjectProtocol), with: sourceObject) { + stop.pointee = true + return true + } + return false + }) + if index != NSNotFound { + return index + } + return nil + } + + private func compare(_ destination: ModelObjectProtocol?, with source: ModelObjectProtocol?) -> Bool { + if destination === source { + return true + } else { + return destination?.isEqual(source) ?? false + } + } + + open func sync(_ list: [ModelObjectProtocol]?) { + if !(self.list?.containsSame(as: list) ?? false) { + self.list = list + } + } + + open func move(from: Int, to: Int, update: Bool = false) { + if let item = list?[from], from != to { + if var list = self.list { + list.remove(at: from) + list.insert(item, at: to) + self.list = list + } + } + } + + public func order(ascending another: ModelObjectProtocol?) -> Bool { + return stringAscending(string: title, another: (another as? ListInteractor)?.title) ?? false + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_List/MapDataPoolInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_List/MapDataPoolInteractor.swift new file mode 100644 index 000000000..fb1cc8eee --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_List/MapDataPoolInteractor.swift @@ -0,0 +1,65 @@ +// +// MapDataPoolInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 1/25/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Utilities + +@objc open class MapDataPoolInteractor: DataPoolInteractor { + private var mapProvider: MapProviderProtocol? { + didSet { + changeObservation(from: oldValue, to: mapProvider, keyPath: #keyPath(MapProviderProtocol.mapRect), block: { [weak self] _, _, _, _ in + if let self = self { + self.visibleMapRect = self.mapProvider?.mapRect + } + }) + } + } + + @objc public dynamic var visibleMapRect: MapArea? { + didSet { + if visibleMapRect != oldValue { + loading = true + } + } + } + + @objc public dynamic var loading: Bool = false { + didSet { + if loading != oldValue { + if loading { + loadingTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false, block: { [weak self] _ in + if let self = self { + self.load() + self.loading = false + } + }) + } else { + loadingTimer = nil + } + } + } + } + + open override var loadingParams: [String: Any]? { + if let northWest = mapProvider?.mapRect?.topLeft, let southEast = mapProvider?.mapRect?.bottomRight { + return formatParam(northWest: northWest, southEast: southEast) + } + return nil + } + + @objc public dynamic var loadingTimer: Timer? + + open override func load() { + if loadingParams != nil { + super.load() + } + } + + open func formatParam(northWest: MapPoint, southEast: MapPoint) -> [String: Any]? { + return nil + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_List/SelectableListInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_List/SelectableListInteractor.swift new file mode 100644 index 000000000..907589d40 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_List/SelectableListInteractor.swift @@ -0,0 +1,13 @@ +// +// SelectableListInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 9/2/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +@objc open class SelectableListInteractor: ListInteractor, SelectableProtocol { + @objc public dynamic var isSelected: Bool = false +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_List/SequentialDataPoolInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_List/SequentialDataPoolInteractor.swift new file mode 100644 index 000000000..5d53b0bea --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_List/SequentialDataPoolInteractor.swift @@ -0,0 +1,198 @@ +// +// SequentialDataPoolInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 11/5/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Utilities + +@objc open class SequentialDataPoolInteractor: DataPoolInteractor { + public class Cursor: NSObject { + private var objects: [ModelObjectProtocol] + + public init(objects: [ModelObjectProtocol]) { + self.objects = objects + if objects.count > 0 { + index = 0 + current = objects[0] + } + super.init() + } + + public var index: Int? { + didSet { + if index != oldValue { + if let index = index { + current = objects[index] + } else { + current = nil + } + } + } + } + + public var current: ModelObjectProtocol? + + public func advance() { + if let index = index, index + 1 < objects.count { + self.index = index + 1 + } else { + index = nil + } + } + + public func rest() -> [ModelObjectProtocol]? { + if let index = index { + return Array(objects[index...]) + } else { + return nil + } + } + } + + override public var sequential: Bool { + return true + } + + public var inputReversed: Bool = false + + public init(inputReversed: Bool = false) { + self.inputReversed = inputReversed + super.init() + } + + public init(key: String? = nil, default defaultJson: String? = nil, inputReversed: Bool = false) { + self.inputReversed = inputReversed + super.init(key: key, default: defaultJson) + } + + override open func sequence(sequence: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + if let sequence = sequence { + if let existing = self.sequence { + return merge(existing, sequence) + } else { + return sequence + } + } else { + return self.sequence + } + } + + override public func ordered(sequence: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + if let sequence = sequence { + if inputReversed { + return Array(sequence.reversed()) + } else { + return sequence + } + } else { + return nil + } + } + + override open func sequence(data: [String: ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + return nil + } + + open func merge(_ sequence1: [ModelObjectProtocol], _ sequence2: [ModelObjectProtocol]) -> [ModelObjectProtocol] { + if let first1 = sequence1.first, let first2 = sequence2.first, let last1 = sequence1.last, let last2 = sequence2.last { + if last1.order?(ascending: first2) == true { + var merged = sequence1 + merged.append(contentsOf: sequence2) + return merged + } else if last2.order?(ascending: first1) == true { + var merged = sequence2 + merged.append(contentsOf: sequence1) + return merged + } else { + if first1.order?(ascending: first2) == true { + if last1.order?(ascending: last2) == true { + return binaryMerge(sequence1, sequence2) + } else { + return sequence1 + } + } else if first2.order?(ascending: first1) == true { + if last2.order?(ascending: last1) == true { + return binaryMerge(sequence2, sequence1) + } else { + return sequence2 + } + } else { + return sequence1 + } + } + } else { + return sequence1.count > 0 ? sequence1 : sequence2 + } + } + + open func binaryMerge(_ sequence1: [ModelObjectProtocol], _ sequence2: [ModelObjectProtocol]) -> [ModelObjectProtocol] { + // first sequence is in front of the second + if let last1 = sequence1.last { + if let index = sequence2.binarySearch(for: { element2 in + return compare(element2, last1) + }) { + if index < sequence2.count - 1 { + let nextIndex = index + 1 + let rest = sequence2[nextIndex...] + var merged = sequence1 + merged.append(contentsOf: rest) + return merged + } else { + return sequence1 + } + } else { + return walk(sequence1, sequence2) + } + } else { + return sequence2 + } + } + + open func compare(_ item1: ModelObjectProtocol, _ item2: ModelObjectProtocol) -> ComparisonResult { + if item1 === item2 { + return .orderedSame + } else { + let ascending = item1.order?(ascending: item2) + if ascending == true { + return .orderedAscending + } else if ascending == false { + return .orderedDescending + } else { + return .orderedAscending + } + } + } + + open func walk(_ sequence1: [ModelObjectProtocol], _ sequence2: [ModelObjectProtocol]) -> [ModelObjectProtocol] { + var merged = [ModelObjectProtocol]() + let cursor1 = Cursor(objects: sequence1) + let cursor2 = Cursor(objects: sequence2) + while let obj1 = cursor1.current, let obj2 = cursor2.current { + if obj1 === obj2 { + merged.append(obj1) + cursor1.advance() + cursor2.advance() + } else { + if obj1.order?(ascending: obj2) ?? false { + merged.append(obj1) + cursor1.advance() + } else { + merged.append(obj2) + cursor2.advance() + } + } + } + + if let rest = cursor1.rest() { + merged.append(contentsOf: rest) + } + if let rest = cursor2.rest() { + merged.append(contentsOf: rest) + } + + return merged + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_List/XibJsonFile.swift b/ParticlesKit/ParticlesKit/_Interactor/_List/XibJsonFile.swift new file mode 100644 index 000000000..33acdb894 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_List/XibJsonFile.swift @@ -0,0 +1,107 @@ +// +// XibJsonFile.swift +// UIToolkits +// +// Created by John Huang on 10/10/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +public class XibJsonFile: NSObject { + public static var parserOverwrite: Parser? + + override public var parser: Parser { + return XibJsonFile.parserOverwrite ?? super.parser + } + + private var xibFiles: [String: Any]? + + private var cache: [String: Any]? + + public init(fileName: String, bundle: Bundle) { + super.init() + xibFiles = JsonLoader.load(bundle: bundle, fileName: fileName) as? [String: Any] + } + + public init(fileName: String, bundles: [Bundle]) { + super.init() + xibFiles = bundles.compactMap({ (bundle) -> [String: Any]? in + JsonLoader.load(bundle: bundle, fileName: fileName) as? [String: Any] + }).first + } + + public func xibFile(object: ModelObjectProtocol?) -> String? { + let value = xibValue(model: object) + return xibValue(object: object, value: value) + } + + func xibValue(model: ModelObjectProtocol?) -> Any? { + let interactor = model as? InteractorProtocol + var value = xibValue(object: interactor) + if value == nil { + let entity = interactor?.entity ?? model + value = xibValue(object: entity) + } + return value + } + + private func xibValue(object: ModelObjectProtocol?) -> Any? { + if let xibFiles = self.xibFiles, let object = object as? NSObject { + let classNames = object.classNames() + var value: Any? + for className in classNames { + value = xibFiles[className.pathExtension] ?? xibFiles[className] // className contains module name + if value != nil { + break + } + } + if let value = value { + if let string = value as? String { + return string + } else { + return parser.conditioned(value) + } + } + } + return nil + } + + private func cache(string: String) -> Any? { + if let value = cache?[string] { + return value + } else { + var cache = cache ?? [String: Any]() + let result = parser.conditioned(string) + cache[string] = result + self.cache = cache + return result + } + } + + private func xibValue(object: ModelObjectProtocol?, value: Any?) -> String? { + if let string = value as? String { + return string + } else if let dictionary = value as? [String: Any] { + var string: String? + var defaultString: String? + for (key, value) in dictionary { + if key == "default" { + defaultString = value as? String + } else if let dictionary = value as? [String: Any], let object = object as? (NSObject & ModelObjectProtocol) { + if let keyPathValue = parser.asString(object.value(forKey: key)) { + string = xibValue(object: object, value: dictionary[keyPathValue]) + if string != nil { + break + } + } + } + } + if string == nil { + string = defaultString + } + return string + } + return nil + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_LocalData/LocalDebugCacheInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_LocalData/LocalDebugCacheInteractor.swift new file mode 100644 index 000000000..9a5a75007 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_LocalData/LocalDebugCacheInteractor.swift @@ -0,0 +1,35 @@ +// +// LocalFeatureFlagsCacheInteractor.swift +// TrackingKit +// +// Created by Qiang Huang on 11/21/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +@objc public final class LocalDebugCacheInteractor: LocalEntityCacheInteractor, SingletonProtocol, DebugProtocol { + public static var shared: LocalDebugCacheInteractor = { + LocalDebugCacheInteractor(key: "debug", default: "debug_default.json") + }() + + public static func mock() -> LocalDebugCacheInteractor { + let debug = shared + debug.key = "mock" + debug.debug = ["api_replay": "p", "integration_test": "t"] + return debug + } + + public override func awakeAfter(using aDecoder: NSCoder) -> Any? { + return LocalDebugCacheInteractor.shared + } + + public var debug: [String: Any]? { + get { + return (entity as? DictionaryEntity)?.force.data + } + set { + (entity as? DictionaryEntity)?.data = newValue + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_LocalData/LocalEntityCacheInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_LocalData/LocalEntityCacheInteractor.swift new file mode 100644 index 000000000..fca3e7196 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_LocalData/LocalEntityCacheInteractor.swift @@ -0,0 +1,60 @@ +// +// LocalEntityCacheInteractor.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/25/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +@objc open class LocalEntityCacheInteractor: LocalJsonCacheInteractor, InteractorProtocol { + @objc open dynamic var entity: ModelObjectProtocol? { + didSet { + didSetEntity(oldValue: oldValue) + } + } + + public var dictionaryEntity: DictionaryEntity? { + return entity as? DictionaryEntity + } + + open func didSetEntity(oldValue: ModelObjectProtocol?) { + changeObservation(from: oldValue, to: entity, keyPath: #keyPath(DictionaryEntity.data)) { [weak self] _, _, _, animated in + self?.save() + } + } + + override open func createLoader() -> LoaderProtocol? { + if let path = path { + return Loader(path: path, io: [JsonDocumentFileCaching()], cache: self) + } + return nil + } + + override open func entity(from data: [String: Any]?) -> ModelObjectProtocol? { + return entity ?? entityObject() + } + + open func entityObject() -> ModelObjectProtocol { + return DictionaryEntity() + } + + override open func receive(io: IOProtocol?, object: Any?, loadTime: Date?, error: Error?) { + if error == nil { + process(object: object as? (ModelObjectProtocol)) + } + } + + open func process(object: ModelObjectProtocol?) { + if let object = object { + entity = object + } else { + entity = entityObject() + } + } + + override open func save() { + loader?.save(object: entity) + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_LocalData/LocalFeatureFlagsCacheInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_LocalData/LocalFeatureFlagsCacheInteractor.swift new file mode 100644 index 000000000..8362d0e4d --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_LocalData/LocalFeatureFlagsCacheInteractor.swift @@ -0,0 +1,61 @@ +// +// LocalFeatureFlagsCacheInteractor.swift +// TrackingKit +// +// Created by Qiang Huang on 11/21/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +@objc public final class LocalFeatureFlagsCacheInteractor: LocalEntityCacheInteractor, SingletonProtocol, FeatureFlagsProtocol { + public static var shared: LocalFeatureFlagsCacheInteractor = { + LocalFeatureFlagsCacheInteractor(key: "features", default: "features_default.json") + }() + + public static func mock() -> LocalFeatureFlagsCacheInteractor { + let featurFlags = shared + featurFlags.key = "mock" + return featurFlags + } + + override public func awakeAfter(using aDecoder: NSCoder) -> Any? { + return LocalFeatureFlagsCacheInteractor.shared + } + + public var featureFlags: [String: Any]? { + get { + return (entity as? DictionaryEntity)?.force.data + } + set { + (entity as? DictionaryEntity)?.data = newValue + } + } + + public func refresh(completion: @escaping () -> Void) { + completion() + } + + public func activate(completion: @escaping () -> Void) { + completion() + } + + public func isOn(feature: String) -> Bool? { + featureFlags?[feature] as? Bool + } + + public func value(feature: String) -> String? { + if let value = featureFlags?[feature] as? String { + return value + } + return nil + } + + public func customized() -> Bool { + #if DEBUG + return false + #else + return (featureFlags?.count ?? 0) > 0 + #endif + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_LocalData/LocalMapCacheInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_LocalData/LocalMapCacheInteractor.swift new file mode 100644 index 000000000..d949e93e8 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_LocalData/LocalMapCacheInteractor.swift @@ -0,0 +1,44 @@ +// +// LocalFeatureFlagsCacheInteractor.swift +// TrackingKit +// +// Created by Qiang Huang on 11/21/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +@objc public protocol MapAppProtocol: NSObjectProtocol { + var mapUrl: String? { get set } +} + +@objc public final class LocalMapCacheInteractor: LocalEntityCacheInteractor, SingletonProtocol, MapAppProtocol { + public static var shared: LocalMapCacheInteractor = { + LocalMapCacheInteractor(key: "map", default: nil) + }() + + public override func awakeAfter(using aDecoder: NSCoder) -> Any? { + return LocalMapCacheInteractor.shared + } + + public var mapUrl: String? { + get { + return (entity as? DictionaryEntity)?.data?["map"] as? String + } + set { + if mapUrl != newValue { + (entity as? DictionaryEntity)?.force.data?["map"] = newValue + save() + } + } + } + + public var map: [String: Any]? { + get { + return (entity as? DictionaryEntity)?.force.data + } + set { + (entity as? DictionaryEntity)?.data = newValue + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_Object/InteractorProtocol.swift b/ParticlesKit/ParticlesKit/_Interactor/_Object/InteractorProtocol.swift new file mode 100644 index 000000000..ad73529d1 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_Object/InteractorProtocol.swift @@ -0,0 +1,26 @@ +// +// ObjectInteractor.swift +// InteractorLib +// +// Created by Qiang Huang on 10/10/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import RoutingKit + +@objc public protocol ActionProtocol: ModelObjectProtocol { + @objc var title: String? { get } + @objc var subtitle: String? { get } + @objc var detail: String? { get } + @objc var image: String? { get } + @objc var routing: RoutingRequest? { get } + @objc var detailRouting: RoutingRequest? { get } +} + +@objc public protocol InteractorProtocol: ModelObjectProtocol { + @objc var entity: ModelObjectProtocol? { get set } +} + +@objc public protocol LoadingInteractorProtocol: InteractorProtocol { + var objectKey: String? { get set } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_Object/LoadingObjectInteractor.swift b/ParticlesKit/ParticlesKit/_Interactor/_Object/LoadingObjectInteractor.swift new file mode 100644 index 000000000..3ba62b38c --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_Object/LoadingObjectInteractor.swift @@ -0,0 +1,44 @@ +// +// ObjectInteractor.swift +// InteractorLib +// +// Created by Qiang Huang on 11/23/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +@objc open class LoadingObjectInteractor: BaseInteractor, LoadingInteractorProtocol { + open var listManager: DataPoolInteractor? + open var loader: LoaderProtocol? + + open var objectKey: String? { + didSet { + if objectKey != oldValue { + loadEntity() + } + } + } + + open var loadingParams: [String: Any]? { + return nil + } + + @objc open dynamic var entity: ModelObjectProtocol? + + open func loadEntity() { + if let objectKey = objectKey { + entity = listManager?.data?[objectKey] + } else { + entity = nil + } + + if let loadingParams = loadingParams, let objectKey = objectKey { + loader?.load(params: loadingParams, completion: { [weak self] (io: IOProtocol?, entity: Any?, _: Date?, _: Error?) in + if self?.objectKey == objectKey { + self?.entity = entity as? (ModelObjectProtocol) + } + }) + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Interactor/_Protocol/FilteredListInteractorProtocol.swift b/ParticlesKit/ParticlesKit/_Interactor/_Protocol/FilteredListInteractorProtocol.swift new file mode 100644 index 000000000..5ffe8ee7c --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Interactor/_Protocol/FilteredListInteractorProtocol.swift @@ -0,0 +1,21 @@ +// +// FilteredListInteractorProtocol.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/19/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public protocol FilteredListInteractorProtocol: NSObjectProtocol { + var onlyShowLiked: Bool { get set } + var liked: LikedObjectsProtocol? { get set } + var filters: FilterEntity? { get set } + var filterText: String? { get set } + var data: [ModelObjectProtocol]? { get } +} + +public protocol FilteredObjectProtocol: NSObjectProtocol { + func filter(text: String?) -> Bool +} diff --git a/ParticlesKit/ParticlesKit/_Loader/Loader.swift b/ParticlesKit/ParticlesKit/_Loader/Loader.swift new file mode 100644 index 000000000..4a497a3fa --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Loader/Loader.swift @@ -0,0 +1,182 @@ +// +// Loader.swift +// ParticlesKit +// +// Created by John Huang on 12/29/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +open class Loader: NSObject, LoaderProtocol where EntityClass: NSObject & ModelObjectProtocol & ParsingProtocol { + @objc public dynamic var isLoading: Bool { + let loadingIo = io.first { io in + io.isLoading + } + return loadingIo !== nil + } + + internal var path: String + internal var io: [IOProtocol] + internal var fields: [String]? + internal var loadTime: Date? + internal var lastPriority: Int? + internal var readerDebounce: Debouncer = Debouncer() + internal var writerDebounce: Debouncer = Debouncer() + public weak var cache: LocalCacheProtocol? + + public init(path: String, io: [IOProtocol], fields: [String]? = nil, cache: LocalCacheProtocol? = nil) { + self.path = path + self.io = io + self.fields = fields + self.cache = cache + super.init() + } + + open func load(params: [String: Any]?, completion: LoaderCompletionHandler?) { + if let handler = readerDebounce.debounce() { + handler.run({ [weak self] in + if let self = self { + self.lastPriority = nil + for i in 0 ..< self.io.count { + let ioLoader = self.io[i] + ioLoader.priority = i + ioLoader.load(path: self.path, params: params) { [weak self] (data: Any?, _: Any?, priority: Int, error: Error?) in + handler.run({ [weak self] in + if let self = self { + let lastPriority = self.lastPriority ?? -1 + if priority > lastPriority { + self.lastPriority = priority + if error == nil { + self.parse(io: ioLoader, data: data, error: error, completion: completion) + } else { + completion?(ioLoader, data, nil, error) + } + } + } + }, delay: nil) + } + } + } + }, delay: nil) + } + } + + open func parse(io: IOProtocol?, data: Any?, error: Error?, completion: LoaderCompletionHandler?) { + if let entities = parseEntities(data: data) { + completion?(io, entities, loadTime, error) + } else if let entity = parseEntity(data: data) { + completion?(io, entity, loadTime, error) + } else { + completion?(io, nil, loadTime, error) + } + } + + open func parseEntities(data: Any?) -> [ModelObjectProtocol]? { + if let result = result(data: data) { + if let array = result as? [Any] { + var entities = [ModelObjectProtocol]() + for entityData in array { + if let entity = entity(data: entityData) { + entities.append(entity) + } + } + return entities + } else if let map = result as? [String: [String: Any]] { + var entities = [ModelObjectProtocol]() + for (key, entityData) in map { + let merged = self.merge(data: entityData, key: key) + if let entity = entity(data: merged) { + entities.append(entity) + } + } + return entities + } + } + return nil + } + + open func merge(data: [String: Any], key: String) -> [String: Any] { + var modified = data + modified["map_key"] = key + return modified + } + + open func list(data: Any?) -> [[String: Any]]? { + return data as? [[String: Any]] + } + + open func map(data: Any?) -> [String: [String: Any]]? { + return data as? [String: [String: Any]] + } + + open func result(data: Any?) -> Any? { + return list(data: data) ?? map(data: data) + } + + private func parseEntity(data: Any?) -> ModelObjectProtocol? { + if let result = data as? [String: Any] { + return entity(data: result) + } + return nil + } + + open func entity(data: Any?) -> ModelObjectProtocol? { + if let entityData = data as? [String: Any] { + let obj = entity(from: entityData) + (obj as? ParsingProtocol)?.parse?(dictionary: entityData) + return obj + } + return nil + } + + open func entity(from data: [String: Any]?) -> ModelObjectProtocol { + if let entity = cache?.entity(from: data) { + return entity + } + return createEntity() + } + + open func createEntity() -> ModelObjectProtocol { + return EntityClass() + } + + open func save(object: Any?) { + if lastPriority == io.count - 1 { + if let entity = object as? JsonPersistable, let data = entity.json { + save(data: data) + } else if let entities = object as? [JsonPersistable] { + var data = [[String: Any]]() + for entity in entities { + if let entityData = entity.json { + data.append(entityData) + } + } + save(data: data) + } else if let entities = object as? [String: JsonPersistable] { + var data = [[String: Any]]() + for (_, entity) in entities { + if let entityData = entity.json { + data.append(entityData) + } + } + save(data: data) + } + } + } + + open func save(data: Any?) { + if let handler = writerDebounce.debounce() { + handler.run({ [weak self] in + if let self = self { + for i in 0 ..< self.io.count { + let ioLoader = self.io[i] + if !(ioLoader is ApiProtocol) { + ioLoader.save(path: self.path, params: nil, data: data, completion: nil) + } + } + } + }, delay: nil) + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Loader/LoaderProtocol.swift b/ParticlesKit/ParticlesKit/_Loader/LoaderProtocol.swift new file mode 100644 index 000000000..3ec81d4e5 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Loader/LoaderProtocol.swift @@ -0,0 +1,21 @@ +// +// LoaderProtocol.swift +// LoaderLib +// +// Created by Qiang Huang on 10/11/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public protocol LoaderProtocol { + var isLoading: Bool { get } + func load(params: [String: Any]?, completion: LoaderCompletionHandler?) + func parse(io: IOProtocol?, data: Any?, error: Error?, completion: LoaderCompletionHandler?) + func save(object: Any?) + func createEntity() -> ModelObjectProtocol +} + +// loadingTime is used for differentiated loading +// if error is not nil, something happened +public typealias LoaderCompletionHandler = (_ io: IOProtocol?, _ loadedObject: Any?, _ loadTime: Date?, _ error: Error?) -> Void diff --git a/ParticlesKit/ParticlesKit/_Loader/LoaderProvider.swift b/ParticlesKit/ParticlesKit/_Loader/LoaderProvider.swift new file mode 100644 index 000000000..a2c2a37d8 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Loader/LoaderProvider.swift @@ -0,0 +1,19 @@ +// +// LoaderProvider.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/30/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +public protocol LoaderProviderProtocol { + func loader(tag: String, cache: LocalCacheProtocol?) -> LoaderProtocol? + func localLoader(path: String, cache: LocalCacheProtocol?) -> LoaderProtocol? + func localAsyncLoader(path: String, cache: LocalCacheProtocol?) -> LoaderProtocol? +} + +public class LoaderProvider { + public static var shared: LoaderProviderProtocol? +} diff --git a/ParticlesKit/ParticlesKit/_Map/MapProvider.swift b/ParticlesKit/ParticlesKit/_Map/MapProvider.swift new file mode 100644 index 000000000..28bf2986e --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Map/MapProvider.swift @@ -0,0 +1,17 @@ +// +// MapProvider.swift +// ParticlesKit +// +// Created by Qiang Huang on 1/25/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Utilities + +@objc public protocol MapProviderProtocol: NSObjectProtocol { + @objc var mapRect: MapArea? { get } +} + +public class MapProvider { + public static var shared: MapProviderProtocol? +} diff --git a/ParticlesKit/ParticlesKit/_Map/PlacemarkProtocols.swift b/ParticlesKit/ParticlesKit/_Map/PlacemarkProtocols.swift new file mode 100644 index 000000000..e1bd28d72 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Map/PlacemarkProtocols.swift @@ -0,0 +1,15 @@ +// +// PlacemarkProtocols.swift +// ParticlesKit +// +// Created by Qiang Huang on 8/29/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import MapKit + +public protocol PlacemarkProtocol: AnnotationProtocol { + var color: String? { get } + var imageUrl: String? { get } + var placemarkName: String? { get } +} diff --git a/ParticlesKit/ParticlesKit/_Presenter/XibFileLoader.swift b/ParticlesKit/ParticlesKit/_Presenter/XibFileLoader.swift new file mode 100644 index 000000000..0a4be6a4a --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/XibFileLoader.swift @@ -0,0 +1,69 @@ +// +// XibFileLoader.swift +// ParticlesKit +// +// Created by John Huang on 1/16/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +/* + open class XibFileLoader: NSObject { + public var xibRegister: XibRegisterProtocol? + public var xibSizes: [String: CGSize] = [:] + @IBInspectable var xibMap: String? { + didSet { + if xibMap != oldValue { + if let xibMap = xibMap { + xibMapFile = XibJsonFile(fileName: xibMap, bundle: Bundle.ui()) + } + } + } + } + + private static var sharedXibMapFile: XibJsonFile = { + XibJsonFile(fileName: "xib.json", bundle: Bundle.ui()) + }() + + private var xibMapFile: XibJsonFile? + + public func xibFile(object: (ModelObjectProtocol)?) -> String? { + var xib: String? + if let xibProvider = object as? XibProviderProtocol { + xib = xibProvider.xib + } + if xib == nil { + if let xibFile = xibMapFile { + xib = xibFile.xibFile(object: object) + } + } + if xib == nil { + let xibFile = XibListPresenter.sharedXibMapFile + xib = xibFile.xibFile(object: object) + } + return xib + } + + public func xib(object: (ModelObjectProtocol)?) -> String? { + if let xibFile = self.xibFile(object: object) { + xibRegister?.registerXibIfNeeded(xibFile) + return xibFile + } + return nil + } + + public func defaultSize(xib: String?) -> CGSize? { + if let xib = xib { + var size = xibSizes[xib] + if size == nil { + if let loadedView = XibLoader.loadView(fromNib: xib) { + size = loadedView.frame.size + xibSizes[xib] = size + } + } + return size + } + return nil + } + } + */ diff --git a/ParticlesKit/ParticlesKit/_Presenter/XibPresenterCache.swift b/ParticlesKit/ParticlesKit/_Presenter/XibPresenterCache.swift new file mode 100644 index 000000000..b780a372a --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/XibPresenterCache.swift @@ -0,0 +1,84 @@ +// +// XibPresenterCache.swift +// ParticlesKit +// +// Created by John Huang on 1/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +open class XibPresenterCache: NSObject { + private static var sharedXibMapFile: XibJsonFile = { + XibJsonFile(fileName: ParticilesKitConfig.xibJson, bundles: Bundle.particles) + }() + + private var xibMapFile: XibJsonFile? + + private var xibCache: [String: String] = [:] + public var xibSizes: [String: CGSize] = [:] + public var xibMap: String? { + didSet { + if xibMap != oldValue { + if let xibMap = xibMap { + xibMapFile = XibJsonFile(fileName: xibMap, bundles: Bundle.particles) + } + } + } + } + + public func xibFile(object: ModelObjectProtocol?) -> String? { + if let object = object { + var xib: String? + if let xibProvider = object as? XibProviderProtocol { + xib = xibProvider.xib + } + let className = (object as? NSObject)?.className() + if xib == nil, let className = className { + xib = xibCache[className] + } + if xib == nil { + if let xibFile = xibMapFile { + xib = xibFile.xibFile(object: object) + if let xib = xib, let className = className { + xibCache[className] = xib + } + } + } + if xib == nil { + let xibFile = XibPresenterCache.sharedXibMapFile + xib = xibFile.xibFile(object: object) + if let xib = xib, let className = className { + xibCache[className] = xib + } + } + if xib == nil { + let className = String(describing: type(of: object)) + Console.shared.log("XIB not found for \(className)") + } + return xib + } + return nil + } + + public func xib(object: ModelObjectProtocol?) -> String? { + if let xibFile = self.xibFile(object: object) { + return xibFile + } + return nil + } + + public func defaultSize(xib: String?) -> CGSize? { + if let xib = xib { + var size = xibSizes[xib] + if size == nil { + if let loadedView: UIView = XibLoader.load(from: xib) { + size = loadedView.frame.size + xibSizes[xib] = size + } + } + return size + } + return nil + } +} diff --git a/ParticlesKit/ParticlesKit/_Presenter/_Grid Presenter/GridObjectPresenter.swift b/ParticlesKit/ParticlesKit/_Presenter/_Grid Presenter/GridObjectPresenter.swift new file mode 100644 index 000000000..b68c996c4 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/_Grid Presenter/GridObjectPresenter.swift @@ -0,0 +1,23 @@ +// +// GridObjectPresenter.swift +// ParticlesKit +// +// Created by Qiang Huang on 1/20/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +open class GridObjectPresenter: ObjectPresenter { + open override var model: ModelObjectProtocol? { + didSet { + gridPresenter?.interactor = (model as? GridInteractor) + } + } + + @IBOutlet public var gridPresenter: GridPresenter? { + didSet { + gridPresenter?.visible = true + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Presenter/_Grid Presenter/GridPresenter.swift b/ParticlesKit/ParticlesKit/_Presenter/_Grid Presenter/GridPresenter.swift new file mode 100644 index 000000000..7f439be4a --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/_Grid Presenter/GridPresenter.swift @@ -0,0 +1,92 @@ +// +// GridPresenter.swift +// ParticlesKit +// +// Created by Qiang Huang on 1/16/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +import RoutingKit + +@IBDesignable +@objc open class GridPresenter: NSObject { + @IBInspectable open var sequence: Int = 0 + + @IBOutlet open var selectionHandler: SelectionHandlerProtocol? + + @IBOutlet open dynamic var interactor: ModelGridProtocol? { + didSet { + if interactor !== oldValue { + let grid = #keyPath(GridInteractor.grid) + kvoController.unobserve(oldValue, keyPath: grid) + if interactor != nil { + visible = true + changeObservation(from: oldValue, to: interactor, keyPath: grid) { [weak self] _, _, change, _ in + if let self = self { + let new = change[NSKeyValueChangeKey.newKey.rawValue] as? [[ModelObjectProtocol]] + if let current = self.current, let new = new { + if current.count > 0 && new.count > 0 { + self.change(to: new) + } else { + self.refresh(with: new) + } + } else { + self.refresh(with: new) + } + } + } + } else { + refresh(with: nil) + visible = false + } + } + } + } + + @objc open dynamic var updating: Bool = false + + open var visible: Bool? + + open var count: Int? { + return current?.count + } + + open var title: String? { + return nil + } + + open var lastVisible: Int? { + return nil + } + + open var current: [[ModelObjectProtocol]]? + + open func change(to new: [[ModelObjectProtocol]]) { + refresh(with: new) + } + + open func refresh(with new: [[ModelObjectProtocol]]?) { + current = new + refresh(animated: true) + } + + open func refresh(animated: Bool) { + } + + open func select(x: Int, y: Int) { + select(object: object(x: x, y: y)) + } + + open func select(object: ModelObjectProtocol?) { + let selection: SelectionHandlerProtocol = selectionHandler ?? SelectionHandler.standard + selection.select(object) + } + + open func object(x: Int, y: Int) -> ModelObjectProtocol? { + return current?[y][x] + } + + open func updateLayout() { + } +} diff --git a/ParticlesKit/ParticlesKit/_Presenter/_Grid Presenter/GridPresenterManagerProtocol.swift b/ParticlesKit/ParticlesKit/_Presenter/_Grid Presenter/GridPresenterManagerProtocol.swift new file mode 100644 index 000000000..a508cda0e --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/_Grid Presenter/GridPresenterManagerProtocol.swift @@ -0,0 +1,16 @@ +// +// GridPresenterManagerProtocol.swift +// ParticlesKit +// +// Created by Qiang Huang on 1/16/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol GridPresenterManagerProtocol: NSObjectProtocol { + var loadingPresenter: GridPresenter? { get set } + var presenters: [GridPresenter]? { get set } + var index: NSNumber? { get set } + var current: GridPresenter? { get set } +} diff --git a/ParticlesKit/ParticlesKit/_Presenter/_List Presenter/ListObjectPresenter.swift b/ParticlesKit/ParticlesKit/_Presenter/_List Presenter/ListObjectPresenter.swift new file mode 100644 index 000000000..34d604c3b --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/_List Presenter/ListObjectPresenter.swift @@ -0,0 +1,27 @@ +// +// ListObjectPresenter.swift +// ParticlesKit +// +// Created by Qiang Huang on 1/20/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +open class ListObjectPresenter: ObjectPresenter { + open override var model: ModelObjectProtocol? { + didSet { + listPresenter?.interactor = (model as? ListInteractor) + } + } + + @IBOutlet open var listPresenter: ListPresenter? { + didSet { + listPresenter?.visible = true + } + } + + open override var selectable: Bool { + return false + } +} diff --git a/ParticlesKit/ParticlesKit/_Presenter/_List Presenter/ListPresenter.swift b/ParticlesKit/ParticlesKit/_Presenter/_List Presenter/ListPresenter.swift new file mode 100644 index 000000000..1996cf355 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/_List Presenter/ListPresenter.swift @@ -0,0 +1,161 @@ +// +// ListPresenterProtocol.swift +// PresenterLib +// +// Created by John Huang on 10/9/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import RoutingKit +import Utilities + +public enum PresenterMode: Int { + case linear + case sections +} + +public typealias SelectionCallack = (_ deselect: Bool) -> Void + +@IBDesignable +@objc open class ListPresenter: NSObject { + @IBInspectable open var sequence: Int = 0 + private var debouncer: Debouncer = Debouncer() + @IBOutlet open dynamic var selectionHandler: SelectionHandlerProtocol? { + didSet { + changeObservation(from: oldValue, to: selectionHandler, keyPath: #keyPath(PersistSelectionHandlerProtocol.selected)) { [weak self] _, object, _, _ in + if let self = self { + if (self.selectionHandler as? PersistSelectionHandlerProtocol)?.selected != nil || !(object is NSNull) { + if let handler = self.debouncer.debounce() { + handler.run({ [weak self] in + if let self = self { + self.changed(selected: (self.selectionHandler as? PersistSelectionHandlerProtocol)?.selected) + } + }, delay: 0.01) + } + } + } + } + } + } + + open var moving: Bool = false + + @IBOutlet open dynamic var interactor: ListInteractor? { + didSet { + didSetInteractor(oldValue: oldValue) + } + } + + @IBInspectable open dynamic var visible: Bool = true { + didSet { + if visible != oldValue { + if visible == true, !(pending?.containsSame(as: current) ?? false) { + update() + } + } + } + } + + open var count: Int? { + return current?.count + } + + open var title: String? { + return nil + } + + @objc open dynamic var items: [ModelObjectProtocol]? { + didSet { + let filtered = filter(items: items) + pending = filtered + } + } + + @objc open dynamic var pending: [ModelObjectProtocol]? { + didSet { + pendingUpdate() + } + } + + @objc open dynamic var current: [ModelObjectProtocol]? { + didSet { + didSetCurrent(oldValue: oldValue) + } + } + + open func didSetInteractor(oldValue: ListInteractor?) { + changeObservation(from: oldValue, to: interactor, keyPath: #keyPath(ListInteractor.list)) { [weak self] _, _, _, _ in + if let self = self, !self.moving { + self.items = self.interactor?.list + } + } + } + + open func filter(items: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + return items + } + + open func pendingUpdate() { + if visible == true { + update() + } + } + + open func update() { + update(move: true) + } + + open func update(move: Bool) { + } + + open func firstIndex(of object: ModelObjectProtocol, in list: [ModelObjectProtocol], after start: Int) -> Int? { + return list.suffix(from: start).firstIndex(where: { (item) -> Bool in + object === item + }) + } + + open func index(of object: ModelObjectProtocol?) -> Int? { + if let object = object, let current = current { + return firstIndex(of: object, in: current, after: 0) + } + return nil + } + + open func select(index: Int, completion: SelectionCallack?) { + select(object: object(at: index), completion: completion) + } + + open func select(object: ModelObjectProtocol?, completion: SelectionCallack?) { + let selection: SelectionHandlerProtocol = selectionHandler ?? SelectionHandler.standard + let selected = selection.select(object) + completion?(!selected) + } + + open func deselect(index: Int) { + deselect(object: object(at: index)) + } + + open func deselect(object: ModelObjectProtocol?) { + let selection: SelectionHandlerProtocol = selectionHandler ?? SelectionHandler.standard + selection.deselect(object) + } + + open func object(at index: Int) -> ModelObjectProtocol? { + return current?.at(index) + } + + open func updateLayout() { + } + + open func changed(selected: [ModelObjectProtocol]?) { + } + + open func didSetCurrent(oldValue: [ModelObjectProtocol]?) { + } +} + +@objc public protocol ScrollingProtocol: NSObjectProtocol { + @objc var autoScroll: Bool { get set } + @objc var isAtEnd: Bool { get set } + func scrollToEnd(animated: Bool) +} diff --git a/ParticlesKit/ParticlesKit/_Presenter/_List Presenter/ListPresenterManagerProtocol.swift b/ParticlesKit/ParticlesKit/_Presenter/_List Presenter/ListPresenterManagerProtocol.swift new file mode 100644 index 000000000..c677115e9 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/_List Presenter/ListPresenterManagerProtocol.swift @@ -0,0 +1,18 @@ +// +// ListPresenterManagerProtocol.swift +// PresenterLib +// +// Created by Qiang Huang on 10/12/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol ListPresenterManagerProtocol: NSObjectProtocol { + var flat: Bool { get set } + var flatConstraints: [NSLayoutConstraint]? { get set } + var loadingPresenter: ListPresenter? { get set } + var presenters: [ListPresenter]? { get set } + var index: NSNumber? { get set } + var current: ListPresenter? { get set } +} diff --git a/ParticlesKit/ParticlesKit/_Presenter/_Object Presenter/ObjectPresenter.swift b/ParticlesKit/ParticlesKit/_Presenter/_Object Presenter/ObjectPresenter.swift new file mode 100644 index 000000000..54e712319 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/_Object Presenter/ObjectPresenter.swift @@ -0,0 +1,51 @@ +// +// ObjectPresenter.swift +// PresenterLib +// +// Created by Qiang Huang on 10/10/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities +import Combine + +@objc public protocol ObjectPresenterProtocol { + @objc var model: ModelObjectProtocol? { get set } + @objc optional var selectable: Bool { get } +} + +@objc public protocol SelectableProtocol { + @objc var isSelected: Bool { get set } +} + +@objc public protocol HighlightableProtocol { + @objc var isHighlighted: Bool { get set } +} + +@objc open class ObjectPresenter: NSObject, ObjectPresenterProtocol, CombineObserving { + public var cancellableMap = [AnyKeyPath: AnyCancellable]() + + @IBOutlet @objc open dynamic var model: ModelObjectProtocol? { + didSet { + if model !== oldValue { + didSetModel(oldValue: oldValue) + } + } + } + +// public var debouncer: Debouncer = Debouncer() + + @objc open dynamic var isFirst: Bool = false + @objc open dynamic var isLast: Bool = false + + @objc open dynamic var selectable: Bool { + return true + } + + open func didSetModel(oldValue: ModelObjectProtocol?) { + } +} + +@objc public protocol ObjectTableCellPresenterProtocol { + var showDisclosure: Bool { get } +} diff --git a/ParticlesKit/ParticlesKit/_Presenter/_Protocol/GraphingProtocols.swift b/ParticlesKit/ParticlesKit/_Presenter/_Protocol/GraphingProtocols.swift new file mode 100644 index 000000000..d0cdf5284 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/_Protocol/GraphingProtocols.swift @@ -0,0 +1,15 @@ +// +// GraphingProtocols.swift +// ParticlesKit +// +// Created by Qiang Huang on 9/29/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol CandleStickListProviderProtocol: NSObjectProtocol { + var maxVolume: NSNumber? { get } + var lowPrice: NSNumber? { get } + var highPrice: NSNumber? { get } +} diff --git a/ParticlesKit/ParticlesKit/_Presenter/_Protocol/XibProviderProtocol.swift b/ParticlesKit/ParticlesKit/_Presenter/_Protocol/XibProviderProtocol.swift new file mode 100644 index 000000000..75484ab6c --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/_Protocol/XibProviderProtocol.swift @@ -0,0 +1,13 @@ +// +// XibProviderProtocol.swift +// PresenterLib +// +// Created by Qiang Huang on 10/16/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public protocol XibProviderProtocol: NSObjectProtocol { + var xib: String? { get } +} diff --git a/ParticlesKit/ParticlesKit/_Presenter/_Protocol/XibRegisterProtocol.swift b/ParticlesKit/ParticlesKit/_Presenter/_Protocol/XibRegisterProtocol.swift new file mode 100644 index 000000000..303d305ec --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/_Protocol/XibRegisterProtocol.swift @@ -0,0 +1,26 @@ +// +// QueuedXibListPresenter.swift +// PresenterLib +// +// Created by Qiang Huang on 10/10/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public protocol XibRegisterProtocol: NSObjectProtocol { + var registeredXibs: Set { get set } + func registerXibIfNeeded(_ xibFile: String?) + func register(xib: String) +} + +extension XibRegisterProtocol { + public func registerXibIfNeeded(_ xibFile: String?) { + if let xibFile = xibFile { + if !registeredXibs.contains(xibFile) { + registeredXibs.insert(xibFile) + register(xib: xibFile) + } + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Presenter/_Selection/SelectionHandler.swift b/ParticlesKit/ParticlesKit/_Presenter/_Selection/SelectionHandler.swift new file mode 100644 index 000000000..32dd8aaa6 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Presenter/_Selection/SelectionHandler.swift @@ -0,0 +1,153 @@ +// +// ModelSelectionProtocol.swift +// PresenterLib +// +// Created by John Huang on 11/20/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +import RoutingKit + +@objc public protocol SelectionHandlerProtocol: NSObjectProtocol { + @discardableResult @objc func select(_ object: ModelObjectProtocol?) -> Bool + @objc func deselect(_ object: ModelObjectProtocol?) +} + +@objc public protocol PersistSelectionHandlerProtocol: SelectionHandlerProtocol { + @objc var singleSelected: ModelObjectProtocol? { get } + @objc var selected: [ModelObjectProtocol]? { get set } + @objc var multipleSelection: Bool { get set } +} + +@objc open class RoutingSelectionHandler: NSObject & SelectionHandlerProtocol { + @discardableResult open func select(_ object: ModelObjectProtocol?) -> Bool { + if let origintor = object as? RoutingOriginatorProtocol { + Router.shared?.navigate(to: origintor, animated: true, completion: nil) + } + return false + } + + public func deselect(_ object: ModelObjectProtocol?) { + } +} + +@objc open class IdleSelectionHandler: NSObject, PersistSelectionHandlerProtocol { + @objc open dynamic var singleSelected: ModelObjectProtocol? { + if multipleSelection { + return nil + } else { + return selected?.first + } + } + + @objc open dynamic var selected: [ModelObjectProtocol]? { + didSet { + if !(selected?.containsSame(as: oldValue) ?? false) { + if let previous = oldValue { + for object in previous { + (object as? SelectableProtocol)?.isSelected = false + } + } + if let selected = selected { + for object in selected { + (object as? SelectableProtocol)?.isSelected = true + } + } + } + } + } + + @IBInspectable @objc open dynamic var multipleSelection: Bool = false { + didSet { + if multipleSelection != oldValue { + selected = nil + } + } + } + + @discardableResult open func select(_ object: ModelObjectProtocol?) -> Bool { + if let object = object { + if multipleSelection { + if let selectable = object as? (SelectableProtocol & ModelObjectProtocol) { + if !selectable.isSelected { + select(selectable: selectable) + selected(object: object) + } + } else { + if selected?.first === object && (selected?.count ?? 0) == 1 { + } else { + selected = [object] + selected(object: object) + } + } + } else { + if selected?.first === object && (selected?.count ?? 0) == 1 { + } else { + selected = [object] + selected(object: object) + } + } + return true + } else { + if selected != nil { + selected = nil + } + return false + } + } + + open func deselect(_ object: ModelObjectProtocol?) { + if let object = object { + if multipleSelection { + if let selectable = object as? (SelectableProtocol & ModelObjectProtocol) { + if selectable.isSelected { + deselect(selectable: selectable) + } + } else { + if selected?.first === object { + selected = nil + } + } + } else { + if selected?.first === object { + selected = nil + } + } + } + } + + open func deselect(selectable: SelectableProtocol & ModelObjectProtocol) { + if var selected = self.selected { + selected.removeAll { (item) -> Bool in + item === selectable + } + self.selected = selected + } + } + + open func select(selectable: SelectableProtocol & ModelObjectProtocol) { + var selected = self.selected ?? [ModelObjectProtocol]() + selected.append(selectable) + self.selected = selected + } + + open func selected(object: ModelObjectProtocol) { + } +} + +@objc public class PersistSelectionHandler: IdleSelectionHandler { + public override func selected(object: ModelObjectProtocol) { + if !multipleSelection { + if let origintor = object as? RoutingOriginatorProtocol { + Router.shared?.navigate(to: origintor, animated: true, completion: nil) + } + } + } +} + +@objc public class SelectionHandler: NSObject { + @objc public static var standard: SelectionHandlerProtocol = { + RoutingSelectionHandler() + }() +} diff --git a/ParticlesKit/ParticlesKit/_Recorder/ApiReplayer.swift b/ParticlesKit/ParticlesKit/_Recorder/ApiReplayer.swift new file mode 100644 index 000000000..4e03d2ba4 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Recorder/ApiReplayer.swift @@ -0,0 +1,121 @@ +// +// ApiReplayer.swift +// ParticlesKit +// +// Created by Qiang Huang on 11/30/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +public enum ApiReplayMode { + case normal + case recording + case replay +} + +public protocol ApiReplayerProtocol { + var mode: ApiReplayMode { get } + func replay(urlPath: String, params: [String]?) -> Any? + func record(urlPath: String, params: [String]?, data: Any?) +} + +public class ApiReplayer { + public static var shared: ApiReplayerProtocol? +} + +public class JsonApiReplayer: NSObject, ApiReplayerProtocol { + public var path: String? + public var key: String? = "default" + public var recording: [String: Any]? + public var debouncer: Debouncer = Debouncer() + + public var mode: ApiReplayMode { + if let testString = parser.asString(DebugSettings.shared?.debug?["integration_test"]) { + if testString == "r" { + return .recording + } else if testString == "t" { + return .replay + } + } else if let modeString = parser.asString(DebugSettings.shared?.debug?["api_replay"]) { + if modeString == "r" { + return .recording + } else if modeString == "p" { + return .replay + } + } + return .normal + } + + public var persistTag: String? { + if let key = key { + return "\(String(describing: type(of: self))).persist.\(key)" + } + return nil + } + + public var persistDataFile: String? { + if let persistTag = persistTag { + if let path = path ?? FolderService.shared?.documents() { + _ = Directory.ensure(path) + return path.stringByAppendingPathComponent(path: "\(persistTag).data.json") + } + } + return nil + } + + public init(path: String? = nil) { + super.init() + self.path = path + load() + } + + private func uniqueUrl(urlPath: String, params: [String]?) -> String { + let params = params?.sorted(by: { (string1, string2) -> Bool in + string1 < string2 + }) + var urlPath = urlPath + if let params = params { + if params.count > 0 { + urlPath = urlPath + "?" + params.joined(separator: "&") + } + } + return urlPath + } + + public func replay(urlPath: String, params: [String]?) -> Any? { + if mode == .replay { + let url = uniqueUrl(urlPath: urlPath, params: params) + return recording?[url] + } + return nil + } + + public func record(urlPath: String, params: [String]?, data: Any?) { + if mode == .replay || mode == .recording { + if recording == nil { + recording = [:] + } + let url = uniqueUrl(urlPath: urlPath, params: params) + recording?[url] = data + + if let handler = debouncer.debounce() { + handler.run({ [weak self] in + self?.save() + }, delay: 0.5) + } + } + } + + private func save() { + if let persistDataFile = self.persistDataFile, let persist = recording { + JsonWriter.write(persist, to: persistDataFile) + } + } + + open func load() { + if let persistDataFile = self.persistDataFile, let recording = JsonLoader.load(file: persistDataFile) as? [String: Any] { + self.recording = recording + } + } +} diff --git a/ParticlesKit/ParticlesKit/_Services/_Calendar/DateService.swift b/ParticlesKit/ParticlesKit/_Services/_Calendar/DateService.swift new file mode 100644 index 000000000..0756a9a48 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Services/_Calendar/DateService.swift @@ -0,0 +1,38 @@ +// +// File.swift +// Utilities +// +// Created by Qiang Huang on 11/25/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public protocol DateProviderProtocol { + func now() -> Date +} + +public class DateService { + public static var shared: DateProviderProtocol? +} + +public class RealDateProvider: DateProviderProtocol { + public init() { + } + + public func now() -> Date { + return Date() + } +} + +public class FixedDateProvider: DateProviderProtocol { + private var date: Date? + + public required init(date: Date?) { + self.date = date + } + + public func now() -> Date { + return date ?? Date() + } +} diff --git a/ParticlesKit/ParticlesKit/_Services/_Locator/LocatorService.swift b/ParticlesKit/ParticlesKit/_Services/_Locator/LocatorService.swift new file mode 100644 index 000000000..a96940646 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Services/_Locator/LocatorService.swift @@ -0,0 +1,19 @@ +// +// LocatorService.swift +// ParticlesKit +// +// Created by Qiang Huang on 9/15/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol LocatorProviderProtocol: NSObjectProtocol { + @objc var running: Bool { get set } + @objc var shouldBeRunning: Bool { get set } + func mark(tag: String) +} + +public class LocatorService { + public static var shared: LocatorProviderProtocol? +} diff --git a/ParticlesKit/ParticlesKit/_Services/_Places/PlacesService.swift b/ParticlesKit/ParticlesKit/_Services/_Places/PlacesService.swift new file mode 100644 index 000000000..ff91ca0a7 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Services/_Places/PlacesService.swift @@ -0,0 +1,89 @@ +// +// PlacesService.swift +// ParticlesKit +// +// Created by Qiang Huang on 7/31/19. +// Copyright © 2019 Qiang Huang. All rights reserved. +// + +import Utilities + +@objc public protocol PlaceProtocol: ModelObjectProtocol { + var name: String? { get } + var address: String? { get } + var type: String? { get } + var mapPoint: MapPoint? { get } +} + +@objc public protocol PlacesProviderProtocol: NSObjectProtocol { + var constraint: Bool { get set } + var searchText: String? { get set } + var area: MapArea? { get set } + var places: [PlaceProtocol]? { get set } +} + +extension PlacesProviderProtocol { + public func searchArea() -> MapArea? { + let coordinate = LocationProvider.shared?.location?.coordinate + if let area = area { + let flag = (self as? NSObject)?.parser.asString(FeatureService.shared?.flag(feature: "auto_complete_area")) + switch flag { + case "user": + if let coordinate = coordinate { + return area.shift(to: MapPoint(latitude: coordinate.latitude, longitude: coordinate.longitude)) + } else { + return area + } + + case "map": + fallthrough + default: + return area + } + } else { + if let coordinate = coordinate { + let point1 = MapPoint(latitude: coordinate.latitude - 0.001, longitude: coordinate.longitude - 0.001) + let point2 = MapPoint(latitude: coordinate.latitude + 0.001, longitude: coordinate.longitude + 0.001) + return MapArea(points: [point1, point2]) + } else { + return nil + } + } + } + + public func searchCenter() -> MapPoint? { + let coordinate = LocationProvider.shared?.location?.coordinate + let area = PlacesService.shared?.area + let flag = (self as? NSObject)?.parser.asString(FeatureService.shared?.flag(feature: "auto_complete_area")) + switch flag { + case "user": + if let coordinate = coordinate { + return MapPoint(latitude: coordinate.latitude, longitude: coordinate.longitude) + } else { + return area?.center + } + + case "map": + fallthrough + default: + if let area = area { + return area.center + } else { + if let coordinate = coordinate { + return MapPoint(latitude: coordinate.latitude, longitude: coordinate.longitude) + } else { + return nil + } + } + } + } + + public func searchRadius() -> Double? { + let area = PlacesService.shared?.area + return area?.radius() + } +} + +public class PlacesService { + public static var shared: PlacesProviderProtocol? +} diff --git a/ParticlesKit/ParticlesKit/_Shared/ParticlesInjection.swift b/ParticlesKit/ParticlesKit/_Shared/ParticlesInjection.swift new file mode 100644 index 000000000..4b86706d7 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Shared/ParticlesInjection.swift @@ -0,0 +1,108 @@ +// +// ParticlesInjections.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/26/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import RoutingKit +import Utilities + +public protocol InjectionProtocol: NSObjectProtocol { + func injectCore(completion: @escaping () -> Void) + func injectFeatures(completion: @escaping () -> Void) + func injectFeatured(completion: @escaping () -> Void) + func injectParsers() + func injectAppStart(completion: @escaping () -> Void) +} + +public class Injection { + internal static var _shared: InjectionProtocol? + public static var shared: InjectionProtocol? { + return _shared + } + + public static func inject(injeciton: InjectionProtocol, completion: @escaping () -> Void) { + _shared = injeciton + _shared?.injectCore(completion: completion) + } +} + +open class ParticlesInjection: NSObject, InjectionProtocol { + open func injectCore(completion: @escaping () -> Void) { + injectFolderService() + injectDebug() + injectLocalization() + completion() + } + + open func injectFolderService() { + Console.shared.log("injectFolderService") + if CommandLine.arguments.contains("-MockTest") { + FolderService.shared = RealFolderProvider.mock() + _ = LocalDebugCacheInteractor.mock() + _ = LocalFeatureFlagsCacheInteractor.mock() + } else { + FolderService.shared = RealFolderProvider() + } + } + + open func injectDebug() { + DebugSettings.shared = LocalDebugCacheInteractor.shared + } + + open func injectLocalization() { + #if DEBUG + LocalizerBuffer.shared = DebugLocalizer() + #endif + } + + open func injectFeatures(completion: @escaping () -> Void) { + injectFeatureService { [weak self] in + self?.injectFeatured(completion: completion) + } + } + + open func injectFeatureService(completion: @escaping () -> Void) { + Console.shared.log("injectFeatures") + FeatureService.shared = LocalFeatureFlagsCacheInteractor.shared + FeatureService.shared?.activate(completion: completion) + } + + open func injectFeatured(completion: @escaping () -> Void) { + Console.shared.log("injectFeatured") + if let integration_test = parser.asString(DebugSettings.shared?.debug?["integration_test"]), integration_test == "r" { + FolderService.shared = RealFolderProvider.mock() + } + + if let debug = DebugSettings.shared?.debug, parser.asString(debug["demo_mode"]) == "1" { + if let dateText = parser.asString(debug["demo_date"]) { + DateService.shared = FixedDateProvider(date: Date.date(serverString: dateText)) + } else { + DateService.shared = FixedDateProvider(date: Date()) + } + } else { + DateService.shared = RealDateProvider() + } + completion() + } + + open func injectParsers() { + Console.shared.log("injectParsers") + MappedRouter.parserOverwrite = Parser.standard + XibJsonFile.parserOverwrite = Parser.standard + JsonEndpointResolver.parserOverwrite = Parser.debug + JsonCredentialsProvider.parserOverwrite = Parser.debug + } + + open func injectReplay() { + Console.shared.log("injectReplay") + // set up the router + ApiReplayer.shared = JsonApiReplayer(path: ProcessInfo.processInfo.environment["API_RECORDER_DIR"]) + } + + open func injectAppStart(completion: @escaping () -> Void) { + completion() + } +} diff --git a/ParticlesKit/ParticlesKit/_WebSocket/WebSocketApi.swift b/ParticlesKit/ParticlesKit/_WebSocket/WebSocketApi.swift new file mode 100644 index 000000000..1e4be79d9 --- /dev/null +++ b/ParticlesKit/ParticlesKit/_WebSocket/WebSocketApi.swift @@ -0,0 +1,269 @@ +// +// WebSocketApi.swift +// ParticlesKit +// +// Created by Qiang Huang on 3/5/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Utilities + +public typealias WebSocketReceiveHandler = (_ meta: [String: Any]?, _ received: Any, _ hasData: Bool) -> Void +public typealias WebSocketSubscribingDataProvider = () -> Any? +public typealias WebSocketUnsubscribingDataProvider = () -> [String: Any]? + +public class WebSocketHook { + public var hasData: Bool = false + public var subscribeData: WebSocketSubscribingDataProvider? + public var unsubscribeData: WebSocketUnsubscribingDataProvider? + public var handler: WebSocketReceiveHandler + + init(subscribeData: WebSocketSubscribingDataProvider?, unsubscribeData: WebSocketUnsubscribingDataProvider?, handler: @escaping WebSocketReceiveHandler) { + self.subscribeData = subscribeData + self.unsubscribeData = unsubscribeData + self.handler = handler + } +} + +@available(iOS 13.0, *) +@objc open class WebSocketApi: HttpApi { + private var networkStatus: NetworkConnection? { + didSet { + changeObservation(from: oldValue, to: networkStatus, keyPath: #keyPath(NetworkConnection.connected)) { [weak self] _, _, _, _ in + if let self = self { + self.running = self.shouldRun() + } + } + } + } + + override public var background: Bool { + didSet { + if background != oldValue { + running = shouldRun() + } + } + } + + public var hooks: [String: WebSocketHook] = [:] + + private var url: URL? { + didSet { + if url != oldValue { + reset() + } + } + } + + private var dataTask: URLSessionWebSocketTask? { + didSet { + if dataTask !== oldValue { + if dataTask != nil { + dataTask?.resume() + receive() + } else { + connected = false + } + } + } + } + + private var connected: Bool = false { + didSet { + if connected != oldValue { + if connected { +// sendSubscriptions() + } + pingTimer = connected ? ping() : nil + } + } + } + + @objc public dynamic var running: Bool = false { + didSet { + if running != oldValue { + for (_, hook) in hooks { + hook.hasData = false + } + if running { + if let url = url { + let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + let dataTask = urlSession.webSocketTask(with: url) + dataTask.resume() + self.dataTask = dataTask + } else { + running = false + } + } else { + Console.shared.log("Websocket: Task cancelled") + dataTask?.cancel() + dataTask = nil + } + } + } + } + + private var pingTimer: Timer? { + didSet { + if pingTimer !== oldValue { + oldValue?.invalidate() + } + } + } + + deinit { + running = false + } + + public func load(path: String, params: [String: Any]?) { + if networkStatus == nil { + networkStatus = NetworkConnection.shared + } + if let server = server { + let pathAndParams = url(server: server, path: path, params: params) + if pathAndParams.urlPath.contains("{") { + // unresolved params + url = nil + } else { + url = url(path: pathAndParams.urlPath, params: pathAndParams.paramStrings) + } + } + } + + public func subscribe(channel: String, subscribeData: WebSocketSubscribingDataProvider?, unsubscribeData: WebSocketUnsubscribingDataProvider?, hook: @escaping WebSocketReceiveHandler) { + hooks[channel] = WebSocketHook(subscribeData: subscribeData, unsubscribeData: unsubscribeData, handler: hook) + if running { + if connected { + if let subscribeData = subscribeData?() { + send(data: subscribeData) + } + } + } else { + running = shouldRun() + } + } + + public func unsubscribe(channel: String) { + if let hook = hooks[channel] { + if let unsubscribeData = hook.unsubscribeData?() { + send(data: unsubscribeData) + } + } + hooks[channel] = nil + running = shouldRun() + } + + private func shouldRun() -> Bool { + return !background && (networkStatus?.connected?.boolValue ?? false) && hooks.count != 0 + } + + private func websocket() -> URLSessionWebSocketTask? { + if let url = url { + let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + return urlSession.webSocketTask(with: url) + } + return nil + } + + public func sendSubscriptions() { + for (_, hook) in hooks { + send(data: hook.subscribeData?()) + } + } + + public func send(data: Any?) { + if let string = data as? String { + let message = URLSessionWebSocketTask.Message.string(string) + dataTask?.send(message, completionHandler: { [weak self] error in + if error != nil { + self?.reset() + } + }) + } else if let data = data as? Data { + let message = URLSessionWebSocketTask.Message.data(data) + dataTask?.send(message, completionHandler: { [weak self] error in + if error != nil { + self?.reset() + } + }) + } else if let data = data as? [String: Any] { + if let string = string(json: data) { + let message = URLSessionWebSocketTask.Message.string(string) + dataTask?.send(message, completionHandler: { [weak self] error in + if error != nil { + self?.reset() + } + }) + } + } + } + + private func reset() { + running = false + running = shouldRun() + } + + private func receive() { + dataTask?.receive { [weak self] result in + if let self = self { + switch result { + case let .failure(error): + ErrorLogging.shared?.log(error) + self.reset() + + case let .success(message): + switch message { + case let .string(text): +// Console.shared.log("Websocket: Received text message: \(text)") + self.dispatch(text: text) + + case let .data(data): +// Console.shared.log("Websocket: Received binary message: \(data)") + self.dispatch(data: data) + + @unknown default: +// Console.shared.log("Websocket: Unknown error") + break + } + self.receive() + } + } + } + } + + open func dispatch(text: String) { + } + + open func dispatch(data: Data) { + } + + private func ping() -> Timer { + return Timer.scheduledTimer(withTimeInterval: 25, repeats: true) { [weak self] _ in + self?.dataTask?.sendPing { error in + if let error = error { + Console.shared.log("Ping failed: \(error)") + } + } + } + } + + private func string(json: Any?) -> String? { + if let json = json { + if let data = try? JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions.prettyPrinted) { + return String(data: data, encoding: String.Encoding.utf8) + } + } + return nil + } +} + +@available(iOS 13.0, *) +extension WebSocketApi: URLSessionWebSocketDelegate { + public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) { + connected = true + } + + public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) { + running = false + } +} diff --git a/ParticlesKit/ParticlesKit/_Worker/WorkerProtocol.swift b/ParticlesKit/ParticlesKit/_Worker/WorkerProtocol.swift new file mode 100644 index 000000000..aabcfe6bb --- /dev/null +++ b/ParticlesKit/ParticlesKit/_Worker/WorkerProtocol.swift @@ -0,0 +1,39 @@ +// +// WorkerProtocol.swift +// ParticlesKit +// +// Created by Rui Huang on 8/2/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import Foundation +import Combine + +public protocol WorkerProtocol: NSObjectProtocol { + func start() + func stop() + var isStarted: Bool { get set } +} + +open class BaseWorker: NSObject, WorkerProtocol { + public var subscriptions = Set() + public var isStarted = false + + public override init() {} + + open func start() { + if !isStarted { + isStarted = true + } + } + + open func stop() { + if isStarted { + subscriptions.forEach { cancellable in + cancellable.cancel() + } + subscriptions.removeAll() + isStarted = false + } + } +} diff --git a/ParticlesKit/ParticlesKitAppleTV/Info.plist b/ParticlesKit/ParticlesKitAppleTV/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/ParticlesKit/ParticlesKitAppleTV/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/ParticlesKit/ParticlesKitAppleTV/ParticlesKit.h b/ParticlesKit/ParticlesKitAppleTV/ParticlesKit.h new file mode 100644 index 000000000..67749f836 --- /dev/null +++ b/ParticlesKit/ParticlesKitAppleTV/ParticlesKit.h @@ -0,0 +1,17 @@ +// +// ParticlesKitAppleTV.h +// ParticlesKitAppleTV +// +// Created by Qiang Huang on 12/6/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for ParticlesKitAppleTV. +FOUNDATION_EXPORT double ParticlesKitAppleTVVersionNumber; + +//! Project version string for ParticlesKitAppleTV. +FOUNDATION_EXPORT const unsigned char ParticlesKitAppleTVVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/ParticlesKit/ParticlesKitAppleTVTests/Info.plist b/ParticlesKit/ParticlesKitAppleTVTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/ParticlesKit/ParticlesKitAppleTVTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/ParticlesKit/ParticlesKitAppleTVTests/ParticlesKitAppleTVTests.swift b/ParticlesKit/ParticlesKitAppleTVTests/ParticlesKitAppleTVTests.swift new file mode 100644 index 000000000..fd05d0f90 --- /dev/null +++ b/ParticlesKit/ParticlesKitAppleTVTests/ParticlesKitAppleTVTests.swift @@ -0,0 +1,32 @@ +// +// ParticlesKitAppleTVTests.swift +// ParticlesKitAppleTVTests +// +// Created by Qiang Huang on 12/6/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +@testable import ParticlesKit +import XCTest + +class ParticlesKitAppleTVTests: XCTestCase { + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. + } + } +} diff --git a/ParticlesKit/ParticlesKitAppleWatch/Info.plist b/ParticlesKit/ParticlesKitAppleWatch/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/ParticlesKit/ParticlesKitAppleWatch/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/ParticlesKit/ParticlesKitAppleWatch/ParticlesKit.h b/ParticlesKit/ParticlesKitAppleWatch/ParticlesKit.h new file mode 100644 index 000000000..33a1e0821 --- /dev/null +++ b/ParticlesKit/ParticlesKitAppleWatch/ParticlesKit.h @@ -0,0 +1,17 @@ +// +// ParticlesKitAppleWatch.h +// ParticlesKitAppleWatch +// +// Created by Qiang Huang on 12/4/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for ParticlesKitAppleWatch. +FOUNDATION_EXPORT double ParticlesKitAppleWatchVersionNumber; + +//! Project version string for ParticlesKitAppleWatch. +FOUNDATION_EXPORT const unsigned char ParticlesKitAppleWatchVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/ParticlesKit/ParticlesKitTests/Info.plist b/ParticlesKit/ParticlesKitTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/ParticlesKit/ParticlesKitTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/ParticlesKit/ParticlesKitTests/ParticlesKitTests.swift b/ParticlesKit/ParticlesKitTests/ParticlesKitTests.swift new file mode 100644 index 000000000..31bbde207 --- /dev/null +++ b/ParticlesKit/ParticlesKitTests/ParticlesKitTests.swift @@ -0,0 +1,52 @@ +// +// ParticlesKitTests.swift +// ParticlesKitTests +// +// Created by Qiang Huang on 11/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import XCTest +@testable import ParticlesKit + +class ParticlesKitTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testKeychain() { + // keychain access requires entitlement + /* + let path1 = "path1" + let key1 = "key1" + let value1 = "value1" + let obj1 = [key1: value1] + let keychain = JsonKeychainCaching() + keychain.write(path: path1, data: obj1, completion: nil) + keychain.read(path: path1) { (obj, error) in + if let dictionary = obj as? [String: String] { + XCTAssert(dictionary[key1] == value1) + } else { + XCTAssert(false) + } + } + */ + } + + func testExample() { + + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/PlatformParticles/MessageParticles/Info.plist b/PlatformParticles/MessageParticles/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/PlatformParticles/MessageParticles/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/PlatformParticles/MessageParticles/MessageParticles.h b/PlatformParticles/MessageParticles/MessageParticles.h new file mode 100644 index 000000000..0f5ad7a15 --- /dev/null +++ b/PlatformParticles/MessageParticles/MessageParticles.h @@ -0,0 +1,19 @@ +// +// MessageParticles.h +// MessageParticles +// +// Created by Qiang Huang on 12/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for MessageParticles. +FOUNDATION_EXPORT double MessageParticlesVersionNumber; + +//! Project version string for MessageParticles. +FOUNDATION_EXPORT const unsigned char MessageParticlesVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/PlatformParticles/MessageParticlesTests/Info.plist b/PlatformParticles/MessageParticlesTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/PlatformParticles/MessageParticlesTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/PlatformParticles/MessageParticlesTests/MessageParticlesTests.swift b/PlatformParticles/MessageParticlesTests/MessageParticlesTests.swift new file mode 100644 index 000000000..87bd0980d --- /dev/null +++ b/PlatformParticles/MessageParticlesTests/MessageParticlesTests.swift @@ -0,0 +1,34 @@ +// +// MessageParticlesTests.swift +// MessageParticlesTests +// +// Created by Qiang Huang on 12/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import XCTest +@testable import MessageParticles + +class MessageParticlesTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/PlatformParticles/PlatformParticles.xcodeproj/project.pbxproj b/PlatformParticles/PlatformParticles.xcodeproj/project.pbxproj new file mode 100644 index 000000000..d986c54c2 --- /dev/null +++ b/PlatformParticles/PlatformParticles.xcodeproj/project.pbxproj @@ -0,0 +1,3150 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 0271236928E545A50015D39F /* SwiftMessages in Frameworks */ = {isa = PBXBuildFile; productRef = 0271236828E545A50015D39F /* SwiftMessages */; }; + 27ED353A2AD5BD0900C159F5 /* BannerErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27ED35392AD5BD0900C159F5 /* BannerErrorAlert.swift */; }; + 3101F9C52511303E00AC4010 /* ConfirmAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F9C42511303E00AC4010 /* ConfirmAction.swift */; }; + 3101F9C62511303E00AC4010 /* ConfirmAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F9C42511303E00AC4010 /* ConfirmAction.swift */; }; + 3101F9C72511303E00AC4010 /* ConfirmAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F9C42511303E00AC4010 /* ConfirmAction.swift */; }; + 3101F9CA2511308E00AC4010 /* AuthLoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F9C92511308E00AC4010 /* AuthLoginPresenter.swift */; }; + 3101F9CB251130A700AC4010 /* AuthLoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F9C92511308E00AC4010 /* AuthLoginPresenter.swift */; }; + 3101F9CD251130D100AC4010 /* AuthLoginPrimerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F9CC251130D100AC4010 /* AuthLoginPrimerViewController.swift */; }; + 3101F9CE251130D100AC4010 /* AuthLoginPrimerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F9CC251130D100AC4010 /* AuthLoginPrimerViewController.swift */; }; + 3101F9D12511311600AC4010 /* AuthLoginPrimer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3101F9D02511311600AC4010 /* AuthLoginPrimer.storyboard */; }; + 3101F9D32511314200AC4010 /* ConfirmAction.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3101F9D22511314200AC4010 /* ConfirmAction.xib */; }; + 3101F9D52511317800AC4010 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3101F9D42511317800AC4010 /* Media.xcassets */; }; + 310C3B2D21D9C9C70081E56D /* UIAppToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310C3B2721D9C9BF0081E56D /* UIAppToolkits.framework */; }; + 310D8900262DF3EC00FEF56A /* ObjectViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310D88FF262DF3EC00FEF56A /* ObjectViewPresenter.swift */; }; + 310D8901262DF3EC00FEF56A /* ObjectViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310D88FF262DF3EC00FEF56A /* ObjectViewPresenter.swift */; }; + 310D8902262DF3EC00FEF56A /* ObjectViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310D88FF262DF3EC00FEF56A /* ObjectViewPresenter.swift */; }; + 3111C2432543AD2E00F204D9 /* CompositeObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111C2422543AD2E00F204D9 /* CompositeObjectPresenter.swift */; }; + 3111C2442543AD2E00F204D9 /* CompositeObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111C2422543AD2E00F204D9 /* CompositeObjectPresenter.swift */; }; + 3111C2452543AD2E00F204D9 /* CompositeObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111C2422543AD2E00F204D9 /* CompositeObjectPresenter.swift */; }; + 3111C2462543AD2E00F204D9 /* CompositeObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111C2422543AD2E00F204D9 /* CompositeObjectPresenter.swift */; }; + 3128DDF026B89CE80099B62E /* LikedTableViewListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC923DCCE2800139EB3 /* LikedTableViewListPresenter.swift */; }; + 3128DDFE26B89CE90099B62E /* LikedTableViewListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC923DCCE2800139EB3 /* LikedTableViewListPresenter.swift */; }; + 31308B8224FAEF86003B5B9A /* ColoredClusterPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B8124FAEF86003B5B9A /* ColoredClusterPresenter.swift */; }; + 31308B8424FAF01E003B5B9A /* LocationPermissionAction.xib in Resources */ = {isa = PBXBuildFile; fileRef = 31308B8324FAF01E003B5B9A /* LocationPermissionAction.xib */; }; + 31347F6E2710FDF40095829A /* ParticlesCandleChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31347F6D2710FDF40095829A /* ParticlesCandleChartDataEntry.swift */; }; + 31347F7927110DAE0095829A /* ParticlesLineChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31347F7727110DAD0095829A /* ParticlesLineChartDataEntry.swift */; }; + 31347F7A27110DAE0095829A /* ParticlesBarChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31347F7827110DAE0095829A /* ParticlesBarChartDataEntry.swift */; }; + 313EB7CA21BB592E00BEF926 /* PlatformParticles.h in Headers */ = {isa = PBXBuildFile; fileRef = 313EB7C821BB592E00BEF926 /* PlatformParticles.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 313EB7D621BB598300BEF926 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB28E21BA299300BEF926 /* Utilities.framework */; }; + 313EB7D721BB598300BEF926 /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB29E21BA299300BEF926 /* ParticlesKit.framework */; }; + 313EB7D821BB598300BEF926 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB2AF21BA299300BEF926 /* RoutingKit.framework */; }; + 313EB7D921BB598300BEF926 /* PlatformRouting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB7BF21BB591200BEF926 /* PlatformRouting.framework */; }; + 313EB7DF21BB5AB800BEF926 /* TableViewListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EB7DE21BB5AB800BEF926 /* TableViewListPresenter.swift */; }; + 313EB86C21BB73B400BEF926 /* XibListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EB86821BB73B400BEF926 /* XibListPresenter.swift */; }; + 313EB86F21BB73FE00BEF926 /* UIToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB86B21BB73B400BEF926 /* UIToolkits.framework */; }; + 313EBB4C21BB7A4B00BEF926 /* PlatformParticles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBB4321BB7A4B00BEF926 /* PlatformParticles.framework */; }; + 313EBB5121BB7A4B00BEF926 /* PlatformParticlesAppleTVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EBB5021BB7A4B00BEF926 /* PlatformParticlesAppleTVTests.swift */; }; + 313EBB5321BB7A4B00BEF926 /* PlatformParticles.h in Headers */ = {isa = PBXBuildFile; fileRef = 313EBB4521BB7A4B00BEF926 /* PlatformParticles.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 313EBCF821BB7D7400BEF926 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB29021BA299300BEF926 /* Utilities.framework */; }; + 313EBCF921BB7D7400BEF926 /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB2A021BA299300BEF926 /* ParticlesKit.framework */; }; + 313EBCFA21BB7D7400BEF926 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB2B121BA299300BEF926 /* RoutingKit.framework */; }; + 313EBCFB21BB7D7400BEF926 /* PlatformRouting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBCD221BB7D1100BEF926 /* PlatformRouting.framework */; }; + 313EBCFC21BB7D7400BEF926 /* UIToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBB5921BB7A4B00BEF926 /* UIToolkits.framework */; }; + 313EBE0321BC400300BEF926 /* DefaultExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EBE0221BC400300BEF926 /* DefaultExtensionDelegate.swift */; }; + 313EBF3721BE0C7B00BEF926 /* ListPresenterInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EBF3621BE0C7B00BEF926 /* ListPresenterInterfaceController.swift */; }; + 31439A2B2672BAD6003871A1 /* ObjectLinePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A2A2672BAD6003871A1 /* ObjectLinePresenter.swift */; }; + 31439A2D2672BC26003871A1 /* ObjectValueLinePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A2C2672BC26003871A1 /* ObjectValueLinePresenter.swift */; }; + 31439A402672C29E003871A1 /* ObjectTextLinePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A3F2672C29E003871A1 /* ObjectTextLinePresenter.swift */; }; + 31439A422672C3A3003871A1 /* ObjectEditLinePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A412672C3A3003871A1 /* ObjectEditLinePresenter.swift */; }; + 31439A442672C4E6003871A1 /* ObjectSwitchLinePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A432672C4E6003871A1 /* ObjectSwitchLinePresenter.swift */; }; + 31439A5C2673DE2B003871A1 /* ObjectOptionsLinePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A5B2673DE2B003871A1 /* ObjectOptionsLinePresenter.swift */; }; + 31439A842677F665003871A1 /* ObjectCompositeLinePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31439A832677F665003871A1 /* ObjectCompositeLinePresenter.swift */; }; + 3145CE0725F432BE00BCCFCA /* GraphingListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3145CE0625F432BE00BCCFCA /* GraphingListPresenter.swift */; }; + 3145CE1725F437A700BCCFCA /* LineGraphingPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3145CE1625F437A700BCCFCA /* LineGraphingPresenter.swift */; }; + 314614432703B6B60000DDFF /* TimeAxisFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314614422703B6B60000DDFF /* TimeAxisFormatter.swift */; }; + 314B5FD123DCCE2900139EB3 /* PrivacyPermissionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F6E23DCCE2700139EB3 /* PrivacyPermissionPresenter.swift */; }; + 314B5FD223DCCE2900139EB3 /* PrivacyPermissionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F6E23DCCE2700139EB3 /* PrivacyPermissionPresenter.swift */; }; + 314B5FD323DCCE2900139EB3 /* BackgroundTasksPoolInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7023DCCE2700139EB3 /* BackgroundTasksPoolInteractor.swift */; }; + 314B5FD423DCCE2900139EB3 /* BackgroundTasksPoolInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7023DCCE2700139EB3 /* BackgroundTasksPoolInteractor.swift */; }; + 314B5FD523DCCE2900139EB3 /* UIView+Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7223DCCE2700139EB3 /* UIView+Binding.swift */; }; + 314B5FD623DCCE2900139EB3 /* UIView+Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7223DCCE2700139EB3 /* UIView+Binding.swift */; }; + 314B5FD723DCCE2900139EB3 /* ListInteractorPresenterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7323DCCE2700139EB3 /* ListInteractorPresenterView.swift */; }; + 314B5FD823DCCE2900139EB3 /* ListInteractorPresenterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7323DCCE2700139EB3 /* ListInteractorPresenterView.swift */; }; + 314B5FD923DCCE2900139EB3 /* ObjectPresenterContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7423DCCE2700139EB3 /* ObjectPresenterContainerView.swift */; }; + 314B5FDA23DCCE2900139EB3 /* ObjectPresenterContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7423DCCE2700139EB3 /* ObjectPresenterContainerView.swift */; }; + 314B5FDB23DCCE2900139EB3 /* ObjectPresenterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7523DCCE2700139EB3 /* ObjectPresenterView.swift */; }; + 314B5FDC23DCCE2900139EB3 /* ObjectPresenterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7523DCCE2700139EB3 /* ObjectPresenterView.swift */; }; + 314B5FDD23DCCE2900139EB3 /* ParallaxObjectPresenterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7623DCCE2700139EB3 /* ParallaxObjectPresenterTableViewCell.swift */; }; + 314B5FDE23DCCE2900139EB3 /* ParallaxObjectPresenterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7623DCCE2700139EB3 /* ParallaxObjectPresenterTableViewCell.swift */; }; + 314B5FE123DCCE2900139EB3 /* UIView+Xib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7823DCCE2700139EB3 /* UIView+Xib.swift */; }; + 314B5FE223DCCE2900139EB3 /* UIView+Xib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7823DCCE2700139EB3 /* UIView+Xib.swift */; }; + 314B5FE323DCCE2900139EB3 /* ObjectPresenterCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7923DCCE2700139EB3 /* ObjectPresenterCollectionViewCell.swift */; }; + 314B5FE423DCCE2900139EB3 /* ObjectPresenterCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7923DCCE2700139EB3 /* ObjectPresenterCollectionViewCell.swift */; }; + 314B5FE523DCCE2900139EB3 /* ObjectLikeBarItemPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7A23DCCE2700139EB3 /* ObjectLikeBarItemPresenter.swift */; }; + 314B5FE623DCCE2900139EB3 /* ObjectLikeBarItemPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7A23DCCE2700139EB3 /* ObjectLikeBarItemPresenter.swift */; }; + 314B5FE723DCCE2900139EB3 /* XibActionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7B23DCCE2700139EB3 /* XibActionPresenter.swift */; }; + 314B5FE823DCCE2900139EB3 /* XibActionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7B23DCCE2700139EB3 /* XibActionPresenter.swift */; }; + 314B5FE923DCCE2900139EB3 /* LikedSmartObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7C23DCCE2700139EB3 /* LikedSmartObjectPresenter.swift */; }; + 314B5FEA23DCCE2900139EB3 /* LikedSmartObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7C23DCCE2700139EB3 /* LikedSmartObjectPresenter.swift */; }; + 314B5FEB23DCCE2900139EB3 /* SmartObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7D23DCCE2700139EB3 /* SmartObjectPresenter.swift */; }; + 314B5FEC23DCCE2900139EB3 /* SmartObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7D23DCCE2700139EB3 /* SmartObjectPresenter.swift */; }; + 314B5FED23DCCE2900139EB3 /* ActionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7E23DCCE2700139EB3 /* ActionPresenter.swift */; }; + 314B5FEE23DCCE2900139EB3 /* ActionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7E23DCCE2700139EB3 /* ActionPresenter.swift */; }; + 314B5FEF23DCCE2900139EB3 /* ObjectPresenterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7F23DCCE2700139EB3 /* ObjectPresenterTableViewCell.swift */; }; + 314B5FF023DCCE2900139EB3 /* ObjectPresenterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F7F23DCCE2700139EB3 /* ObjectPresenterTableViewCell.swift */; }; + 314B5FF123DCCE2900139EB3 /* TransformerTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8123DCCE2700139EB3 /* TransformerTracker.swift */; }; + 314B5FF223DCCE2900139EB3 /* TransformerTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8123DCCE2700139EB3 /* TransformerTracker.swift */; }; + 314B5FF323DCCE2900139EB3 /* Attribution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8323DCCE2700139EB3 /* Attribution.swift */; }; + 314B5FF423DCCE2900139EB3 /* Attribution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8323DCCE2700139EB3 /* Attribution.swift */; }; + 314B5FF523DCCE2900139EB3 /* JsonDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8523DCCE2700139EB3 /* JsonDocument.swift */; }; + 314B5FF723DCCE2900139EB3 /* JsonAppGroupFileCaching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8623DCCE2700139EB3 /* JsonAppGroupFileCaching.swift */; }; + 314B5FFD23DCCE2900139EB3 /* XibGridPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8B23DCCE2700139EB3 /* XibGridPresenter.swift */; }; + 314B5FFE23DCCE2900139EB3 /* XibGridPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8B23DCCE2700139EB3 /* XibGridPresenter.swift */; }; + 314B5FFF23DCCE2900139EB3 /* GridPresenter+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8C23DCCE2700139EB3 /* GridPresenter+iOS.swift */; }; + 314B600023DCCE2900139EB3 /* GridPresenter+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8C23DCCE2700139EB3 /* GridPresenter+iOS.swift */; }; + 314B600123DCCE2900139EB3 /* CollectionViewGridPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8E23DCCE2700139EB3 /* CollectionViewGridPresenter.swift */; }; + 314B600223DCCE2900139EB3 /* CollectionViewGridPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8E23DCCE2700139EB3 /* CollectionViewGridPresenter.swift */; }; + 314B600323DCCE2900139EB3 /* NativeGridPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8F23DCCE2700139EB3 /* NativeGridPresenter.swift */; }; + 314B600423DCCE2900139EB3 /* NativeGridPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F8F23DCCE2700139EB3 /* NativeGridPresenter.swift */; }; + 314B600523DCCE2900139EB3 /* ParticlesAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9123DCCE2800139EB3 /* ParticlesAppDelegate.swift */; }; + 314B600623DCCE2900139EB3 /* ParticlesAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9123DCCE2800139EB3 /* ParticlesAppDelegate.swift */; }; + 314B600923DCCE2900139EB3 /* ErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9423DCCE2800139EB3 /* ErrorAlert.swift */; }; + 314B600A23DCCE2900139EB3 /* ErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9423DCCE2800139EB3 /* ErrorAlert.swift */; }; + 314B600B23DCCE2900139EB3 /* GridPresenterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9623DCCE2800139EB3 /* GridPresenterViewController.swift */; }; + 314B600C23DCCE2900139EB3 /* GridPresenterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9623DCCE2800139EB3 /* GridPresenterViewController.swift */; }; + 314B600D23DCCE2900139EB3 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9723DCCE2800139EB3 /* SearchViewController.swift */; }; + 314B600E23DCCE2900139EB3 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9723DCCE2800139EB3 /* SearchViewController.swift */; }; + 314B600F23DCCE2900139EB3 /* UpgradeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9823DCCE2800139EB3 /* UpgradeViewController.swift */; }; + 314B601023DCCE2900139EB3 /* UpgradeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9823DCCE2800139EB3 /* UpgradeViewController.swift */; }; + 314B601123DCCE2900139EB3 /* DataPresenterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9923DCCE2800139EB3 /* DataPresenterViewController.swift */; }; + 314B601223DCCE2900139EB3 /* DataPresenterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9923DCCE2800139EB3 /* DataPresenterViewController.swift */; }; + 314B601323DCCE2900139EB3 /* ListPresenterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9A23DCCE2800139EB3 /* ListPresenterViewController.swift */; }; + 314B601423DCCE2900139EB3 /* ListPresenterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9A23DCCE2800139EB3 /* ListPresenterViewController.swift */; }; + 314B601523DCCE2900139EB3 /* TrackingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9B23DCCE2800139EB3 /* TrackingViewController.swift */; }; + 314B601623DCCE2900139EB3 /* TrackingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9B23DCCE2800139EB3 /* TrackingViewController.swift */; }; + 314B601723DCCE2900139EB3 /* PrivacyPermissionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9C23DCCE2800139EB3 /* PrivacyPermissionViewController.swift */; }; + 314B601823DCCE2900139EB3 /* PrivacyPermissionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5F9C23DCCE2800139EB3 /* PrivacyPermissionViewController.swift */; }; + 314B601B23DCCE2900139EB3 /* MessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FA023DCCE2800139EB3 /* MessageAction.swift */; }; + 314B601D23DCCE2900139EB3 /* ConfirmationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FA223DCCE2800139EB3 /* ConfirmationAction.swift */; }; + 314B601E23DCCE2900139EB3 /* ConfirmationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FA223DCCE2800139EB3 /* ConfirmationAction.swift */; }; + 314B602123DCCE2900139EB3 /* CameraPermissionAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FA523DCCE2800139EB3 /* CameraPermissionAction.swift */; }; + 314B602523DCCE2900139EB3 /* PhotoAlbumsPermissionAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FA723DCCE2800139EB3 /* PhotoAlbumsPermissionAction.swift */; }; + 314B602623DCCE2900139EB3 /* PhotoAlbumsPermissionAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FA723DCCE2800139EB3 /* PhotoAlbumsPermissionAction.swift */; }; + 314B602723DCCE2900139EB3 /* CalendarPermissionAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FA823DCCE2800139EB3 /* CalendarPermissionAction.swift */; }; + 314B602823DCCE2900139EB3 /* CalendarPermissionAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FA823DCCE2800139EB3 /* CalendarPermissionAction.swift */; }; + 314B602923DCCE2900139EB3 /* NotificationPermissionAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FA923DCCE2800139EB3 /* NotificationPermissionAction.swift */; }; + 314B602A23DCCE2900139EB3 /* NotificationPermissionAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FA923DCCE2800139EB3 /* NotificationPermissionAction.swift */; }; + 314B602B23DCCE2900139EB3 /* PrivacyPermissionAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FAA23DCCE2800139EB3 /* PrivacyPermissionAction.swift */; }; + 314B602C23DCCE2900139EB3 /* PrivacyPermissionAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FAA23DCCE2800139EB3 /* PrivacyPermissionAction.swift */; }; + 314B602D23DCCE2900139EB3 /* ParticlesPlatformAppInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FAC23DCCE2800139EB3 /* ParticlesPlatformAppInjection.swift */; }; + 314B602E23DCCE2900139EB3 /* ParticlesPlatformAppInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FAC23DCCE2800139EB3 /* ParticlesPlatformAppInjection.swift */; }; + 314B602F23DCCE2900139EB3 /* ParticlesPlatformExtensionInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FAD23DCCE2800139EB3 /* ParticlesPlatformExtensionInjection.swift */; }; + 314B603023DCCE2900139EB3 /* ParticlesPlatformExtensionInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FAD23DCCE2800139EB3 /* ParticlesPlatformExtensionInjection.swift */; }; + 314B603123DCCE2900139EB3 /* ParticlesPlatformInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FAE23DCCE2800139EB3 /* ParticlesPlatformInjection.swift */; }; + 314B603223DCCE2900139EB3 /* ParticlesPlatformInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FAE23DCCE2800139EB3 /* ParticlesPlatformInjection.swift */; }; + 314B603523DCCE2900139EB3 /* ProgressPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB123DCCE2800139EB3 /* ProgressPresenter.swift */; }; + 314B603623DCCE2900139EB3 /* ProgressPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB123DCCE2800139EB3 /* ProgressPresenter.swift */; }; + 314B603723DCCE2900139EB3 /* ListPresenter+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB323DCCE2800139EB3 /* ListPresenter+iOS.swift */; }; + 314B603823DCCE2900139EB3 /* ListPresenter+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB323DCCE2800139EB3 /* ListPresenter+iOS.swift */; }; + 314B603923DCCE2900139EB3 /* CollectionViewXibRegister.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB523DCCE2800139EB3 /* CollectionViewXibRegister.swift */; }; + 314B603A23DCCE2900139EB3 /* CollectionViewXibRegister.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB523DCCE2800139EB3 /* CollectionViewXibRegister.swift */; }; + 314B603B23DCCE2900139EB3 /* RigidCollectionViewListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB623DCCE2800139EB3 /* RigidCollectionViewListPresenter.swift */; }; + 314B603C23DCCE2900139EB3 /* RigidCollectionViewListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB623DCCE2800139EB3 /* RigidCollectionViewListPresenter.swift */; }; + 314B603D23DCCE2900139EB3 /* CollectionViewListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB723DCCE2800139EB3 /* CollectionViewListPresenter.swift */; }; + 314B603E23DCCE2900139EB3 /* CollectionViewListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB723DCCE2800139EB3 /* CollectionViewListPresenter.swift */; }; + 314B603F23DCCE2900139EB3 /* CollectionViewSectionListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB823DCCE2800139EB3 /* CollectionViewSectionListPresenter.swift */; }; + 314B604023DCCE2900139EB3 /* CollectionViewSectionListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB823DCCE2800139EB3 /* CollectionViewSectionListPresenter.swift */; }; + 314B604123DCCE2900139EB3 /* XibListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB923DCCE2800139EB3 /* XibListPresenter.swift */; }; + 314B604223DCCE2900139EB3 /* XibListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FB923DCCE2800139EB3 /* XibListPresenter.swift */; }; + 314B604323DCCE2900139EB3 /* CarouselListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FBB23DCCE2800139EB3 /* CarouselListPresenter.swift */; }; + 314B604523DCCE2900139EB3 /* CalendarViewListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FBD23DCCE2800139EB3 /* CalendarViewListPresenter.swift */; }; + 314B604723DCCE2900139EB3 /* ListLoadingPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FBF23DCCE2800139EB3 /* ListLoadingPresenter.swift */; }; + 314B604823DCCE2900139EB3 /* ListLoadingPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FBF23DCCE2800139EB3 /* ListLoadingPresenter.swift */; }; + 314B604923DCCE2900139EB3 /* BarButtonSwitchedListPresenterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC023DCCE2800139EB3 /* BarButtonSwitchedListPresenterManager.swift */; }; + 314B604A23DCCE2900139EB3 /* BarButtonSwitchedListPresenterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC023DCCE2800139EB3 /* BarButtonSwitchedListPresenterManager.swift */; }; + 314B604B23DCCE2900139EB3 /* GridPresenterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC123DCCE2800139EB3 /* GridPresenterManager.swift */; }; + 314B604C23DCCE2900139EB3 /* GridPresenterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC123DCCE2800139EB3 /* GridPresenterManager.swift */; }; + 314B604D23DCCE2900139EB3 /* ListPresenterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC223DCCE2800139EB3 /* ListPresenterManager.swift */; }; + 314B604E23DCCE2900139EB3 /* ListPresenterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC223DCCE2800139EB3 /* ListPresenterManager.swift */; }; + 314B604F23DCCE2900139EB3 /* StackViewListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC423DCCE2800139EB3 /* StackViewListPresenter.swift */; }; + 314B605023DCCE2900139EB3 /* StackViewListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC423DCCE2800139EB3 /* StackViewListPresenter.swift */; }; + 314B605123DCCE2900139EB3 /* NativeListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC523DCCE2800139EB3 /* NativeListPresenter.swift */; }; + 314B605223DCCE2900139EB3 /* NativeListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC523DCCE2800139EB3 /* NativeListPresenter.swift */; }; + 314B605323DCCE2900139EB3 /* TableViewSectionListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC723DCCE2800139EB3 /* TableViewSectionListPresenter.swift */; }; + 314B605423DCCE2900139EB3 /* TableViewSectionListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC723DCCE2800139EB3 /* TableViewSectionListPresenter.swift */; }; + 314B605523DCCE2900139EB3 /* TableViewListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC823DCCE2800139EB3 /* TableViewListPresenter.swift */; }; + 314B605923DCCE2900139EB3 /* TableViewXibRegister.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FCA23DCCE2800139EB3 /* TableViewXibRegister.swift */; }; + 314B605A23DCCE2900139EB3 /* TableViewXibRegister.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FCA23DCCE2800139EB3 /* TableViewXibRegister.swift */; }; + 314B605F23DCCE2900139EB3 /* AppInfoPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FCF23DCCE2900139EB3 /* AppInfoPresenter.swift */; }; + 314B606023DCCE2900139EB3 /* AppInfoPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FCF23DCCE2900139EB3 /* AppInfoPresenter.swift */; }; + 314B677923DCEEEB00139EB3 /* XibPresenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B677523DCEEEB00139EB3 /* XibPresenterProtocol.swift */; }; + 314B677A23DCEEEB00139EB3 /* XibPresenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B677523DCEEEB00139EB3 /* XibPresenterProtocol.swift */; }; + 314B677B23DCEEEB00139EB3 /* XibPresenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B677523DCEEEB00139EB3 /* XibPresenterProtocol.swift */; }; + 314C35CC24C7C31400695F7E /* Version.xib in Resources */ = {isa = PBXBuildFile; fileRef = 314C35CB24C7C31400695F7E /* Version.xib */; }; + 314C35D024C7C35000695F7E /* PhotoAlbumsPermissionAction.xib in Resources */ = {isa = PBXBuildFile; fileRef = 314C35CD24C7C35000695F7E /* PhotoAlbumsPermissionAction.xib */; }; + 314C35D324C7C35000695F7E /* CameraPermissionAction.xib in Resources */ = {isa = PBXBuildFile; fileRef = 314C35CE24C7C35000695F7E /* CameraPermissionAction.xib */; }; + 314C35D624C7C35000695F7E /* NotificationPermissionAction.xib in Resources */ = {isa = PBXBuildFile; fileRef = 314C35CF24C7C35000695F7E /* NotificationPermissionAction.xib */; }; + 314C35D924C7C36E00695F7E /* NotificationPermissionAction.xib in Resources */ = {isa = PBXBuildFile; fileRef = 314C35CF24C7C35000695F7E /* NotificationPermissionAction.xib */; }; + 314C35E024C7C3A600695F7E /* TableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 314C35DB24C7C3A600695F7E /* TableViewCell.xib */; }; + 314C35E124C7C3A600695F7E /* CollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 314C35DC24C7C3A600695F7E /* CollectionViewCell.xib */; }; + 314C35E224C7C3A600695F7E /* text_collection_cell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 314C35DD24C7C3A600695F7E /* text_collection_cell.xib */; }; + 314C35E324C7C3A600695F7E /* image_collection_cell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 314C35DE24C7C3A600695F7E /* image_collection_cell.xib */; }; + 314C35E424C7C3A600695F7E /* ParallaxTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 314C35DF24C7C3A600695F7E /* ParallaxTableViewCell.xib */; }; + 3165647926E2BDEC002D797E /* ListObjectViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3165647826E2BDEC002D797E /* ListObjectViewPresenter.swift */; }; + 317556702628ED3400D76C4C /* CandleStickGraphingPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3175566F2628ED3400D76C4C /* CandleStickGraphingPresenter.swift */; }; + 317E99A723D80E4900C348FA /* ParticlesCommonModels.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 317E999223D80E0800C348FA /* ParticlesCommonModels.framework */; }; + 317E99AA23D80E5300C348FA /* ParticlesCommonModels.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 317E999423D80E0800C348FA /* ParticlesCommonModels.framework */; }; + 317E99AD23D80E5B00C348FA /* ParticlesCommonModels.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 317E999623D80E0800C348FA /* ParticlesCommonModels.framework */; }; + 317F18C02572DA8A00D178B8 /* image_action_cell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 317F18B12572DA8A00D178B8 /* image_action_cell.xib */; }; + 317F18C12572DA8A00D178B8 /* text_table_cell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 317F18BF2572DA8A00D178B8 /* text_table_cell.xib */; }; + 3180B05C272B509400CCAB67 /* DismissAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3180B04E272B509400CCAB67 /* DismissAction.swift */; }; + 3180B060272C770E00CCAB67 /* ParticlesChartDataEntryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3180B05F272C770E00CCAB67 /* ParticlesChartDataEntryProtocol.swift */; }; + 318A5BA8272CA108000DA46C /* ParticlesPieChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318A5B9A272CA107000DA46C /* ParticlesPieChartDataEntry.swift */; }; + 3197614A26850649008EB757 /* ObjectWebLinePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3197614926850649008EB757 /* ObjectWebLinePresenter.swift */; }; + 31A071A626B890C20069C58A /* TableViewListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B5FC823DCCE2800139EB3 /* TableViewListPresenter.swift */; }; + 31A53E892713693F0097BFE4 /* BarLineChartViewBase+Scaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A53E882713693F0097BFE4 /* BarLineChartViewBase+Scaling.swift */; }; + 31AA4B042704BF1300451307 /* GraphingAxisFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AA4B032704BF1300451307 /* GraphingAxisFormatter.swift */; }; + 31AA4B2C2704D37900451307 /* CandleStickGraphingRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AA4B2B2704D37900451307 /* CandleStickGraphingRenderer.swift */; }; + 31ACB73321B0EE2D00391ADF /* PlatformParticles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB72921B0EE2D00391ADF /* PlatformParticles.framework */; }; + 31ACB73821B0EE2D00391ADF /* PlatformParticlesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ACB73721B0EE2D00391ADF /* PlatformParticlesTests.swift */; }; + 31ACB73A21B0EE2D00391ADF /* PlatformParticles.h in Headers */ = {isa = PBXBuildFile; fileRef = 31ACB72C21B0EE2D00391ADF /* PlatformParticles.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 31ACB7C121B0F07A00391ADF /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB7A921B0EFF300391ADF /* Utilities.framework */; }; + 31ACB7C221B0F07A00391ADF /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB79721B0EFF300391ADF /* ParticlesKit.framework */; }; + 31ACB7C321B0F07A00391ADF /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB78E21B0EFF300391ADF /* RoutingKit.framework */; }; + 31ACB7C421B0F07A00391ADF /* UIToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB7A021B0EFF300391ADF /* UIToolkits.framework */; }; + 31ACB7D021B0F10A00391ADF /* PlatformRouting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB7CB21B0F0F800391ADF /* PlatformRouting.framework */; }; + 31B0610026A8D1B500431CFA /* GraphingPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B060FF26A8D1B500431CFA /* GraphingPresenter.swift */; }; + 31C7E6882551CE21000643B0 /* ListInteractor.xib in Resources */ = {isa = PBXBuildFile; fileRef = 31C7E67A2551CE21000643B0 /* ListInteractor.xib */; }; + 31CC977A26CB14EF00E56B83 /* CombinedGraphingPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC977926CB14EF00E56B83 /* CombinedGraphingPresenter.swift */; }; + 31CFF7CA21D7D1F300DE7A79 /* MessageParticles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31CFF7C121D7D1F200DE7A79 /* MessageParticles.framework */; }; + 31CFF7CF21D7D1F300DE7A79 /* MessageParticlesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CFF7CE21D7D1F300DE7A79 /* MessageParticlesTests.swift */; }; + 31CFF7D121D7D1F300DE7A79 /* MessageParticles.h in Headers */ = {isa = PBXBuildFile; fileRef = 31CFF7C321D7D1F200DE7A79 /* MessageParticles.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 31CFF7DC21D7D23C00DE7A79 /* PlatformParticles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB72921B0EE2D00391ADF /* PlatformParticles.framework */; }; + 31CFF7DD21D7D23C00DE7A79 /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB79721B0EFF300391ADF /* ParticlesKit.framework */; }; + 31CFF7DE21D7D23C00DE7A79 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB78E21B0EFF300391ADF /* RoutingKit.framework */; }; + 31CFF7DF21D7D23C00DE7A79 /* UIToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB7A021B0EFF300391ADF /* UIToolkits.framework */; }; + 31CFF7E021D7D23C00DE7A79 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31ACB7A921B0EFF300391ADF /* Utilities.framework */; }; + 31E1B30B26B0738200A4E3FC /* PieGraphingPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E1B2FD26B0738200A4E3FC /* PieGraphingPresenter.swift */; }; + 31E822022555C38A001FE2F1 /* ThinlinePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E822012555C38A001FE2F1 /* ThinlinePresenter.swift */; }; + 31E8221F2555C3FD001FE2F1 /* Thinline.xib in Resources */ = {isa = PBXBuildFile; fileRef = 31E8221E2555C3FD001FE2F1 /* Thinline.xib */; }; + 31E823D825566599001FE2F1 /* PermissionPrimerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E823D725566598001FE2F1 /* PermissionPrimerViewController.swift */; }; + 31EAFE3025C63A8800412F11 /* NavigationObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EAFE2F25C63A8800412F11 /* NavigationObjectPresenter.swift */; }; + 31EAFE3125C63A8800412F11 /* NavigationObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EAFE2F25C63A8800412F11 /* NavigationObjectPresenter.swift */; }; + 31EAFE3225C63A8800412F11 /* NavigationObjectPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EAFE2F25C63A8800412F11 /* NavigationObjectPresenter.swift */; }; + 31ECFF58253C986E00D6336F /* TableViewListPresenter+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ECFF57253C986E00D6336F /* TableViewListPresenter+Actions.swift */; }; + 31ED732027345B3C0032B501 /* ParticlesChartDataSetProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ED731F27345B3C0032B501 /* ParticlesChartDataSetProtocol.swift */; }; + 31ED7322273460350032B501 /* ParticlesLineChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ED7321273460350032B501 /* ParticlesLineChartDataSet.swift */; }; + 31ED7324273460D40032B501 /* ParticlesBarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ED7323273460D40032B501 /* ParticlesBarChartDataSet.swift */; }; + 31ED7326273460F20032B501 /* ParticlesPieChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ED7325273460F20032B501 /* ParticlesPieChartDataSet.swift */; }; + 31ED7328273461130032B501 /* ParticlesCandleChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ED7327273461130032B501 /* ParticlesCandleChartDataSet.swift */; }; + 643494AA27B810E000A0CC09 /* TimeIntervalObjectViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643494A927B810E000A0CC09 /* TimeIntervalObjectViewPresenter.swift */; }; + 648F46C727D8090800FD2FC6 /* XibTableViewHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648F46C627D8090800FD2FC6 /* XibTableViewHeaderFooterView.swift */; }; + 648F46D627D80B4400FD2FC6 /* TableViewHeaderXibRegister.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648F46D527D80B4400FD2FC6 /* TableViewHeaderXibRegister.swift */; }; + 64AF47832817123C00EBFDC6 /* SafariAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AF47822817123C00EBFDC6 /* SafariAction.swift */; }; + 6A4D707CD262BA071D79871D /* Pods_iOS_PlatformParticlesTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE57233C9BB53C21FDEFE974 /* Pods_iOS_PlatformParticlesTests.framework */; }; + F1601E34A925CDC119296640 /* Pods_iOS_PlatformParticles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68ED3D120714A6AAFC95CDFC /* Pods_iOS_PlatformParticles.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 310C3B1621D9C9BF0081E56D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 310C3A0A21D9623E0081E56D; + remoteInfo = UIAppToolkits; + }; + 310C3B2621D9C9BF0081E56D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310C3A0B21D9623E0081E56D; + remoteInfo = UIAppToolkits; + }; + 310C3B2821D9C9BF0081E56D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310C3A1321D9623F0081E56D; + remoteInfo = UIAppToolkitsTests; + }; + 313EB28D21BA299300BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7A321B0EFF300391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 313EB28F21BA299300BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7A321B0EFF300391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 313EB29321BA299300BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7A321B0EFF300391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 313EB29D21BA299300BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79121B0EFF300391ADF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 319682B421B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 313EB29F21BA299300BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79121B0EFF300391ADF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16121BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 313EB2A321BA299300BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79121B0EFF300391ADF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16921BA246A00BEF926; + remoteInfo = ParticlesKitAppleTVTests; + }; + 313EB2AE21BA299300BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB78821B0EFF300391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196825B21B7947600AE0F28; + remoteInfo = RoutingKitAppleWatch; + }; + 313EB2B021BA299300BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB78821B0EFF300391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22321BA26D700BEF926; + remoteInfo = RoutingKitAppleTV; + }; + 313EB2B421BA299300BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB78821B0EFF300391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22B21BA26D800BEF926; + remoteInfo = RoutingKitAppleTVTests; + }; + 313EB7BE21BB591200BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7C521B0F0F800391ADF /* PlatformRouting.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB75F21BB4CA300BEF926; + remoteInfo = PlatformRoutingAppleWatch; + }; + 313EB7CE21BB596000BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7A321B0EFF300391ADF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 3196823621B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 313EB7D021BB596000BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79121B0EFF300391ADF /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 319682B321B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 313EB7D221BB596000BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB78821B0EFF300391ADF /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 3196825A21B7947600AE0F28; + remoteInfo = RoutingKitAppleWatch; + }; + 313EB7D421BB596000BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7C521B0F0F800391ADF /* PlatformRouting.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313EB75E21BB4CA300BEF926; + remoteInfo = PlatformRoutingAppleWatch; + }; + 313EB86A21BB73B400BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB85D21BB730700BEF926; + remoteInfo = UIToolkitsAppleWatch; + }; + 313EB86D21BB73D300BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313EB85C21BB730700BEF926; + remoteInfo = UIToolkitsAppleWatch; + }; + 313EBB4D21BB7A4B00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB72021B0EE2D00391ADF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 313EBB4221BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTV; + }; + 313EBB5821BB7A4B00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB0421BB79AD00BEF926; + remoteInfo = UIToolkitsAppleTV; + }; + 313EBB5C21BB7A4B00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB0C21BB79AD00BEF926; + remoteInfo = UIToolkitsAppleTVTests; + }; + 313EBCD121BB7D1100BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7C521B0F0F800391ADF /* PlatformRouting.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBBE521BB7B7D00BEF926; + remoteInfo = PlatformRoutingAppleTV; + }; + 313EBCD521BB7D1100BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7C521B0F0F800391ADF /* PlatformRouting.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBBED21BB7B7D00BEF926; + remoteInfo = PlatformRoutingAppleTVTests; + }; + 313EBCDB21BB7D2C00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7A321B0EFF300391ADF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313A536421B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 313EBCDD21BB7D2C00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79121B0EFF300391ADF /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313EB16021BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 313EBCDF21BB7D2C00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB78821B0EFF300391ADF /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313EB22221BA26D700BEF926; + remoteInfo = RoutingKitAppleTV; + }; + 313EBCE121BB7D2C00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7C521B0F0F800391ADF /* PlatformRouting.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313EBBE421BB7B7D00BEF926; + remoteInfo = PlatformRoutingAppleTV; + }; + 313EBCE321BB7D2C00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313EBB0321BB79AD00BEF926; + remoteInfo = UIToolkitsAppleTV; + }; + 317E999123D80E0800C348FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 317E998723D80E0800C348FA /* ParticlesCommonModels.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310CDE5221C8C133009665CF; + remoteInfo = ParticlesCommonModels; + }; + 317E999323D80E0800C348FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 317E998723D80E0800C348FA /* ParticlesCommonModels.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310CDF1A21C8C3DC009665CF; + remoteInfo = ParticlesCommonModelsAppleWatch; + }; + 317E999523D80E0800C348FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 317E998723D80E0800C348FA /* ParticlesCommonModels.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310CDF2721C8C3F0009665CF; + remoteInfo = ParticlesCommonModelsAppleTV; + }; + 317E999923D80E0800C348FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 317E998723D80E0800C348FA /* ParticlesCommonModels.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310CDE5B21C8C133009665CF; + remoteInfo = ParticlesCommonModelsTests; + }; + 317E999B23D80E0800C348FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 317E998723D80E0800C348FA /* ParticlesCommonModels.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310CDF2F21C8C3F0009665CF; + remoteInfo = ParticlesCommonModelsAppleTVTests; + }; + 317E999F23D80E1B00C348FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 317E998723D80E0800C348FA /* ParticlesCommonModels.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 310CDE5121C8C133009665CF; + remoteInfo = ParticlesCommonModels; + }; + 317E99A123D80E2B00C348FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 317E998723D80E0800C348FA /* ParticlesCommonModels.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 310CDF2621C8C3F0009665CF; + remoteInfo = ParticlesCommonModelsAppleTV; + }; + 317E99A323D80E3100C348FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 317E998723D80E0800C348FA /* ParticlesCommonModels.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 310CDF1921C8C3DC009665CF; + remoteInfo = ParticlesCommonModelsAppleWatch; + }; + 31ACB73421B0EE2D00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB72021B0EE2D00391ADF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31ACB72821B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 31ACB78D21B0EFF300391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB78821B0EFF300391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B01216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 31ACB78F21B0EFF300391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB78821B0EFF300391ADF /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B0A216BCA10008ABEE9; + remoteInfo = RoutingKitTests; + }; + 31ACB79621B0EFF300391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79121B0EFF300391ADF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FDA21B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 31ACB79821B0EFF300391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79121B0EFF300391ADF /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FE321B0E9C4001775BA; + remoteInfo = ParticlesKitTests; + }; + 31ACB79F21B0EFF300391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65ADD216BC9E1008ABEE9; + remoteInfo = UIToolkits; + }; + 31ACB7A121B0EFF300391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AE6216BC9E1008ABEE9; + remoteInfo = UIToolkitsTests; + }; + 31ACB7A821B0EFF300391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7A321B0EFF300391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 31ACB7AA21B0EFF300391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7A321B0EFF300391ADF /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 31ACB7B721B0F05E00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7A321B0EFF300391ADF /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 31ACB7B921B0F05E00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79121B0EFF300391ADF /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 311C0FD921B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 31ACB7BB21B0F05E00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB78821B0EFF300391ADF /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65B00216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 31ACB7BD21B0F05E00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65ADC216BC9E1008ABEE9; + remoteInfo = UIToolkits; + }; + 31ACB7CA21B0F0F800391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7C521B0F0F800391ADF /* PlatformRouting.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CEE9B52170FA2700DC61DA; + remoteInfo = PlatformRouting; + }; + 31ACB7CC21B0F0F800391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7C521B0F0F800391ADF /* PlatformRouting.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CEE9BE2170FA2700DC61DA; + remoteInfo = PlatformRoutingTests; + }; + 31ACB7CE21B0F0FF00391ADF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB7C521B0F0F800391ADF /* PlatformRouting.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31CEE9B42170FA2700DC61DA; + remoteInfo = PlatformRouting; + }; + 31CFF7CB21D7D1F300DE7A79 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB72021B0EE2D00391ADF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31CFF7C021D7D1F200DE7A79; + remoteInfo = MessageParticles; + }; + 31CFF7DA21D7D21200DE7A79 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31ACB72021B0EE2D00391ADF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31ACB72821B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 27ED35392AD5BD0900C159F5 /* BannerErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerErrorAlert.swift; sourceTree = ""; }; + 3101F9C42511303E00AC4010 /* ConfirmAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmAction.swift; sourceTree = ""; }; + 3101F9C92511308E00AC4010 /* AuthLoginPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthLoginPresenter.swift; sourceTree = ""; }; + 3101F9CC251130D100AC4010 /* AuthLoginPrimerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthLoginPrimerViewController.swift; sourceTree = ""; }; + 3101F9D02511311600AC4010 /* AuthLoginPrimer.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = AuthLoginPrimer.storyboard; sourceTree = ""; }; + 3101F9D22511314200AC4010 /* ConfirmAction.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ConfirmAction.xib; sourceTree = ""; }; + 3101F9D42511317800AC4010 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; + 310D88FF262DF3EC00FEF56A /* ObjectViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectViewPresenter.swift; sourceTree = ""; }; + 3111C2422543AD2E00F204D9 /* CompositeObjectPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositeObjectPresenter.swift; sourceTree = ""; }; + 31308B8124FAEF86003B5B9A /* ColoredClusterPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColoredClusterPresenter.swift; sourceTree = ""; }; + 31308B8324FAF01E003B5B9A /* LocationPermissionAction.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LocationPermissionAction.xib; sourceTree = ""; }; + 31347F6D2710FDF40095829A /* ParticlesCandleChartDataEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesCandleChartDataEntry.swift; sourceTree = ""; }; + 31347F7727110DAD0095829A /* ParticlesLineChartDataEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticlesLineChartDataEntry.swift; sourceTree = ""; }; + 31347F7827110DAE0095829A /* ParticlesBarChartDataEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticlesBarChartDataEntry.swift; sourceTree = ""; }; + 313EB7C621BB592E00BEF926 /* PlatformParticles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlatformParticles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EB7C821BB592E00BEF926 /* PlatformParticles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformParticles.h; sourceTree = ""; }; + 313EB7C921BB592E00BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EB7DE21BB5AB800BEF926 /* TableViewListPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewListPresenter.swift; sourceTree = ""; }; + 313EB86821BB73B400BEF926 /* XibListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibListPresenter.swift; sourceTree = ""; }; + 313EBB4321BB7A4B00BEF926 /* PlatformParticles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlatformParticles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EBB4521BB7A4B00BEF926 /* PlatformParticles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformParticles.h; sourceTree = ""; }; + 313EBB4621BB7A4B00BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EBB4B21BB7A4B00BEF926 /* PlatformParticlesAppleTVTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PlatformParticlesAppleTVTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EBB5021BB7A4B00BEF926 /* PlatformParticlesAppleTVTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformParticlesAppleTVTests.swift; sourceTree = ""; }; + 313EBB5221BB7A4B00BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EBE0221BC400300BEF926 /* DefaultExtensionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultExtensionDelegate.swift; sourceTree = ""; }; + 313EBF3621BE0C7B00BEF926 /* ListPresenterInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListPresenterInterfaceController.swift; sourceTree = ""; }; + 31439A2A2672BAD6003871A1 /* ObjectLinePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectLinePresenter.swift; sourceTree = ""; }; + 31439A2C2672BC26003871A1 /* ObjectValueLinePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectValueLinePresenter.swift; sourceTree = ""; }; + 31439A3F2672C29E003871A1 /* ObjectTextLinePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectTextLinePresenter.swift; sourceTree = ""; }; + 31439A412672C3A3003871A1 /* ObjectEditLinePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectEditLinePresenter.swift; sourceTree = ""; }; + 31439A432672C4E6003871A1 /* ObjectSwitchLinePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectSwitchLinePresenter.swift; sourceTree = ""; }; + 31439A5B2673DE2B003871A1 /* ObjectOptionsLinePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectOptionsLinePresenter.swift; sourceTree = ""; }; + 31439A832677F665003871A1 /* ObjectCompositeLinePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCompositeLinePresenter.swift; sourceTree = ""; }; + 3145CE0625F432BE00BCCFCA /* GraphingListPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphingListPresenter.swift; sourceTree = ""; }; + 3145CE1625F437A700BCCFCA /* LineGraphingPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGraphingPresenter.swift; sourceTree = ""; }; + 314614422703B6B60000DDFF /* TimeAxisFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeAxisFormatter.swift; sourceTree = ""; }; + 314B5F6E23DCCE2700139EB3 /* PrivacyPermissionPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyPermissionPresenter.swift; sourceTree = ""; }; + 314B5F7023DCCE2700139EB3 /* BackgroundTasksPoolInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundTasksPoolInteractor.swift; sourceTree = ""; }; + 314B5F7223DCCE2700139EB3 /* UIView+Binding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Binding.swift"; sourceTree = ""; }; + 314B5F7323DCCE2700139EB3 /* ListInteractorPresenterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListInteractorPresenterView.swift; sourceTree = ""; }; + 314B5F7423DCCE2700139EB3 /* ObjectPresenterContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectPresenterContainerView.swift; sourceTree = ""; }; + 314B5F7523DCCE2700139EB3 /* ObjectPresenterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectPresenterView.swift; sourceTree = ""; }; + 314B5F7623DCCE2700139EB3 /* ParallaxObjectPresenterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParallaxObjectPresenterTableViewCell.swift; sourceTree = ""; }; + 314B5F7823DCCE2700139EB3 /* UIView+Xib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Xib.swift"; sourceTree = ""; }; + 314B5F7923DCCE2700139EB3 /* ObjectPresenterCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectPresenterCollectionViewCell.swift; sourceTree = ""; }; + 314B5F7A23DCCE2700139EB3 /* ObjectLikeBarItemPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectLikeBarItemPresenter.swift; sourceTree = ""; }; + 314B5F7B23DCCE2700139EB3 /* XibActionPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibActionPresenter.swift; sourceTree = ""; }; + 314B5F7C23DCCE2700139EB3 /* LikedSmartObjectPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LikedSmartObjectPresenter.swift; sourceTree = ""; }; + 314B5F7D23DCCE2700139EB3 /* SmartObjectPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SmartObjectPresenter.swift; sourceTree = ""; }; + 314B5F7E23DCCE2700139EB3 /* ActionPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionPresenter.swift; sourceTree = ""; }; + 314B5F7F23DCCE2700139EB3 /* ObjectPresenterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectPresenterTableViewCell.swift; sourceTree = ""; }; + 314B5F8123DCCE2700139EB3 /* TransformerTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformerTracker.swift; sourceTree = ""; }; + 314B5F8323DCCE2700139EB3 /* Attribution.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Attribution.swift; sourceTree = ""; }; + 314B5F8523DCCE2700139EB3 /* JsonDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonDocument.swift; sourceTree = ""; }; + 314B5F8623DCCE2700139EB3 /* JsonAppGroupFileCaching.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonAppGroupFileCaching.swift; sourceTree = ""; }; + 314B5F8B23DCCE2700139EB3 /* XibGridPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibGridPresenter.swift; sourceTree = ""; }; + 314B5F8C23DCCE2700139EB3 /* GridPresenter+iOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GridPresenter+iOS.swift"; sourceTree = ""; }; + 314B5F8E23DCCE2700139EB3 /* CollectionViewGridPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewGridPresenter.swift; sourceTree = ""; }; + 314B5F8F23DCCE2700139EB3 /* NativeGridPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeGridPresenter.swift; sourceTree = ""; }; + 314B5F9123DCCE2800139EB3 /* ParticlesAppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticlesAppDelegate.swift; sourceTree = ""; }; + 314B5F9423DCCE2800139EB3 /* ErrorAlert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = ""; }; + 314B5F9623DCCE2800139EB3 /* GridPresenterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridPresenterViewController.swift; sourceTree = ""; }; + 314B5F9723DCCE2800139EB3 /* SearchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; + 314B5F9823DCCE2800139EB3 /* UpgradeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpgradeViewController.swift; sourceTree = ""; }; + 314B5F9923DCCE2800139EB3 /* DataPresenterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataPresenterViewController.swift; sourceTree = ""; }; + 314B5F9A23DCCE2800139EB3 /* ListPresenterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListPresenterViewController.swift; sourceTree = ""; }; + 314B5F9B23DCCE2800139EB3 /* TrackingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrackingViewController.swift; sourceTree = ""; }; + 314B5F9C23DCCE2800139EB3 /* PrivacyPermissionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyPermissionViewController.swift; sourceTree = ""; }; + 314B5FA023DCCE2800139EB3 /* MessageAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageAction.swift; sourceTree = ""; }; + 314B5FA223DCCE2800139EB3 /* ConfirmationAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfirmationAction.swift; sourceTree = ""; }; + 314B5FA523DCCE2800139EB3 /* CameraPermissionAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraPermissionAction.swift; sourceTree = ""; }; + 314B5FA723DCCE2800139EB3 /* PhotoAlbumsPermissionAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoAlbumsPermissionAction.swift; sourceTree = ""; }; + 314B5FA823DCCE2800139EB3 /* CalendarPermissionAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarPermissionAction.swift; sourceTree = ""; }; + 314B5FA923DCCE2800139EB3 /* NotificationPermissionAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationPermissionAction.swift; sourceTree = ""; }; + 314B5FAA23DCCE2800139EB3 /* PrivacyPermissionAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyPermissionAction.swift; sourceTree = ""; }; + 314B5FAC23DCCE2800139EB3 /* ParticlesPlatformAppInjection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticlesPlatformAppInjection.swift; sourceTree = ""; }; + 314B5FAD23DCCE2800139EB3 /* ParticlesPlatformExtensionInjection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticlesPlatformExtensionInjection.swift; sourceTree = ""; }; + 314B5FAE23DCCE2800139EB3 /* ParticlesPlatformInjection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticlesPlatformInjection.swift; sourceTree = ""; }; + 314B5FB123DCCE2800139EB3 /* ProgressPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressPresenter.swift; sourceTree = ""; }; + 314B5FB323DCCE2800139EB3 /* ListPresenter+iOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ListPresenter+iOS.swift"; sourceTree = ""; }; + 314B5FB523DCCE2800139EB3 /* CollectionViewXibRegister.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewXibRegister.swift; sourceTree = ""; }; + 314B5FB623DCCE2800139EB3 /* RigidCollectionViewListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RigidCollectionViewListPresenter.swift; sourceTree = ""; }; + 314B5FB723DCCE2800139EB3 /* CollectionViewListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewListPresenter.swift; sourceTree = ""; }; + 314B5FB823DCCE2800139EB3 /* CollectionViewSectionListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewSectionListPresenter.swift; sourceTree = ""; }; + 314B5FB923DCCE2800139EB3 /* XibListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibListPresenter.swift; sourceTree = ""; }; + 314B5FBB23DCCE2800139EB3 /* CarouselListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CarouselListPresenter.swift; sourceTree = ""; }; + 314B5FBD23DCCE2800139EB3 /* CalendarViewListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarViewListPresenter.swift; sourceTree = ""; }; + 314B5FBF23DCCE2800139EB3 /* ListLoadingPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLoadingPresenter.swift; sourceTree = ""; }; + 314B5FC023DCCE2800139EB3 /* BarButtonSwitchedListPresenterManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarButtonSwitchedListPresenterManager.swift; sourceTree = ""; }; + 314B5FC123DCCE2800139EB3 /* GridPresenterManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridPresenterManager.swift; sourceTree = ""; }; + 314B5FC223DCCE2800139EB3 /* ListPresenterManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListPresenterManager.swift; sourceTree = ""; }; + 314B5FC423DCCE2800139EB3 /* StackViewListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackViewListPresenter.swift; sourceTree = ""; }; + 314B5FC523DCCE2800139EB3 /* NativeListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeListPresenter.swift; sourceTree = ""; }; + 314B5FC723DCCE2800139EB3 /* TableViewSectionListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewSectionListPresenter.swift; sourceTree = ""; }; + 314B5FC823DCCE2800139EB3 /* TableViewListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewListPresenter.swift; sourceTree = ""; }; + 314B5FC923DCCE2800139EB3 /* LikedTableViewListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LikedTableViewListPresenter.swift; sourceTree = ""; }; + 314B5FCA23DCCE2800139EB3 /* TableViewXibRegister.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewXibRegister.swift; sourceTree = ""; }; + 314B5FCF23DCCE2900139EB3 /* AppInfoPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppInfoPresenter.swift; sourceTree = ""; }; + 314B677523DCEEEB00139EB3 /* XibPresenterProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibPresenterProtocol.swift; sourceTree = ""; }; + 314C35CB24C7C31400695F7E /* Version.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Version.xib; sourceTree = ""; }; + 314C35CD24C7C35000695F7E /* PhotoAlbumsPermissionAction.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PhotoAlbumsPermissionAction.xib; sourceTree = ""; }; + 314C35CE24C7C35000695F7E /* CameraPermissionAction.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CameraPermissionAction.xib; sourceTree = ""; }; + 314C35CF24C7C35000695F7E /* NotificationPermissionAction.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NotificationPermissionAction.xib; sourceTree = ""; }; + 314C35DB24C7C3A600695F7E /* TableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TableViewCell.xib; sourceTree = ""; }; + 314C35DC24C7C3A600695F7E /* CollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CollectionViewCell.xib; sourceTree = ""; }; + 314C35DD24C7C3A600695F7E /* text_collection_cell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = text_collection_cell.xib; sourceTree = ""; }; + 314C35DE24C7C3A600695F7E /* image_collection_cell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = image_collection_cell.xib; sourceTree = ""; }; + 314C35DF24C7C3A600695F7E /* ParallaxTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ParallaxTableViewCell.xib; sourceTree = ""; }; + 3165647826E2BDEC002D797E /* ListObjectViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListObjectViewPresenter.swift; sourceTree = ""; }; + 3175566F2628ED3400D76C4C /* CandleStickGraphingPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CandleStickGraphingPresenter.swift; sourceTree = ""; }; + 317E998723D80E0800C348FA /* ParticlesCommonModels.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParticlesCommonModels.xcodeproj; path = ../ParticlesCommonModels/ParticlesCommonModels.xcodeproj; sourceTree = ""; }; + 317F18B12572DA8A00D178B8 /* image_action_cell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = image_action_cell.xib; sourceTree = ""; }; + 317F18BF2572DA8A00D178B8 /* text_table_cell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = text_table_cell.xib; sourceTree = ""; }; + 3180B04E272B509400CCAB67 /* DismissAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DismissAction.swift; sourceTree = ""; }; + 3180B05F272C770E00CCAB67 /* ParticlesChartDataEntryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesChartDataEntryProtocol.swift; sourceTree = ""; }; + 318A5B9A272CA107000DA46C /* ParticlesPieChartDataEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticlesPieChartDataEntry.swift; sourceTree = ""; }; + 3197614926850649008EB757 /* ObjectWebLinePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectWebLinePresenter.swift; sourceTree = ""; }; + 31A53E882713693F0097BFE4 /* BarLineChartViewBase+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BarLineChartViewBase+Scaling.swift"; sourceTree = ""; }; + 31AA4B032704BF1300451307 /* GraphingAxisFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphingAxisFormatter.swift; sourceTree = ""; }; + 31AA4B2B2704D37900451307 /* CandleStickGraphingRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CandleStickGraphingRenderer.swift; sourceTree = ""; }; + 31ACB72921B0EE2D00391ADF /* PlatformParticles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlatformParticles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31ACB72C21B0EE2D00391ADF /* PlatformParticles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformParticles.h; sourceTree = ""; }; + 31ACB72D21B0EE2D00391ADF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31ACB73221B0EE2D00391ADF /* PlatformParticlesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PlatformParticlesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 31ACB73721B0EE2D00391ADF /* PlatformParticlesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformParticlesTests.swift; sourceTree = ""; }; + 31ACB73921B0EE2D00391ADF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31ACB78821B0EFF300391ADF /* RoutingKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RoutingKit.xcodeproj; path = ../RoutingKit/RoutingKit.xcodeproj; sourceTree = ""; }; + 31ACB79121B0EFF300391ADF /* ParticlesKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParticlesKit.xcodeproj; path = ../ParticlesKit/ParticlesKit.xcodeproj; sourceTree = ""; }; + 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = UIToolkits.xcodeproj; path = ../UIToolkits/UIToolkits.xcodeproj; sourceTree = ""; }; + 31ACB7A321B0EFF300391ADF /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 31ACB7C521B0F0F800391ADF /* PlatformRouting.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PlatformRouting.xcodeproj; path = ../PlatformRouting/PlatformRouting.xcodeproj; sourceTree = ""; }; + 31B060FF26A8D1B500431CFA /* GraphingPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphingPresenter.swift; sourceTree = ""; }; + 31C7E67A2551CE21000643B0 /* ListInteractor.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ListInteractor.xib; sourceTree = ""; }; + 31CC977926CB14EF00E56B83 /* CombinedGraphingPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedGraphingPresenter.swift; sourceTree = ""; }; + 31CFF7C121D7D1F200DE7A79 /* MessageParticles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MessageParticles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31CFF7C321D7D1F200DE7A79 /* MessageParticles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MessageParticles.h; sourceTree = ""; }; + 31CFF7C421D7D1F200DE7A79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31CFF7C921D7D1F300DE7A79 /* MessageParticlesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MessageParticlesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 31CFF7CE21D7D1F300DE7A79 /* MessageParticlesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageParticlesTests.swift; sourceTree = ""; }; + 31CFF7D021D7D1F300DE7A79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31E1B2FD26B0738200A4E3FC /* PieGraphingPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieGraphingPresenter.swift; sourceTree = ""; }; + 31E822012555C38A001FE2F1 /* ThinlinePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThinlinePresenter.swift; sourceTree = ""; }; + 31E8221E2555C3FD001FE2F1 /* Thinline.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Thinline.xib; sourceTree = ""; }; + 31E823D725566598001FE2F1 /* PermissionPrimerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionPrimerViewController.swift; sourceTree = ""; }; + 31EAFE2F25C63A8800412F11 /* NavigationObjectPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationObjectPresenter.swift; sourceTree = ""; }; + 31ECFF57253C986E00D6336F /* TableViewListPresenter+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TableViewListPresenter+Actions.swift"; sourceTree = ""; }; + 31ED731F27345B3C0032B501 /* ParticlesChartDataSetProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesChartDataSetProtocol.swift; sourceTree = ""; }; + 31ED7321273460350032B501 /* ParticlesLineChartDataSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesLineChartDataSet.swift; sourceTree = ""; }; + 31ED7323273460D40032B501 /* ParticlesBarChartDataSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesBarChartDataSet.swift; sourceTree = ""; }; + 31ED7325273460F20032B501 /* ParticlesPieChartDataSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesPieChartDataSet.swift; sourceTree = ""; }; + 31ED7327273461130032B501 /* ParticlesCandleChartDataSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesCandleChartDataSet.swift; sourceTree = ""; }; + 643494A927B810E000A0CC09 /* TimeIntervalObjectViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeIntervalObjectViewPresenter.swift; sourceTree = ""; }; + 648F46C627D8090800FD2FC6 /* XibTableViewHeaderFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XibTableViewHeaderFooterView.swift; sourceTree = ""; }; + 648F46D527D80B4400FD2FC6 /* TableViewHeaderXibRegister.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewHeaderXibRegister.swift; sourceTree = ""; }; + 64AF47822817123C00EBFDC6 /* SafariAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariAction.swift; sourceTree = ""; }; + 68ED3D120714A6AAFC95CDFC /* Pods_iOS_PlatformParticles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_PlatformParticles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8E50A1767A8443DAE7A5E486 /* Pods-iOS-PlatformParticles.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformParticles.release.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformParticles/Pods-iOS-PlatformParticles.release.xcconfig"; sourceTree = ""; }; + B09049A22701CF86FA5E0853 /* Pods-iOS-PlatformParticlesTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformParticlesTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformParticlesTests/Pods-iOS-PlatformParticlesTests.release.xcconfig"; sourceTree = ""; }; + C0294673341C6FE9011A57B7 /* Pods-iOS-PlatformParticlesTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformParticlesTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformParticlesTests/Pods-iOS-PlatformParticlesTests.debug.xcconfig"; sourceTree = ""; }; + DE57233C9BB53C21FDEFE974 /* Pods_iOS_PlatformParticlesTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_PlatformParticlesTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F4B59F8B8C5836B5E6EDB8F8 /* Pods-iOS-PlatformParticles.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformParticles.debug.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformParticles/Pods-iOS-PlatformParticles.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 313EB7C321BB592E00BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 317E99AA23D80E5300C348FA /* ParticlesCommonModels.framework in Frameworks */, + 313EB86F21BB73FE00BEF926 /* UIToolkits.framework in Frameworks */, + 313EB7D621BB598300BEF926 /* Utilities.framework in Frameworks */, + 313EB7D721BB598300BEF926 /* ParticlesKit.framework in Frameworks */, + 313EB7D821BB598300BEF926 /* RoutingKit.framework in Frameworks */, + 313EB7D921BB598300BEF926 /* PlatformRouting.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB4021BB7A4B00BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 317E99AD23D80E5B00C348FA /* ParticlesCommonModels.framework in Frameworks */, + 313EBCF821BB7D7400BEF926 /* Utilities.framework in Frameworks */, + 313EBCF921BB7D7400BEF926 /* ParticlesKit.framework in Frameworks */, + 313EBCFA21BB7D7400BEF926 /* RoutingKit.framework in Frameworks */, + 313EBCFB21BB7D7400BEF926 /* PlatformRouting.framework in Frameworks */, + 313EBCFC21BB7D7400BEF926 /* UIToolkits.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB4821BB7A4B00BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBB4C21BB7A4B00BEF926 /* PlatformParticles.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB72621B0EE2D00391ADF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 310C3B2D21D9C9C70081E56D /* UIAppToolkits.framework in Frameworks */, + 0271236928E545A50015D39F /* SwiftMessages in Frameworks */, + 31ACB7D021B0F10A00391ADF /* PlatformRouting.framework in Frameworks */, + 31ACB7C121B0F07A00391ADF /* Utilities.framework in Frameworks */, + 31ACB7C221B0F07A00391ADF /* ParticlesKit.framework in Frameworks */, + 31ACB7C321B0F07A00391ADF /* RoutingKit.framework in Frameworks */, + 31ACB7C421B0F07A00391ADF /* UIToolkits.framework in Frameworks */, + 317E99A723D80E4900C348FA /* ParticlesCommonModels.framework in Frameworks */, + F1601E34A925CDC119296640 /* Pods_iOS_PlatformParticles.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB72F21B0EE2D00391ADF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31ACB73321B0EE2D00391ADF /* PlatformParticles.framework in Frameworks */, + 6A4D707CD262BA071D79871D /* Pods_iOS_PlatformParticlesTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CFF7BE21D7D1F200DE7A79 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31CFF7DC21D7D23C00DE7A79 /* PlatformParticles.framework in Frameworks */, + 31CFF7DD21D7D23C00DE7A79 /* ParticlesKit.framework in Frameworks */, + 31CFF7DE21D7D23C00DE7A79 /* RoutingKit.framework in Frameworks */, + 31CFF7DF21D7D23C00DE7A79 /* UIToolkits.framework in Frameworks */, + 31CFF7E021D7D23C00DE7A79 /* Utilities.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CFF7C621D7D1F300DE7A79 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31CFF7CA21D7D1F300DE7A79 /* MessageParticles.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0271237728E54B480015D39F /* Dependencies */ = { + isa = PBXGroup; + children = ( + 317E998723D80E0800C348FA /* ParticlesCommonModels.xcodeproj */, + 31ACB79121B0EFF300391ADF /* ParticlesKit.xcodeproj */, + 31ACB7C521B0F0F800391ADF /* PlatformRouting.xcodeproj */, + 31ACB78821B0EFF300391ADF /* RoutingKit.xcodeproj */, + 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */, + 31ACB7A321B0EFF300391ADF /* Utilities.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 3101F9C82511306A00AC4010 /* _Auth */ = { + isa = PBXGroup; + children = ( + 3101F9C92511308E00AC4010 /* AuthLoginPresenter.swift */, + 3101F9CC251130D100AC4010 /* AuthLoginPrimerViewController.swift */, + ); + path = _Auth; + sourceTree = ""; + }; + 3101F9CF251130F000AC4010 /* _Storyboards */ = { + isa = PBXGroup; + children = ( + 3101F9D02511311600AC4010 /* AuthLoginPrimer.storyboard */, + ); + path = _Storyboards; + sourceTree = ""; + }; + 31347F5F2710FDDD0095829A /* _DataEntry */ = { + isa = PBXGroup; + children = ( + 3180B05F272C770E00CCAB67 /* ParticlesChartDataEntryProtocol.swift */, + 31347F7827110DAE0095829A /* ParticlesBarChartDataEntry.swift */, + 31347F6D2710FDF40095829A /* ParticlesCandleChartDataEntry.swift */, + 31347F7727110DAD0095829A /* ParticlesLineChartDataEntry.swift */, + 318A5B9A272CA107000DA46C /* ParticlesPieChartDataEntry.swift */, + ); + path = _DataEntry; + sourceTree = ""; + }; + 313EB7C721BB592E00BEF926 /* PlatformParticlesAppleWatch */ = { + isa = PBXGroup; + children = ( + 313EBE0121BC3FF000BEF926 /* ExtensionDelegate */, + 313EB7DC21BB5A6F00BEF926 /* InterfaceController */, + 313EB7DA21BB5A5C00BEF926 /* List Presenter */, + 313EB7DB21BB5A6400BEF926 /* Object Presenter */, + 313EB7C821BB592E00BEF926 /* PlatformParticles.h */, + 313EB7C921BB592E00BEF926 /* Info.plist */, + ); + path = PlatformParticlesAppleWatch; + sourceTree = ""; + }; + 313EB7DA21BB5A5C00BEF926 /* List Presenter */ = { + isa = PBXGroup; + children = ( + 313EBF3521BE0BF500BEF926 /* InterfaceController */, + 313EB87021BB746B00BEF926 /* Table */, + 313EB86821BB73B400BEF926 /* XibListPresenter.swift */, + ); + path = "List Presenter"; + sourceTree = ""; + }; + 313EB7DB21BB5A6400BEF926 /* Object Presenter */ = { + isa = PBXGroup; + children = ( + ); + path = "Object Presenter"; + sourceTree = ""; + }; + 313EB7DC21BB5A6F00BEF926 /* InterfaceController */ = { + isa = PBXGroup; + children = ( + ); + path = InterfaceController; + sourceTree = ""; + }; + 313EB87021BB746B00BEF926 /* Table */ = { + isa = PBXGroup; + children = ( + 313EB7DE21BB5AB800BEF926 /* TableViewListPresenter.swift */, + ); + path = Table; + sourceTree = ""; + }; + 313EBB4421BB7A4B00BEF926 /* PlatformParticlesAppleTV */ = { + isa = PBXGroup; + children = ( + 313EBB4521BB7A4B00BEF926 /* PlatformParticles.h */, + 313EBB4621BB7A4B00BEF926 /* Info.plist */, + ); + path = PlatformParticlesAppleTV; + sourceTree = ""; + }; + 313EBB4F21BB7A4B00BEF926 /* PlatformParticlesAppleTVTests */ = { + isa = PBXGroup; + children = ( + 313EBB5021BB7A4B00BEF926 /* PlatformParticlesAppleTVTests.swift */, + 313EBB5221BB7A4B00BEF926 /* Info.plist */, + ); + path = PlatformParticlesAppleTVTests; + sourceTree = ""; + }; + 313EBE0121BC3FF000BEF926 /* ExtensionDelegate */ = { + isa = PBXGroup; + children = ( + 313EBE0221BC400300BEF926 /* DefaultExtensionDelegate.swift */, + ); + path = ExtensionDelegate; + sourceTree = ""; + }; + 313EBF3521BE0BF500BEF926 /* InterfaceController */ = { + isa = PBXGroup; + children = ( + 313EBF3621BE0C7B00BEF926 /* ListPresenterInterfaceController.swift */, + ); + path = InterfaceController; + sourceTree = ""; + }; + 31439A1C2672BAA6003871A1 /* _Line Presenter */ = { + isa = PBXGroup; + children = ( + 31439A832677F665003871A1 /* ObjectCompositeLinePresenter.swift */, + 31439A412672C3A3003871A1 /* ObjectEditLinePresenter.swift */, + 31439A2A2672BAD6003871A1 /* ObjectLinePresenter.swift */, + 31439A5B2673DE2B003871A1 /* ObjectOptionsLinePresenter.swift */, + 31439A432672C4E6003871A1 /* ObjectSwitchLinePresenter.swift */, + 31439A3F2672C29E003871A1 /* ObjectTextLinePresenter.swift */, + 31439A2C2672BC26003871A1 /* ObjectValueLinePresenter.swift */, + 3197614926850649008EB757 /* ObjectWebLinePresenter.swift */, + ); + path = "_Line Presenter"; + sourceTree = ""; + }; + 3145CE0525F4329F00BCCFCA /* _Graphing */ = { + isa = PBXGroup; + children = ( + 31347F5F2710FDDD0095829A /* _DataEntry */, + 31ED731127345B1D0032B501 /* _DataSet */, + 31A53E7A271369200097BFE4 /* _Extensions */, + 31AA4B022704BEFD00451307 /* _Formatter */, + 31AA4B1D2704D36100451307 /* _Renderer */, + 3175566F2628ED3400D76C4C /* CandleStickGraphingPresenter.swift */, + 31CC977926CB14EF00E56B83 /* CombinedGraphingPresenter.swift */, + 3145CE0625F432BE00BCCFCA /* GraphingListPresenter.swift */, + 31B060FF26A8D1B500431CFA /* GraphingPresenter.swift */, + 3145CE1625F437A700BCCFCA /* LineGraphingPresenter.swift */, + 31E1B2FD26B0738200A4E3FC /* PieGraphingPresenter.swift */, + ); + path = _Graphing; + sourceTree = ""; + }; + 314B5F6D23DCCE2700139EB3 /* _Permissions */ = { + isa = PBXGroup; + children = ( + 314B5F6E23DCCE2700139EB3 /* PrivacyPermissionPresenter.swift */, + ); + path = _Permissions; + sourceTree = ""; + }; + 314B5F6F23DCCE2700139EB3 /* _Background */ = { + isa = PBXGroup; + children = ( + 314B5F7023DCCE2700139EB3 /* BackgroundTasksPoolInteractor.swift */, + ); + path = _Background; + sourceTree = ""; + }; + 314B5F7123DCCE2700139EB3 /* _Object Presenter */ = { + isa = PBXGroup; + children = ( + 314B5F7E23DCCE2700139EB3 /* ActionPresenter.swift */, + 31308B8124FAEF86003B5B9A /* ColoredClusterPresenter.swift */, + 3111C2422543AD2E00F204D9 /* CompositeObjectPresenter.swift */, + 314B5F7C23DCCE2700139EB3 /* LikedSmartObjectPresenter.swift */, + 314B5F7323DCCE2700139EB3 /* ListInteractorPresenterView.swift */, + 3165647826E2BDEC002D797E /* ListObjectViewPresenter.swift */, + 314B5F7A23DCCE2700139EB3 /* ObjectLikeBarItemPresenter.swift */, + 314B5F7923DCCE2700139EB3 /* ObjectPresenterCollectionViewCell.swift */, + 314B5F7423DCCE2700139EB3 /* ObjectPresenterContainerView.swift */, + 314B5F7F23DCCE2700139EB3 /* ObjectPresenterTableViewCell.swift */, + 314B5F7523DCCE2700139EB3 /* ObjectPresenterView.swift */, + 310D88FF262DF3EC00FEF56A /* ObjectViewPresenter.swift */, + 314B5F7623DCCE2700139EB3 /* ParallaxObjectPresenterTableViewCell.swift */, + 314B5F7D23DCCE2700139EB3 /* SmartObjectPresenter.swift */, + 31E822012555C38A001FE2F1 /* ThinlinePresenter.swift */, + 314B5F7223DCCE2700139EB3 /* UIView+Binding.swift */, + 314B5F7823DCCE2700139EB3 /* UIView+Xib.swift */, + 314B5F7B23DCCE2700139EB3 /* XibActionPresenter.swift */, + 643494A927B810E000A0CC09 /* TimeIntervalObjectViewPresenter.swift */, + ); + path = "_Object Presenter"; + sourceTree = ""; + }; + 314B5F8023DCCE2700139EB3 /* _Tracking */ = { + isa = PBXGroup; + children = ( + 314B5F8123DCCE2700139EB3 /* TransformerTracker.swift */, + ); + path = _Tracking; + sourceTree = ""; + }; + 314B5F8223DCCE2700139EB3 /* _Attribution */ = { + isa = PBXGroup; + children = ( + 314B5F8323DCCE2700139EB3 /* Attribution.swift */, + ); + path = _Attribution; + sourceTree = ""; + }; + 314B5F8423DCCE2700139EB3 /* _Cache */ = { + isa = PBXGroup; + children = ( + 314B5F8623DCCE2700139EB3 /* JsonAppGroupFileCaching.swift */, + 314B5F8523DCCE2700139EB3 /* JsonDocument.swift */, + ); + path = _Cache; + sourceTree = ""; + }; + 314B5F8A23DCCE2700139EB3 /* _Grid Presenter */ = { + isa = PBXGroup; + children = ( + 314B5F8D23DCCE2700139EB3 /* _Collection View */, + 314B5F8C23DCCE2700139EB3 /* GridPresenter+iOS.swift */, + 314B5F8F23DCCE2700139EB3 /* NativeGridPresenter.swift */, + 314B5F8B23DCCE2700139EB3 /* XibGridPresenter.swift */, + ); + path = "_Grid Presenter"; + sourceTree = ""; + }; + 314B5F8D23DCCE2700139EB3 /* _Collection View */ = { + isa = PBXGroup; + children = ( + 314B5F8E23DCCE2700139EB3 /* CollectionViewGridPresenter.swift */, + ); + path = "_Collection View"; + sourceTree = ""; + }; + 314B5F9023DCCE2800139EB3 /* _AppDelegate */ = { + isa = PBXGroup; + children = ( + 314B5F9123DCCE2800139EB3 /* ParticlesAppDelegate.swift */, + ); + path = _AppDelegate; + sourceTree = ""; + }; + 314B5F9223DCCE2800139EB3 /* _Error */ = { + isa = PBXGroup; + children = ( + 314B5F9423DCCE2800139EB3 /* ErrorAlert.swift */, + 27ED35392AD5BD0900C159F5 /* BannerErrorAlert.swift */, + ); + path = _Error; + sourceTree = ""; + }; + 314B5F9523DCCE2800139EB3 /* _ViewController */ = { + isa = PBXGroup; + children = ( + 314B5F9923DCCE2800139EB3 /* DataPresenterViewController.swift */, + 314B5F9623DCCE2800139EB3 /* GridPresenterViewController.swift */, + 314B5F9A23DCCE2800139EB3 /* ListPresenterViewController.swift */, + 31E823D725566598001FE2F1 /* PermissionPrimerViewController.swift */, + 314B5F9C23DCCE2800139EB3 /* PrivacyPermissionViewController.swift */, + 314B5F9723DCCE2800139EB3 /* SearchViewController.swift */, + 314B5F9B23DCCE2800139EB3 /* TrackingViewController.swift */, + 314B5F9823DCCE2800139EB3 /* UpgradeViewController.swift */, + ); + path = _ViewController; + sourceTree = ""; + }; + 314B5F9F23DCCE2800139EB3 /* _Messages */ = { + isa = PBXGroup; + children = ( + 314B5FA023DCCE2800139EB3 /* MessageAction.swift */, + ); + path = _Messages; + sourceTree = ""; + }; + 314B5FA123DCCE2800139EB3 /* _Actions */ = { + isa = PBXGroup; + children = ( + 314B5FA323DCCE2800139EB3 /* _Permissions */, + 314B5FA223DCCE2800139EB3 /* ConfirmationAction.swift */, + 3101F9C42511303E00AC4010 /* ConfirmAction.swift */, + 3180B04E272B509400CCAB67 /* DismissAction.swift */, + 64AF47822817123C00EBFDC6 /* SafariAction.swift */, + ); + path = _Actions; + sourceTree = ""; + }; + 314B5FA323DCCE2800139EB3 /* _Permissions */ = { + isa = PBXGroup; + children = ( + 314B5FA823DCCE2800139EB3 /* CalendarPermissionAction.swift */, + 314B5FA523DCCE2800139EB3 /* CameraPermissionAction.swift */, + 314B5FA923DCCE2800139EB3 /* NotificationPermissionAction.swift */, + 314B5FA723DCCE2800139EB3 /* PhotoAlbumsPermissionAction.swift */, + 314B5FAA23DCCE2800139EB3 /* PrivacyPermissionAction.swift */, + ); + path = _Permissions; + sourceTree = ""; + }; + 314B5FAB23DCCE2800139EB3 /* _Shared */ = { + isa = PBXGroup; + children = ( + 314B5FAC23DCCE2800139EB3 /* ParticlesPlatformAppInjection.swift */, + 314B5FAD23DCCE2800139EB3 /* ParticlesPlatformExtensionInjection.swift */, + 314B5FAE23DCCE2800139EB3 /* ParticlesPlatformInjection.swift */, + ); + path = _Shared; + sourceTree = ""; + }; + 314B5FB023DCCE2800139EB3 /* _Progress */ = { + isa = PBXGroup; + children = ( + 314B5FB123DCCE2800139EB3 /* ProgressPresenter.swift */, + ); + path = _Progress; + sourceTree = ""; + }; + 314B5FB223DCCE2800139EB3 /* _List Presenter */ = { + isa = PBXGroup; + children = ( + 3145CE0525F4329F00BCCFCA /* _Graphing */, + 314B5FBC23DCCE2800139EB3 /* _Calendar */, + 314B5FBA23DCCE2800139EB3 /* _Carousel */, + 314B5FB423DCCE2800139EB3 /* _Collection */, + 314B5FBE23DCCE2800139EB3 /* _Manager */, + 314B5FC323DCCE2800139EB3 /* _Stack */, + 314B5FC623DCCE2800139EB3 /* _Table */, + 314B5FB323DCCE2800139EB3 /* ListPresenter+iOS.swift */, + 314B5FC523DCCE2800139EB3 /* NativeListPresenter.swift */, + 314B5FB923DCCE2800139EB3 /* XibListPresenter.swift */, + ); + path = "_List Presenter"; + sourceTree = ""; + }; + 314B5FB423DCCE2800139EB3 /* _Collection */ = { + isa = PBXGroup; + children = ( + 314B5FB723DCCE2800139EB3 /* CollectionViewListPresenter.swift */, + 314B5FB823DCCE2800139EB3 /* CollectionViewSectionListPresenter.swift */, + 314B5FB523DCCE2800139EB3 /* CollectionViewXibRegister.swift */, + 314B5FB623DCCE2800139EB3 /* RigidCollectionViewListPresenter.swift */, + ); + path = _Collection; + sourceTree = ""; + }; + 314B5FBA23DCCE2800139EB3 /* _Carousel */ = { + isa = PBXGroup; + children = ( + 314B5FBB23DCCE2800139EB3 /* CarouselListPresenter.swift */, + ); + path = _Carousel; + sourceTree = ""; + }; + 314B5FBC23DCCE2800139EB3 /* _Calendar */ = { + isa = PBXGroup; + children = ( + 314B5FBD23DCCE2800139EB3 /* CalendarViewListPresenter.swift */, + ); + path = _Calendar; + sourceTree = ""; + }; + 314B5FBE23DCCE2800139EB3 /* _Manager */ = { + isa = PBXGroup; + children = ( + 314B5FC023DCCE2800139EB3 /* BarButtonSwitchedListPresenterManager.swift */, + 314B5FC123DCCE2800139EB3 /* GridPresenterManager.swift */, + 314B5FBF23DCCE2800139EB3 /* ListLoadingPresenter.swift */, + 314B5FC223DCCE2800139EB3 /* ListPresenterManager.swift */, + ); + path = _Manager; + sourceTree = ""; + }; + 314B5FC323DCCE2800139EB3 /* _Stack */ = { + isa = PBXGroup; + children = ( + 314B5FC423DCCE2800139EB3 /* StackViewListPresenter.swift */, + ); + path = _Stack; + sourceTree = ""; + }; + 314B5FC623DCCE2800139EB3 /* _Table */ = { + isa = PBXGroup; + children = ( + 314B5FC923DCCE2800139EB3 /* LikedTableViewListPresenter.swift */, + 314B5FC823DCCE2800139EB3 /* TableViewListPresenter.swift */, + 314B5FC723DCCE2800139EB3 /* TableViewSectionListPresenter.swift */, + 314B5FCA23DCCE2800139EB3 /* TableViewXibRegister.swift */, + 31ECFF57253C986E00D6336F /* TableViewListPresenter+Actions.swift */, + 648F46C627D8090800FD2FC6 /* XibTableViewHeaderFooterView.swift */, + 648F46D527D80B4400FD2FC6 /* TableViewHeaderXibRegister.swift */, + ); + path = _Table; + sourceTree = ""; + }; + 314B5FCD23DCCE2900139EB3 /* _Common */ = { + isa = PBXGroup; + children = ( + 314B5FCF23DCCE2900139EB3 /* AppInfoPresenter.swift */, + ); + path = _Common; + sourceTree = ""; + }; + 314B677323DCEEEB00139EB3 /* _Xib */ = { + isa = PBXGroup; + children = ( + 314B677523DCEEEB00139EB3 /* XibPresenterProtocol.swift */, + ); + path = _Xib; + sourceTree = ""; + }; + 314C35B924C7C2C600695F7E /* Resources */ = { + isa = PBXGroup; + children = ( + 314C35C724C7C2D600695F7E /* _iOS */, + ); + path = Resources; + sourceTree = ""; + }; + 314C35C724C7C2D600695F7E /* _iOS */ = { + isa = PBXGroup; + children = ( + 314C35DA24C7C38100695F7E /* _Cells */, + 314C35C824C7C2DC00695F7E /* _Routing */, + 314C35CA24C7C2EA00695F7E /* _Xib */, + 3101F9D42511317800AC4010 /* Media.xcassets */, + ); + path = _iOS; + sourceTree = ""; + }; + 314C35C824C7C2DC00695F7E /* _Routing */ = { + isa = PBXGroup; + children = ( + 3101F9CF251130F000AC4010 /* _Storyboards */, + 314C35C924C7C2E400695F7E /* _Xib */, + ); + path = _Routing; + sourceTree = ""; + }; + 314C35C924C7C2E400695F7E /* _Xib */ = { + isa = PBXGroup; + children = ( + 314C35CE24C7C35000695F7E /* CameraPermissionAction.xib */, + 3101F9D22511314200AC4010 /* ConfirmAction.xib */, + 31308B8324FAF01E003B5B9A /* LocationPermissionAction.xib */, + 314C35CF24C7C35000695F7E /* NotificationPermissionAction.xib */, + 314C35CD24C7C35000695F7E /* PhotoAlbumsPermissionAction.xib */, + ); + path = _Xib; + sourceTree = ""; + }; + 314C35CA24C7C2EA00695F7E /* _Xib */ = { + isa = PBXGroup; + children = ( + 31C7E67A2551CE21000643B0 /* ListInteractor.xib */, + 314C35CB24C7C31400695F7E /* Version.xib */, + 31E8221E2555C3FD001FE2F1 /* Thinline.xib */, + ); + path = _Xib; + sourceTree = ""; + }; + 314C35DA24C7C38100695F7E /* _Cells */ = { + isa = PBXGroup; + children = ( + 314C35DC24C7C3A600695F7E /* CollectionViewCell.xib */, + 317F18B12572DA8A00D178B8 /* image_action_cell.xib */, + 314C35DE24C7C3A600695F7E /* image_collection_cell.xib */, + 314C35DF24C7C3A600695F7E /* ParallaxTableViewCell.xib */, + 314C35DB24C7C3A600695F7E /* TableViewCell.xib */, + 314C35DD24C7C3A600695F7E /* text_collection_cell.xib */, + 317F18BF2572DA8A00D178B8 /* text_table_cell.xib */, + ); + path = _Cells; + sourceTree = ""; + }; + 317E998823D80E0800C348FA /* Products */ = { + isa = PBXGroup; + children = ( + 317E999223D80E0800C348FA /* ParticlesCommonModels.framework */, + 317E999423D80E0800C348FA /* ParticlesCommonModels.framework */, + 317E999623D80E0800C348FA /* ParticlesCommonModels.framework */, + 317E999A23D80E0800C348FA /* ParticlesCommonModelsTests.xctest */, + 317E999C23D80E0800C348FA /* ParticlesCommonModelsAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31A53E7A271369200097BFE4 /* _Extensions */ = { + isa = PBXGroup; + children = ( + 31A53E882713693F0097BFE4 /* BarLineChartViewBase+Scaling.swift */, + ); + path = _Extensions; + sourceTree = ""; + }; + 31AA4B022704BEFD00451307 /* _Formatter */ = { + isa = PBXGroup; + children = ( + 31AA4B032704BF1300451307 /* GraphingAxisFormatter.swift */, + 314614422703B6B60000DDFF /* TimeAxisFormatter.swift */, + ); + path = _Formatter; + sourceTree = ""; + }; + 31AA4B1D2704D36100451307 /* _Renderer */ = { + isa = PBXGroup; + children = ( + 31AA4B2B2704D37900451307 /* CandleStickGraphingRenderer.swift */, + ); + path = _Renderer; + sourceTree = ""; + }; + 31ACB71F21B0EE2D00391ADF = { + isa = PBXGroup; + children = ( + 0271237728E54B480015D39F /* Dependencies */, + 31CFF7C221D7D1F200DE7A79 /* MessageParticles */, + 31CFF7CD21D7D1F300DE7A79 /* MessageParticlesTests */, + 31ACB72B21B0EE2D00391ADF /* PlatformParticles */, + 313EBB4421BB7A4B00BEF926 /* PlatformParticlesAppleTV */, + 313EBB4F21BB7A4B00BEF926 /* PlatformParticlesAppleTVTests */, + 313EB7C721BB592E00BEF926 /* PlatformParticlesAppleWatch */, + 31ACB73621B0EE2D00391ADF /* PlatformParticlesTests */, + 31ACB72A21B0EE2D00391ADF /* Products */, + 95C8B25F7386462C9451F16A /* Pods */, + 6BA338B4936E58322CB1F5DE /* Frameworks */, + ); + sourceTree = ""; + }; + 31ACB72A21B0EE2D00391ADF /* Products */ = { + isa = PBXGroup; + children = ( + 31ACB72921B0EE2D00391ADF /* PlatformParticles.framework */, + 31ACB73221B0EE2D00391ADF /* PlatformParticlesTests.xctest */, + 313EB7C621BB592E00BEF926 /* PlatformParticles.framework */, + 313EBB4321BB7A4B00BEF926 /* PlatformParticles.framework */, + 313EBB4B21BB7A4B00BEF926 /* PlatformParticlesAppleTVTests.xctest */, + 31CFF7C121D7D1F200DE7A79 /* MessageParticles.framework */, + 31CFF7C921D7D1F300DE7A79 /* MessageParticlesTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31ACB72B21B0EE2D00391ADF /* PlatformParticles */ = { + isa = PBXGroup; + children = ( + 314B5FA123DCCE2800139EB3 /* _Actions */, + 314B5F9023DCCE2800139EB3 /* _AppDelegate */, + 314B5F8223DCCE2700139EB3 /* _Attribution */, + 3101F9C82511306A00AC4010 /* _Auth */, + 314B5F6F23DCCE2700139EB3 /* _Background */, + 314B5F8423DCCE2700139EB3 /* _Cache */, + 314B5FCD23DCCE2900139EB3 /* _Common */, + 314B5F9223DCCE2800139EB3 /* _Error */, + 314B5F8A23DCCE2700139EB3 /* _Grid Presenter */, + 31439A1C2672BAA6003871A1 /* _Line Presenter */, + 314B5FB223DCCE2800139EB3 /* _List Presenter */, + 314B5F9F23DCCE2800139EB3 /* _Messages */, + 31EAFE2125C63A6700412F11 /* _Navigation */, + 314B5F7123DCCE2700139EB3 /* _Object Presenter */, + 314B5F6D23DCCE2700139EB3 /* _Permissions */, + 314B5FB023DCCE2800139EB3 /* _Progress */, + 314B5FAB23DCCE2800139EB3 /* _Shared */, + 314B5F8023DCCE2700139EB3 /* _Tracking */, + 314B5F9523DCCE2800139EB3 /* _ViewController */, + 314B677323DCEEEB00139EB3 /* _Xib */, + 31ACB72D21B0EE2D00391ADF /* Info.plist */, + 31ACB72C21B0EE2D00391ADF /* PlatformParticles.h */, + 314C35B924C7C2C600695F7E /* Resources */, + ); + path = PlatformParticles; + sourceTree = ""; + }; + 31ACB73621B0EE2D00391ADF /* PlatformParticlesTests */ = { + isa = PBXGroup; + children = ( + 31ACB73721B0EE2D00391ADF /* PlatformParticlesTests.swift */, + 31ACB73921B0EE2D00391ADF /* Info.plist */, + ); + path = PlatformParticlesTests; + sourceTree = ""; + }; + 31ACB78921B0EFF300391ADF /* Products */ = { + isa = PBXGroup; + children = ( + 31ACB78E21B0EFF300391ADF /* RoutingKit.framework */, + 313EB2AF21BA299300BEF926 /* RoutingKit.framework */, + 313EB2B121BA299300BEF926 /* RoutingKit.framework */, + 31ACB79021B0EFF300391ADF /* RoutingKitTests.xctest */, + 313EB2B521BA299300BEF926 /* RoutingKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31ACB79221B0EFF300391ADF /* Products */ = { + isa = PBXGroup; + children = ( + 31ACB79721B0EFF300391ADF /* ParticlesKit.framework */, + 313EB29E21BA299300BEF926 /* ParticlesKit.framework */, + 313EB2A021BA299300BEF926 /* ParticlesKit.framework */, + 31ACB79921B0EFF300391ADF /* ParticlesKitTests.xctest */, + 313EB2A421BA299300BEF926 /* ParticlesKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31ACB79B21B0EFF300391ADF /* Products */ = { + isa = PBXGroup; + children = ( + 31ACB7A021B0EFF300391ADF /* UIToolkits.framework */, + 313EB86B21BB73B400BEF926 /* UIToolkits.framework */, + 313EBB5921BB7A4B00BEF926 /* UIToolkits.framework */, + 310C3B2721D9C9BF0081E56D /* UIAppToolkits.framework */, + 31ACB7A221B0EFF300391ADF /* UIToolkitsTests.xctest */, + 313EBB5D21BB7A4B00BEF926 /* UIToolkitsAppleTVTests.xctest */, + 310C3B2921D9C9BF0081E56D /* UIAppToolkitsTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31ACB7A421B0EFF300391ADF /* Products */ = { + isa = PBXGroup; + children = ( + 31ACB7A921B0EFF300391ADF /* Utilities.framework */, + 313EB28E21BA299300BEF926 /* Utilities.framework */, + 313EB29021BA299300BEF926 /* Utilities.framework */, + 31ACB7AB21B0EFF300391ADF /* UtilitiesTests.xctest */, + 313EB29421BA299300BEF926 /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31ACB7C621B0F0F800391ADF /* Products */ = { + isa = PBXGroup; + children = ( + 31ACB7CB21B0F0F800391ADF /* PlatformRouting.framework */, + 313EB7BF21BB591200BEF926 /* PlatformRouting.framework */, + 313EBCD221BB7D1100BEF926 /* PlatformRouting.framework */, + 31ACB7CD21B0F0F800391ADF /* PlatformRoutingTests.xctest */, + 313EBCD621BB7D1100BEF926 /* PlatformRoutingAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31CFF7C221D7D1F200DE7A79 /* MessageParticles */ = { + isa = PBXGroup; + children = ( + 31CFF7C321D7D1F200DE7A79 /* MessageParticles.h */, + 31CFF7C421D7D1F200DE7A79 /* Info.plist */, + ); + path = MessageParticles; + sourceTree = ""; + }; + 31CFF7CD21D7D1F300DE7A79 /* MessageParticlesTests */ = { + isa = PBXGroup; + children = ( + 31CFF7CE21D7D1F300DE7A79 /* MessageParticlesTests.swift */, + 31CFF7D021D7D1F300DE7A79 /* Info.plist */, + ); + path = MessageParticlesTests; + sourceTree = ""; + }; + 31EAFE2125C63A6700412F11 /* _Navigation */ = { + isa = PBXGroup; + children = ( + 31EAFE2F25C63A8800412F11 /* NavigationObjectPresenter.swift */, + ); + path = _Navigation; + sourceTree = ""; + }; + 31ED731127345B1D0032B501 /* _DataSet */ = { + isa = PBXGroup; + children = ( + 31ED731F27345B3C0032B501 /* ParticlesChartDataSetProtocol.swift */, + 31ED7323273460D40032B501 /* ParticlesBarChartDataSet.swift */, + 31ED7327273461130032B501 /* ParticlesCandleChartDataSet.swift */, + 31ED7321273460350032B501 /* ParticlesLineChartDataSet.swift */, + 31ED7325273460F20032B501 /* ParticlesPieChartDataSet.swift */, + ); + path = _DataSet; + sourceTree = ""; + }; + 6BA338B4936E58322CB1F5DE /* Frameworks */ = { + isa = PBXGroup; + children = ( + 68ED3D120714A6AAFC95CDFC /* Pods_iOS_PlatformParticles.framework */, + DE57233C9BB53C21FDEFE974 /* Pods_iOS_PlatformParticlesTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 95C8B25F7386462C9451F16A /* Pods */ = { + isa = PBXGroup; + children = ( + F4B59F8B8C5836B5E6EDB8F8 /* Pods-iOS-PlatformParticles.debug.xcconfig */, + 8E50A1767A8443DAE7A5E486 /* Pods-iOS-PlatformParticles.release.xcconfig */, + C0294673341C6FE9011A57B7 /* Pods-iOS-PlatformParticlesTests.debug.xcconfig */, + B09049A22701CF86FA5E0853 /* Pods-iOS-PlatformParticlesTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 313EB7C121BB592E00BEF926 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB7CA21BB592E00BEF926 /* PlatformParticles.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB3E21BB7A4B00BEF926 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBB5321BB7A4B00BEF926 /* PlatformParticles.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB72421B0EE2D00391ADF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 31ACB73A21B0EE2D00391ADF /* PlatformParticles.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CFF7BC21D7D1F200DE7A79 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 31CFF7D121D7D1F300DE7A79 /* MessageParticles.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 313EB7C521BB592E00BEF926 /* PlatformParticlesAppleWatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EB7CB21BB592E00BEF926 /* Build configuration list for PBXNativeTarget "PlatformParticlesAppleWatch" */; + buildPhases = ( + 313EB7C121BB592E00BEF926 /* Headers */, + 313EB7C221BB592E00BEF926 /* Sources */, + 313EB7C321BB592E00BEF926 /* Frameworks */, + 313EB7C421BB592E00BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 317E99A423D80E3100C348FA /* PBXTargetDependency */, + 313EB86E21BB73D300BEF926 /* PBXTargetDependency */, + 313EB7CF21BB596000BEF926 /* PBXTargetDependency */, + 313EB7D121BB596000BEF926 /* PBXTargetDependency */, + 313EB7D321BB596000BEF926 /* PBXTargetDependency */, + 313EB7D521BB596000BEF926 /* PBXTargetDependency */, + ); + name = PlatformParticlesAppleWatch; + productName = PlatformParticlesAppleWatch; + productReference = 313EB7C621BB592E00BEF926 /* PlatformParticles.framework */; + productType = "com.apple.product-type.framework"; + }; + 313EBB4221BB7A4B00BEF926 /* PlatformParticlesAppleTV */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EBB6021BB7A4B00BEF926 /* Build configuration list for PBXNativeTarget "PlatformParticlesAppleTV" */; + buildPhases = ( + 313EBB3E21BB7A4B00BEF926 /* Headers */, + 313EBB3F21BB7A4B00BEF926 /* Sources */, + 313EBB4021BB7A4B00BEF926 /* Frameworks */, + 313EBB4121BB7A4B00BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 317E99A223D80E2B00C348FA /* PBXTargetDependency */, + 313EBCDC21BB7D2C00BEF926 /* PBXTargetDependency */, + 313EBCDE21BB7D2C00BEF926 /* PBXTargetDependency */, + 313EBCE021BB7D2C00BEF926 /* PBXTargetDependency */, + 313EBCE221BB7D2C00BEF926 /* PBXTargetDependency */, + 313EBCE421BB7D2C00BEF926 /* PBXTargetDependency */, + ); + name = PlatformParticlesAppleTV; + productName = PlatformParticlesAppleTV; + productReference = 313EBB4321BB7A4B00BEF926 /* PlatformParticles.framework */; + productType = "com.apple.product-type.framework"; + }; + 313EBB4A21BB7A4B00BEF926 /* PlatformParticlesAppleTVTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EBB6321BB7A4B00BEF926 /* Build configuration list for PBXNativeTarget "PlatformParticlesAppleTVTests" */; + buildPhases = ( + 313EBB4721BB7A4B00BEF926 /* Sources */, + 313EBB4821BB7A4B00BEF926 /* Frameworks */, + 313EBB4921BB7A4B00BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EBB4E21BB7A4B00BEF926 /* PBXTargetDependency */, + ); + name = PlatformParticlesAppleTVTests; + productName = PlatformParticlesAppleTVTests; + productReference = 313EBB4B21BB7A4B00BEF926 /* PlatformParticlesAppleTVTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 31ACB72821B0EE2D00391ADF /* PlatformParticles */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31ACB73D21B0EE2D00391ADF /* Build configuration list for PBXNativeTarget "PlatformParticles" */; + buildPhases = ( + A01C7D16C3374E753C7B2950 /* [CP] Check Pods Manifest.lock */, + 31ACB72421B0EE2D00391ADF /* Headers */, + 31ACB72521B0EE2D00391ADF /* Sources */, + 31ACB72621B0EE2D00391ADF /* Frameworks */, + 31ACB72721B0EE2D00391ADF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 317E99A023D80E1B00C348FA /* PBXTargetDependency */, + 310C3B1721D9C9BF0081E56D /* PBXTargetDependency */, + 31ACB7CF21B0F0FF00391ADF /* PBXTargetDependency */, + 31ACB7B821B0F05E00391ADF /* PBXTargetDependency */, + 31ACB7BA21B0F05E00391ADF /* PBXTargetDependency */, + 31ACB7BC21B0F05E00391ADF /* PBXTargetDependency */, + 31ACB7BE21B0F05E00391ADF /* PBXTargetDependency */, + ); + name = PlatformParticles; + packageProductDependencies = ( + 0271236828E545A50015D39F /* SwiftMessages */, + ); + productName = PlatformParticles; + productReference = 31ACB72921B0EE2D00391ADF /* PlatformParticles.framework */; + productType = "com.apple.product-type.framework"; + }; + 31ACB73121B0EE2D00391ADF /* PlatformParticlesTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31ACB74021B0EE2D00391ADF /* Build configuration list for PBXNativeTarget "PlatformParticlesTests" */; + buildPhases = ( + 428C18730BA8C27E584F9DE6 /* [CP] Check Pods Manifest.lock */, + 31ACB72E21B0EE2D00391ADF /* Sources */, + 31ACB72F21B0EE2D00391ADF /* Frameworks */, + 31ACB73021B0EE2D00391ADF /* Resources */, + 03C20C522A64DB6829F3E42A /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 31ACB73521B0EE2D00391ADF /* PBXTargetDependency */, + ); + name = PlatformParticlesTests; + productName = PlatformParticlesTests; + productReference = 31ACB73221B0EE2D00391ADF /* PlatformParticlesTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 31CFF7C021D7D1F200DE7A79 /* MessageParticles */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31CFF7D221D7D1F300DE7A79 /* Build configuration list for PBXNativeTarget "MessageParticles" */; + buildPhases = ( + 31CFF7BC21D7D1F200DE7A79 /* Headers */, + 31CFF7BD21D7D1F200DE7A79 /* Sources */, + 31CFF7BE21D7D1F200DE7A79 /* Frameworks */, + 31CFF7BF21D7D1F200DE7A79 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 31CFF7DB21D7D21200DE7A79 /* PBXTargetDependency */, + ); + name = MessageParticles; + productName = MessageParticles; + productReference = 31CFF7C121D7D1F200DE7A79 /* MessageParticles.framework */; + productType = "com.apple.product-type.framework"; + }; + 31CFF7C821D7D1F300DE7A79 /* MessageParticlesTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31CFF7D521D7D1F300DE7A79 /* Build configuration list for PBXNativeTarget "MessageParticlesTests" */; + buildPhases = ( + 31CFF7C521D7D1F300DE7A79 /* Sources */, + 31CFF7C621D7D1F300DE7A79 /* Frameworks */, + 31CFF7C721D7D1F300DE7A79 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 31CFF7CC21D7D1F300DE7A79 /* PBXTargetDependency */, + ); + name = MessageParticlesTests; + productName = MessageParticlesTests; + productReference = 31CFF7C921D7D1F300DE7A79 /* MessageParticlesTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 31ACB72021B0EE2D00391ADF /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Original; + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "dYdX Trading Inc"; + TargetAttributes = { + 313EB7C521BB592E00BEF926 = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1010; + }; + 313EBB4221BB7A4B00BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 313EBB4A21BB7A4B00BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 31ACB72821B0EE2D00391ADF = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1020; + }; + 31ACB73121B0EE2D00391ADF = { + CreatedOnToolsVersion = 10.1; + }; + 31CFF7C021D7D1F200DE7A79 = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1210; + }; + 31CFF7C821D7D1F300DE7A79 = { + CreatedOnToolsVersion = 10.1; + }; + }; + }; + buildConfigurationList = 31ACB72321B0EE2D00391ADF /* Build configuration list for PBXProject "PlatformParticles" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 31ACB71F21B0EE2D00391ADF; + packageReferences = ( + 0271236728E545A50015D39F /* XCRemoteSwiftPackageReference "SwiftMessages" */, + ); + productRefGroup = 31ACB72A21B0EE2D00391ADF /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 317E998823D80E0800C348FA /* Products */; + ProjectRef = 317E998723D80E0800C348FA /* ParticlesCommonModels.xcodeproj */; + }, + { + ProductGroup = 31ACB79221B0EFF300391ADF /* Products */; + ProjectRef = 31ACB79121B0EFF300391ADF /* ParticlesKit.xcodeproj */; + }, + { + ProductGroup = 31ACB7C621B0F0F800391ADF /* Products */; + ProjectRef = 31ACB7C521B0F0F800391ADF /* PlatformRouting.xcodeproj */; + }, + { + ProductGroup = 31ACB78921B0EFF300391ADF /* Products */; + ProjectRef = 31ACB78821B0EFF300391ADF /* RoutingKit.xcodeproj */; + }, + { + ProductGroup = 31ACB79B21B0EFF300391ADF /* Products */; + ProjectRef = 31ACB79A21B0EFF300391ADF /* UIToolkits.xcodeproj */; + }, + { + ProductGroup = 31ACB7A421B0EFF300391ADF /* Products */; + ProjectRef = 31ACB7A321B0EFF300391ADF /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 31ACB72821B0EE2D00391ADF /* PlatformParticles */, + 313EB7C521BB592E00BEF926 /* PlatformParticlesAppleWatch */, + 313EBB4221BB7A4B00BEF926 /* PlatformParticlesAppleTV */, + 31CFF7C021D7D1F200DE7A79 /* MessageParticles */, + 31ACB73121B0EE2D00391ADF /* PlatformParticlesTests */, + 313EBB4A21BB7A4B00BEF926 /* PlatformParticlesAppleTVTests */, + 31CFF7C821D7D1F300DE7A79 /* MessageParticlesTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 310C3B2721D9C9BF0081E56D /* UIAppToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIAppToolkits.framework; + remoteRef = 310C3B2621D9C9BF0081E56D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 310C3B2921D9C9BF0081E56D /* UIAppToolkitsTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UIAppToolkitsTests.xctest; + remoteRef = 310C3B2821D9C9BF0081E56D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB28E21BA299300BEF926 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 313EB28D21BA299300BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB29021BA299300BEF926 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 313EB28F21BA299300BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB29421BA299300BEF926 /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 313EB29321BA299300BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB29E21BA299300BEF926 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 313EB29D21BA299300BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB2A021BA299300BEF926 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 313EB29F21BA299300BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB2A421BA299300BEF926 /* ParticlesKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitAppleTVTests.xctest; + remoteRef = 313EB2A321BA299300BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB2AF21BA299300BEF926 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 313EB2AE21BA299300BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB2B121BA299300BEF926 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 313EB2B021BA299300BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB2B521BA299300BEF926 /* RoutingKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitAppleTVTests.xctest; + remoteRef = 313EB2B421BA299300BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB7BF21BB591200BEF926 /* PlatformRouting.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformRouting.framework; + remoteRef = 313EB7BE21BB591200BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB86B21BB73B400BEF926 /* UIToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIToolkits.framework; + remoteRef = 313EB86A21BB73B400BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBB5921BB7A4B00BEF926 /* UIToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIToolkits.framework; + remoteRef = 313EBB5821BB7A4B00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBB5D21BB7A4B00BEF926 /* UIToolkitsAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UIToolkitsAppleTVTests.xctest; + remoteRef = 313EBB5C21BB7A4B00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBCD221BB7D1100BEF926 /* PlatformRouting.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformRouting.framework; + remoteRef = 313EBCD121BB7D1100BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBCD621BB7D1100BEF926 /* PlatformRoutingAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformRoutingAppleTVTests.xctest; + remoteRef = 313EBCD521BB7D1100BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 317E999223D80E0800C348FA /* ParticlesCommonModels.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesCommonModels.framework; + remoteRef = 317E999123D80E0800C348FA /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 317E999423D80E0800C348FA /* ParticlesCommonModels.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesCommonModels.framework; + remoteRef = 317E999323D80E0800C348FA /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 317E999623D80E0800C348FA /* ParticlesCommonModels.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesCommonModels.framework; + remoteRef = 317E999523D80E0800C348FA /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 317E999A23D80E0800C348FA /* ParticlesCommonModelsTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesCommonModelsTests.xctest; + remoteRef = 317E999923D80E0800C348FA /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 317E999C23D80E0800C348FA /* ParticlesCommonModelsAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesCommonModelsAppleTVTests.xctest; + remoteRef = 317E999B23D80E0800C348FA /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB78E21B0EFF300391ADF /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 31ACB78D21B0EFF300391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB79021B0EFF300391ADF /* RoutingKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitTests.xctest; + remoteRef = 31ACB78F21B0EFF300391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB79721B0EFF300391ADF /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31ACB79621B0EFF300391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB79921B0EFF300391ADF /* ParticlesKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitTests.xctest; + remoteRef = 31ACB79821B0EFF300391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB7A021B0EFF300391ADF /* UIToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIToolkits.framework; + remoteRef = 31ACB79F21B0EFF300391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB7A221B0EFF300391ADF /* UIToolkitsTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UIToolkitsTests.xctest; + remoteRef = 31ACB7A121B0EFF300391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB7A921B0EFF300391ADF /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 31ACB7A821B0EFF300391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB7AB21B0EFF300391ADF /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 31ACB7AA21B0EFF300391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB7CB21B0F0F800391ADF /* PlatformRouting.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformRouting.framework; + remoteRef = 31ACB7CA21B0F0F800391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31ACB7CD21B0F0F800391ADF /* PlatformRoutingTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformRoutingTests.xctest; + remoteRef = 31ACB7CC21B0F0F800391ADF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 313EB7C421BB592E00BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314C35D924C7C36E00695F7E /* NotificationPermissionAction.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB4121BB7A4B00BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB4921BB7A4B00BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB72721B0EE2D00391ADF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314C35CC24C7C31400695F7E /* Version.xib in Resources */, + 314C35E024C7C3A600695F7E /* TableViewCell.xib in Resources */, + 3101F9D52511317800AC4010 /* Media.xcassets in Resources */, + 3101F9D12511311600AC4010 /* AuthLoginPrimer.storyboard in Resources */, + 314C35E424C7C3A600695F7E /* ParallaxTableViewCell.xib in Resources */, + 317F18C02572DA8A00D178B8 /* image_action_cell.xib in Resources */, + 314C35E124C7C3A600695F7E /* CollectionViewCell.xib in Resources */, + 314C35D024C7C35000695F7E /* PhotoAlbumsPermissionAction.xib in Resources */, + 3101F9D32511314200AC4010 /* ConfirmAction.xib in Resources */, + 31308B8424FAF01E003B5B9A /* LocationPermissionAction.xib in Resources */, + 314C35E224C7C3A600695F7E /* text_collection_cell.xib in Resources */, + 314C35D624C7C35000695F7E /* NotificationPermissionAction.xib in Resources */, + 314C35D324C7C35000695F7E /* CameraPermissionAction.xib in Resources */, + 31C7E6882551CE21000643B0 /* ListInteractor.xib in Resources */, + 31E8221F2555C3FD001FE2F1 /* Thinline.xib in Resources */, + 317F18C12572DA8A00D178B8 /* text_table_cell.xib in Resources */, + 314C35E324C7C3A600695F7E /* image_collection_cell.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB73021B0EE2D00391ADF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CFF7BF21D7D1F200DE7A79 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CFF7C721D7D1F300DE7A79 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 03C20C522A64DB6829F3E42A /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-PlatformParticlesTests/Pods-iOS-PlatformParticlesTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-PlatformParticlesTests/Pods-iOS-PlatformParticlesTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-PlatformParticlesTests/Pods-iOS-PlatformParticlesTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 428C18730BA8C27E584F9DE6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-PlatformParticlesTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + A01C7D16C3374E753C7B2950 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-PlatformParticles-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 313EB7C221BB592E00BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB86C21BB73B400BEF926 /* XibListPresenter.swift in Sources */, + 3111C2442543AD2E00F204D9 /* CompositeObjectPresenter.swift in Sources */, + 313EB7DF21BB5AB800BEF926 /* TableViewListPresenter.swift in Sources */, + 314B677A23DCEEEB00139EB3 /* XibPresenterProtocol.swift in Sources */, + 313EBF3721BE0C7B00BEF926 /* ListPresenterInterfaceController.swift in Sources */, + 3101F9C62511303E00AC4010 /* ConfirmAction.swift in Sources */, + 313EBE0321BC400300BEF926 /* DefaultExtensionDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB3F21BB7A4B00BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B603823DCCE2900139EB3 /* ListPresenter+iOS.swift in Sources */, + 314B600A23DCCE2900139EB3 /* ErrorAlert.swift in Sources */, + 314B600223DCCE2900139EB3 /* CollectionViewGridPresenter.swift in Sources */, + 3101F9C72511303E00AC4010 /* ConfirmAction.swift in Sources */, + 314B5FDC23DCCE2900139EB3 /* ObjectPresenterView.swift in Sources */, + 314B604A23DCCE2900139EB3 /* BarButtonSwitchedListPresenterManager.swift in Sources */, + 314B604E23DCCE2900139EB3 /* ListPresenterManager.swift in Sources */, + 314B5FD223DCCE2900139EB3 /* PrivacyPermissionPresenter.swift in Sources */, + 314B5FE423DCCE2900139EB3 /* ObjectPresenterCollectionViewCell.swift in Sources */, + 314B5FE223DCCE2900139EB3 /* UIView+Xib.swift in Sources */, + 314B602623DCCE2900139EB3 /* PhotoAlbumsPermissionAction.swift in Sources */, + 314B603C23DCCE2900139EB3 /* RigidCollectionViewListPresenter.swift in Sources */, + 314B601223DCCE2900139EB3 /* DataPresenterViewController.swift in Sources */, + 314B603623DCCE2900139EB3 /* ProgressPresenter.swift in Sources */, + 314B600E23DCCE2900139EB3 /* SearchViewController.swift in Sources */, + 3101F9CB251130A700AC4010 /* AuthLoginPresenter.swift in Sources */, + 314B5FD623DCCE2900139EB3 /* UIView+Binding.swift in Sources */, + 314B603A23DCCE2900139EB3 /* CollectionViewXibRegister.swift in Sources */, + 314B604023DCCE2900139EB3 /* CollectionViewSectionListPresenter.swift in Sources */, + 3111C2452543AD2E00F204D9 /* CompositeObjectPresenter.swift in Sources */, + 314B600023DCCE2900139EB3 /* GridPresenter+iOS.swift in Sources */, + 314B5FE623DCCE2900139EB3 /* ObjectLikeBarItemPresenter.swift in Sources */, + 314B5FEC23DCCE2900139EB3 /* SmartObjectPresenter.swift in Sources */, + 31A071A626B890C20069C58A /* TableViewListPresenter.swift in Sources */, + 314B604C23DCCE2900139EB3 /* GridPresenterManager.swift in Sources */, + 314B605023DCCE2900139EB3 /* StackViewListPresenter.swift in Sources */, + 314B604223DCCE2900139EB3 /* XibListPresenter.swift in Sources */, + 314B5FDA23DCCE2900139EB3 /* ObjectPresenterContainerView.swift in Sources */, + 31EAFE3125C63A8800412F11 /* NavigationObjectPresenter.swift in Sources */, + 314B5FE823DCCE2900139EB3 /* XibActionPresenter.swift in Sources */, + 314B602C23DCCE2900139EB3 /* PrivacyPermissionAction.swift in Sources */, + 314B603E23DCCE2900139EB3 /* CollectionViewListPresenter.swift in Sources */, + 314B603223DCCE2900139EB3 /* ParticlesPlatformInjection.swift in Sources */, + 314B605423DCCE2900139EB3 /* TableViewSectionListPresenter.swift in Sources */, + 314B602A23DCCE2900139EB3 /* NotificationPermissionAction.swift in Sources */, + 314B5FDE23DCCE2900139EB3 /* ParallaxObjectPresenterTableViewCell.swift in Sources */, + 314B5FF023DCCE2900139EB3 /* ObjectPresenterTableViewCell.swift in Sources */, + 314B5FD823DCCE2900139EB3 /* ListInteractorPresenterView.swift in Sources */, + 314B677B23DCEEEB00139EB3 /* XibPresenterProtocol.swift in Sources */, + 310D8901262DF3EC00FEF56A /* ObjectViewPresenter.swift in Sources */, + 314B605A23DCCE2900139EB3 /* TableViewXibRegister.swift in Sources */, + 314B601E23DCCE2900139EB3 /* ConfirmationAction.swift in Sources */, + 314B5FEA23DCCE2900139EB3 /* LikedSmartObjectPresenter.swift in Sources */, + 314B603023DCCE2900139EB3 /* ParticlesPlatformExtensionInjection.swift in Sources */, + 314B601423DCCE2900139EB3 /* ListPresenterViewController.swift in Sources */, + 314B605223DCCE2900139EB3 /* NativeListPresenter.swift in Sources */, + 314B600C23DCCE2900139EB3 /* GridPresenterViewController.swift in Sources */, + 314B600623DCCE2900139EB3 /* ParticlesAppDelegate.swift in Sources */, + 314B5FF223DCCE2900139EB3 /* TransformerTracker.swift in Sources */, + 314B5FF423DCCE2900139EB3 /* Attribution.swift in Sources */, + 314B602823DCCE2900139EB3 /* CalendarPermissionAction.swift in Sources */, + 314B600423DCCE2900139EB3 /* NativeGridPresenter.swift in Sources */, + 314B601623DCCE2900139EB3 /* TrackingViewController.swift in Sources */, + 314B5FD423DCCE2900139EB3 /* BackgroundTasksPoolInteractor.swift in Sources */, + 314B601023DCCE2900139EB3 /* UpgradeViewController.swift in Sources */, + 3101F9CE251130D100AC4010 /* AuthLoginPrimerViewController.swift in Sources */, + 314B601823DCCE2900139EB3 /* PrivacyPermissionViewController.swift in Sources */, + 314B5FFE23DCCE2900139EB3 /* XibGridPresenter.swift in Sources */, + 314B602E23DCCE2900139EB3 /* ParticlesPlatformAppInjection.swift in Sources */, + 314B5FEE23DCCE2900139EB3 /* ActionPresenter.swift in Sources */, + 314B606023DCCE2900139EB3 /* AppInfoPresenter.swift in Sources */, + 3128DDFE26B89CE90099B62E /* LikedTableViewListPresenter.swift in Sources */, + 314B604823DCCE2900139EB3 /* ListLoadingPresenter.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB4721BB7A4B00BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBB5121BB7A4B00BEF926 /* PlatformParticlesAppleTVTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB72521B0EE2D00391ADF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B603723DCCE2900139EB3 /* ListPresenter+iOS.swift in Sources */, + 3111C2432543AD2E00F204D9 /* CompositeObjectPresenter.swift in Sources */, + 31B0610026A8D1B500431CFA /* GraphingPresenter.swift in Sources */, + 3101F9CA2511308E00AC4010 /* AuthLoginPresenter.swift in Sources */, + 31A53E892713693F0097BFE4 /* BarLineChartViewBase+Scaling.swift in Sources */, + 314B600923DCCE2900139EB3 /* ErrorAlert.swift in Sources */, + 31AA4B2C2704D37900451307 /* CandleStickGraphingRenderer.swift in Sources */, + 310D8900262DF3EC00FEF56A /* ObjectViewPresenter.swift in Sources */, + 314B600123DCCE2900139EB3 /* CollectionViewGridPresenter.swift in Sources */, + 314B5FDB23DCCE2900139EB3 /* ObjectPresenterView.swift in Sources */, + 31E823D825566599001FE2F1 /* PermissionPrimerViewController.swift in Sources */, + 314B601B23DCCE2900139EB3 /* MessageAction.swift in Sources */, + 314B604923DCCE2900139EB3 /* BarButtonSwitchedListPresenterManager.swift in Sources */, + 31ED7328273461130032B501 /* ParticlesCandleChartDataSet.swift in Sources */, + 31AA4B042704BF1300451307 /* GraphingAxisFormatter.swift in Sources */, + 31CC977A26CB14EF00E56B83 /* CombinedGraphingPresenter.swift in Sources */, + 31439A5C2673DE2B003871A1 /* ObjectOptionsLinePresenter.swift in Sources */, + 3128DDF026B89CE80099B62E /* LikedTableViewListPresenter.swift in Sources */, + 314B604D23DCCE2900139EB3 /* ListPresenterManager.swift in Sources */, + 314B5FD123DCCE2900139EB3 /* PrivacyPermissionPresenter.swift in Sources */, + 314B5FE323DCCE2900139EB3 /* ObjectPresenterCollectionViewCell.swift in Sources */, + 314B5FE123DCCE2900139EB3 /* UIView+Xib.swift in Sources */, + 3101F9C52511303E00AC4010 /* ConfirmAction.swift in Sources */, + 31347F7927110DAE0095829A /* ParticlesLineChartDataEntry.swift in Sources */, + 643494AA27B810E000A0CC09 /* TimeIntervalObjectViewPresenter.swift in Sources */, + 31ED7322273460350032B501 /* ParticlesLineChartDataSet.swift in Sources */, + 314B602523DCCE2900139EB3 /* PhotoAlbumsPermissionAction.swift in Sources */, + 314B603B23DCCE2900139EB3 /* RigidCollectionViewListPresenter.swift in Sources */, + 31308B8224FAEF86003B5B9A /* ColoredClusterPresenter.swift in Sources */, + 3165647926E2BDEC002D797E /* ListObjectViewPresenter.swift in Sources */, + 3197614A26850649008EB757 /* ObjectWebLinePresenter.swift in Sources */, + 314B602123DCCE2900139EB3 /* CameraPermissionAction.swift in Sources */, + 314B601123DCCE2900139EB3 /* DataPresenterViewController.swift in Sources */, + 3145CE1725F437A700BCCFCA /* LineGraphingPresenter.swift in Sources */, + 314B603523DCCE2900139EB3 /* ProgressPresenter.swift in Sources */, + 314B600D23DCCE2900139EB3 /* SearchViewController.swift in Sources */, + 3180B060272C770E00CCAB67 /* ParticlesChartDataEntryProtocol.swift in Sources */, + 314B605523DCCE2900139EB3 /* TableViewListPresenter.swift in Sources */, + 314B5FD523DCCE2900139EB3 /* UIView+Binding.swift in Sources */, + 314B603923DCCE2900139EB3 /* CollectionViewXibRegister.swift in Sources */, + 314B603F23DCCE2900139EB3 /* CollectionViewSectionListPresenter.swift in Sources */, + 31E1B30B26B0738200A4E3FC /* PieGraphingPresenter.swift in Sources */, + 27ED353A2AD5BD0900C159F5 /* BannerErrorAlert.swift in Sources */, + 31439A422672C3A3003871A1 /* ObjectEditLinePresenter.swift in Sources */, + 314B5FFF23DCCE2900139EB3 /* GridPresenter+iOS.swift in Sources */, + 31439A842677F665003871A1 /* ObjectCompositeLinePresenter.swift in Sources */, + 314B5FE523DCCE2900139EB3 /* ObjectLikeBarItemPresenter.swift in Sources */, + 314B604523DCCE2900139EB3 /* CalendarViewListPresenter.swift in Sources */, + 314B5FEB23DCCE2900139EB3 /* SmartObjectPresenter.swift in Sources */, + 31347F6E2710FDF40095829A /* ParticlesCandleChartDataEntry.swift in Sources */, + 314B604B23DCCE2900139EB3 /* GridPresenterManager.swift in Sources */, + 314B604F23DCCE2900139EB3 /* StackViewListPresenter.swift in Sources */, + 314B604123DCCE2900139EB3 /* XibListPresenter.swift in Sources */, + 314B5FD923DCCE2900139EB3 /* ObjectPresenterContainerView.swift in Sources */, + 314B604323DCCE2900139EB3 /* CarouselListPresenter.swift in Sources */, + 314B5FE723DCCE2900139EB3 /* XibActionPresenter.swift in Sources */, + 3101F9CD251130D100AC4010 /* AuthLoginPrimerViewController.swift in Sources */, + 314B602B23DCCE2900139EB3 /* PrivacyPermissionAction.swift in Sources */, + 314B603D23DCCE2900139EB3 /* CollectionViewListPresenter.swift in Sources */, + 31ED7326273460F20032B501 /* ParticlesPieChartDataSet.swift in Sources */, + 314B5FF523DCCE2900139EB3 /* JsonDocument.swift in Sources */, + 314B603123DCCE2900139EB3 /* ParticlesPlatformInjection.swift in Sources */, + 314B605323DCCE2900139EB3 /* TableViewSectionListPresenter.swift in Sources */, + 314B602923DCCE2900139EB3 /* NotificationPermissionAction.swift in Sources */, + 314B5FDD23DCCE2900139EB3 /* ParallaxObjectPresenterTableViewCell.swift in Sources */, + 31439A442672C4E6003871A1 /* ObjectSwitchLinePresenter.swift in Sources */, + 314B5FEF23DCCE2900139EB3 /* ObjectPresenterTableViewCell.swift in Sources */, + 314B5FD723DCCE2900139EB3 /* ListInteractorPresenterView.swift in Sources */, + 64AF47832817123C00EBFDC6 /* SafariAction.swift in Sources */, + 314B677923DCEEEB00139EB3 /* XibPresenterProtocol.swift in Sources */, + 314B605923DCCE2900139EB3 /* TableViewXibRegister.swift in Sources */, + 314B601D23DCCE2900139EB3 /* ConfirmationAction.swift in Sources */, + 31347F7A27110DAE0095829A /* ParticlesBarChartDataEntry.swift in Sources */, + 31ECFF58253C986E00D6336F /* TableViewListPresenter+Actions.swift in Sources */, + 314B5FE923DCCE2900139EB3 /* LikedSmartObjectPresenter.swift in Sources */, + 318A5BA8272CA108000DA46C /* ParticlesPieChartDataEntry.swift in Sources */, + 3145CE0725F432BE00BCCFCA /* GraphingListPresenter.swift in Sources */, + 314B602F23DCCE2900139EB3 /* ParticlesPlatformExtensionInjection.swift in Sources */, + 31ED7324273460D40032B501 /* ParticlesBarChartDataSet.swift in Sources */, + 648F46C727D8090800FD2FC6 /* XibTableViewHeaderFooterView.swift in Sources */, + 31439A402672C29E003871A1 /* ObjectTextLinePresenter.swift in Sources */, + 314614432703B6B60000DDFF /* TimeAxisFormatter.swift in Sources */, + 314B601323DCCE2900139EB3 /* ListPresenterViewController.swift in Sources */, + 314B605123DCCE2900139EB3 /* NativeListPresenter.swift in Sources */, + 314B600B23DCCE2900139EB3 /* GridPresenterViewController.swift in Sources */, + 314B600523DCCE2900139EB3 /* ParticlesAppDelegate.swift in Sources */, + 314B5FF123DCCE2900139EB3 /* TransformerTracker.swift in Sources */, + 314B5FF323DCCE2900139EB3 /* Attribution.swift in Sources */, + 314B602723DCCE2900139EB3 /* CalendarPermissionAction.swift in Sources */, + 314B600323DCCE2900139EB3 /* NativeGridPresenter.swift in Sources */, + 31E822022555C38A001FE2F1 /* ThinlinePresenter.swift in Sources */, + 314B5FF723DCCE2900139EB3 /* JsonAppGroupFileCaching.swift in Sources */, + 31ED732027345B3C0032B501 /* ParticlesChartDataSetProtocol.swift in Sources */, + 314B601523DCCE2900139EB3 /* TrackingViewController.swift in Sources */, + 3180B05C272B509400CCAB67 /* DismissAction.swift in Sources */, + 314B5FD323DCCE2900139EB3 /* BackgroundTasksPoolInteractor.swift in Sources */, + 314B600F23DCCE2900139EB3 /* UpgradeViewController.swift in Sources */, + 31439A2B2672BAD6003871A1 /* ObjectLinePresenter.swift in Sources */, + 317556702628ED3400D76C4C /* CandleStickGraphingPresenter.swift in Sources */, + 31EAFE3025C63A8800412F11 /* NavigationObjectPresenter.swift in Sources */, + 31439A2D2672BC26003871A1 /* ObjectValueLinePresenter.swift in Sources */, + 314B601723DCCE2900139EB3 /* PrivacyPermissionViewController.swift in Sources */, + 648F46D627D80B4400FD2FC6 /* TableViewHeaderXibRegister.swift in Sources */, + 314B5FFD23DCCE2900139EB3 /* XibGridPresenter.swift in Sources */, + 314B602D23DCCE2900139EB3 /* ParticlesPlatformAppInjection.swift in Sources */, + 314B5FED23DCCE2900139EB3 /* ActionPresenter.swift in Sources */, + 314B605F23DCCE2900139EB3 /* AppInfoPresenter.swift in Sources */, + 314B604723DCCE2900139EB3 /* ListLoadingPresenter.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31ACB72E21B0EE2D00391ADF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31ACB73821B0EE2D00391ADF /* PlatformParticlesTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CFF7BD21D7D1F200DE7A79 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 310D8902262DF3EC00FEF56A /* ObjectViewPresenter.swift in Sources */, + 3111C2462543AD2E00F204D9 /* CompositeObjectPresenter.swift in Sources */, + 31EAFE3225C63A8800412F11 /* NavigationObjectPresenter.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CFF7C521D7D1F300DE7A79 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31CFF7CF21D7D1F300DE7A79 /* MessageParticlesTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 310C3B1721D9C9BF0081E56D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UIAppToolkits; + targetProxy = 310C3B1621D9C9BF0081E56D /* PBXContainerItemProxy */; + }; + 313EB7CF21BB596000BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleWatch; + targetProxy = 313EB7CE21BB596000BEF926 /* PBXContainerItemProxy */; + }; + 313EB7D121BB596000BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKitAppleWatch; + targetProxy = 313EB7D021BB596000BEF926 /* PBXContainerItemProxy */; + }; + 313EB7D321BB596000BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKitAppleWatch; + targetProxy = 313EB7D221BB596000BEF926 /* PBXContainerItemProxy */; + }; + 313EB7D521BB596000BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PlatformRoutingAppleWatch; + targetProxy = 313EB7D421BB596000BEF926 /* PBXContainerItemProxy */; + }; + 313EB86E21BB73D300BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UIToolkitsAppleWatch; + targetProxy = 313EB86D21BB73D300BEF926 /* PBXContainerItemProxy */; + }; + 313EBB4E21BB7A4B00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 313EBB4221BB7A4B00BEF926 /* PlatformParticlesAppleTV */; + targetProxy = 313EBB4D21BB7A4B00BEF926 /* PBXContainerItemProxy */; + }; + 313EBCDC21BB7D2C00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleTV; + targetProxy = 313EBCDB21BB7D2C00BEF926 /* PBXContainerItemProxy */; + }; + 313EBCDE21BB7D2C00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKitAppleTV; + targetProxy = 313EBCDD21BB7D2C00BEF926 /* PBXContainerItemProxy */; + }; + 313EBCE021BB7D2C00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKitAppleTV; + targetProxy = 313EBCDF21BB7D2C00BEF926 /* PBXContainerItemProxy */; + }; + 313EBCE221BB7D2C00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PlatformRoutingAppleTV; + targetProxy = 313EBCE121BB7D2C00BEF926 /* PBXContainerItemProxy */; + }; + 313EBCE421BB7D2C00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UIToolkitsAppleTV; + targetProxy = 313EBCE321BB7D2C00BEF926 /* PBXContainerItemProxy */; + }; + 317E99A023D80E1B00C348FA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesCommonModels; + targetProxy = 317E999F23D80E1B00C348FA /* PBXContainerItemProxy */; + }; + 317E99A223D80E2B00C348FA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesCommonModelsAppleTV; + targetProxy = 317E99A123D80E2B00C348FA /* PBXContainerItemProxy */; + }; + 317E99A423D80E3100C348FA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesCommonModelsAppleWatch; + targetProxy = 317E99A323D80E3100C348FA /* PBXContainerItemProxy */; + }; + 31ACB73521B0EE2D00391ADF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31ACB72821B0EE2D00391ADF /* PlatformParticles */; + targetProxy = 31ACB73421B0EE2D00391ADF /* PBXContainerItemProxy */; + }; + 31ACB7B821B0F05E00391ADF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 31ACB7B721B0F05E00391ADF /* PBXContainerItemProxy */; + }; + 31ACB7BA21B0F05E00391ADF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKit; + targetProxy = 31ACB7B921B0F05E00391ADF /* PBXContainerItemProxy */; + }; + 31ACB7BC21B0F05E00391ADF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKit; + targetProxy = 31ACB7BB21B0F05E00391ADF /* PBXContainerItemProxy */; + }; + 31ACB7BE21B0F05E00391ADF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UIToolkits; + targetProxy = 31ACB7BD21B0F05E00391ADF /* PBXContainerItemProxy */; + }; + 31ACB7CF21B0F0FF00391ADF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PlatformRouting; + targetProxy = 31ACB7CE21B0F0FF00391ADF /* PBXContainerItemProxy */; + }; + 31CFF7CC21D7D1F300DE7A79 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31CFF7C021D7D1F200DE7A79 /* MessageParticles */; + targetProxy = 31CFF7CB21D7D1F300DE7A79 /* PBXContainerItemProxy */; + }; + 31CFF7DB21D7D21200DE7A79 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31ACB72821B0EE2D00391ADF /* PlatformParticles */; + targetProxy = 31CFF7DA21D7D21200DE7A79 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 313EB7CC21BB592E00BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PlatformParticlesAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformParticlesAppleWatch; + PRODUCT_NAME = PlatformParticles; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _watchOS"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Debug; + }; + 313EB7CD21BB592E00BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PlatformParticlesAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformParticlesAppleWatch; + PRODUCT_NAME = PlatformParticles; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _watchOS"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Release; + }; + 313EBB6121BB7A4B00BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PlatformParticlesAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformParticlesAppleTV; + PRODUCT_NAME = PlatformParticles; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _tvOS"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313EBB6221BB7A4B00BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PlatformParticlesAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformParticlesAppleTV; + PRODUCT_NAME = PlatformParticles; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _tvOS"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 313EBB6421BB7A4B00BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = PlatformParticlesAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformParticlesAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313EBB6521BB7A4B00BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = PlatformParticlesAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformParticlesAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 31ACB73B21B0EE2D00391ADF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 31ACB73C21B0EE2D00391ADF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 31ACB73E21B0EE2D00391ADF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F4B59F8B8C5836B5E6EDB8F8 /* Pods-iOS-PlatformParticles.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PlatformParticles/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.PlatformParticles; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _iOS"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31ACB73F21B0EE2D00391ADF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8E50A1767A8443DAE7A5E486 /* Pods-iOS-PlatformParticles.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PlatformParticles/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.PlatformParticles; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _iOS"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 31ACB74121B0EE2D00391ADF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C0294673341C6FE9011A57B7 /* Pods-iOS-PlatformParticlesTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = PlatformParticlesTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformParticlesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31ACB74221B0EE2D00391ADF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B09049A22701CF86FA5E0853 /* Pods-iOS-PlatformParticlesTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = PlatformParticlesTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformParticlesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 31CFF7D321D7D1F300DE7A79 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = MessageParticles/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.MessageParticles; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31CFF7D421D7D1F300DE7A79 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = MessageParticles/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.MessageParticles; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 31CFF7D621D7D1F300DE7A79 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = MessageParticlesTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.MessageParticlesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31CFF7D721D7D1F300DE7A79 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = MessageParticlesTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.MessageParticlesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 313EB7CB21BB592E00BEF926 /* Build configuration list for PBXNativeTarget "PlatformParticlesAppleWatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EB7CC21BB592E00BEF926 /* Debug */, + 313EB7CD21BB592E00BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313EBB6021BB7A4B00BEF926 /* Build configuration list for PBXNativeTarget "PlatformParticlesAppleTV" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EBB6121BB7A4B00BEF926 /* Debug */, + 313EBB6221BB7A4B00BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313EBB6321BB7A4B00BEF926 /* Build configuration list for PBXNativeTarget "PlatformParticlesAppleTVTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EBB6421BB7A4B00BEF926 /* Debug */, + 313EBB6521BB7A4B00BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31ACB72321B0EE2D00391ADF /* Build configuration list for PBXProject "PlatformParticles" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31ACB73B21B0EE2D00391ADF /* Debug */, + 31ACB73C21B0EE2D00391ADF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31ACB73D21B0EE2D00391ADF /* Build configuration list for PBXNativeTarget "PlatformParticles" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31ACB73E21B0EE2D00391ADF /* Debug */, + 31ACB73F21B0EE2D00391ADF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31ACB74021B0EE2D00391ADF /* Build configuration list for PBXNativeTarget "PlatformParticlesTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31ACB74121B0EE2D00391ADF /* Debug */, + 31ACB74221B0EE2D00391ADF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31CFF7D221D7D1F300DE7A79 /* Build configuration list for PBXNativeTarget "MessageParticles" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31CFF7D321D7D1F300DE7A79 /* Debug */, + 31CFF7D421D7D1F300DE7A79 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31CFF7D521D7D1F300DE7A79 /* Build configuration list for PBXNativeTarget "MessageParticlesTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31CFF7D621D7D1F300DE7A79 /* Debug */, + 31CFF7D721D7D1F300DE7A79 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 0271236728E545A50015D39F /* XCRemoteSwiftPackageReference "SwiftMessages" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SwiftKickMobile/SwiftMessages"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 9.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 0271236828E545A50015D39F /* SwiftMessages */ = { + isa = XCSwiftPackageProductDependency; + package = 0271236728E545A50015D39F /* XCRemoteSwiftPackageReference "SwiftMessages" */; + productName = SwiftMessages; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 31ACB72021B0EE2D00391ADF /* Project object */; +} diff --git a/PlatformParticles/PlatformParticles.xcodeproj/xcshareddata/xcschemes/MessageParticles.xcscheme b/PlatformParticles/PlatformParticles.xcodeproj/xcshareddata/xcschemes/MessageParticles.xcscheme new file mode 100644 index 000000000..4013ed2dc --- /dev/null +++ b/PlatformParticles/PlatformParticles.xcodeproj/xcshareddata/xcschemes/MessageParticles.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles.xcodeproj/xcshareddata/xcschemes/PlatformParticles.xcscheme b/PlatformParticles/PlatformParticles.xcodeproj/xcshareddata/xcschemes/PlatformParticles.xcscheme new file mode 100644 index 000000000..c50942a86 --- /dev/null +++ b/PlatformParticles/PlatformParticles.xcodeproj/xcshareddata/xcschemes/PlatformParticles.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Info.plist b/PlatformParticles/PlatformParticles/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/PlatformParticles/PlatformParticles/PlatformParticles.h b/PlatformParticles/PlatformParticles/PlatformParticles.h new file mode 100644 index 000000000..ab9a60aa8 --- /dev/null +++ b/PlatformParticles/PlatformParticles/PlatformParticles.h @@ -0,0 +1,19 @@ +// +// PlatformParticles.h +// PlatformParticles +// +// Created by Qiang Huang on 11/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for PlatformParticles. +FOUNDATION_EXPORT double PlatformParticlesVersionNumber; + +//! Project version string for PlatformParticles. +FOUNDATION_EXPORT const unsigned char PlatformParticlesVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/Contents.json b/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/loginprimer_lock.imageset/Contents.json b/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/loginprimer_lock.imageset/Contents.json new file mode 100644 index 000000000..933b987c1 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/loginprimer_lock.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "lock.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/loginprimer_lock.imageset/lock.pdf b/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/loginprimer_lock.imageset/lock.pdf new file mode 100644 index 000000000..756faa679 Binary files /dev/null and b/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/loginprimer_lock.imageset/lock.pdf differ diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/table_cell_checkmark.imageset/Contents.json b/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/table_cell_checkmark.imageset/Contents.json new file mode 100644 index 000000000..59029f22d --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/table_cell_checkmark.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "streamline-icon-check-1@32x32.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/table_cell_checkmark.imageset/streamline-icon-check-1@32x32.pdf b/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/table_cell_checkmark.imageset/streamline-icon-check-1@32x32.pdf new file mode 100644 index 000000000..d0cc20171 Binary files /dev/null and b/PlatformParticles/PlatformParticles/Resources/_iOS/Media.xcassets/table_cell_checkmark.imageset/streamline-icon-check-1@32x32.pdf differ diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/CollectionViewCell.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/CollectionViewCell.xib new file mode 100644 index 000000000..cb11e4bc4 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/CollectionViewCell.xib @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/ParallaxTableViewCell.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/ParallaxTableViewCell.xib new file mode 100644 index 000000000..92d046029 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/ParallaxTableViewCell.xib @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/TableViewCell.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/TableViewCell.xib new file mode 100644 index 000000000..ed3dfcf00 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/TableViewCell.xib @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/image_action_cell.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/image_action_cell.xib new file mode 100644 index 000000000..668c5e3ea --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/image_action_cell.xib @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/image_collection_cell.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/image_collection_cell.xib new file mode 100644 index 000000000..ff5057ab2 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/image_collection_cell.xib @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/text_collection_cell.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/text_collection_cell.xib new file mode 100644 index 000000000..3cf7757a3 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/text_collection_cell.xib @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/text_table_cell.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/text_table_cell.xib new file mode 100644 index 000000000..0d37b1837 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Cells/text_table_cell.xib @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Storyboards/AuthLoginPrimer.storyboard b/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Storyboards/AuthLoginPrimer.storyboard new file mode 100644 index 000000000..12c7c2a7f --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Storyboards/AuthLoginPrimer.storyboard @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/CameraPermissionAction.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/CameraPermissionAction.xib new file mode 100644 index 000000000..ba2d4acc1 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/CameraPermissionAction.xib @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/ConfirmAction.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/ConfirmAction.xib new file mode 100644 index 000000000..e31a795e9 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/ConfirmAction.xib @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/LocationPermissionAction.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/LocationPermissionAction.xib new file mode 100644 index 000000000..581db7285 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/LocationPermissionAction.xib @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/NotificationPermissionAction.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/NotificationPermissionAction.xib new file mode 100644 index 000000000..19d0b94a1 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/NotificationPermissionAction.xib @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/PhotoAlbumsPermissionAction.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/PhotoAlbumsPermissionAction.xib new file mode 100644 index 000000000..92c53feb5 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Routing/_Xib/PhotoAlbumsPermissionAction.xib @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Xib/ListInteractor.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Xib/ListInteractor.xib new file mode 100644 index 000000000..e05b34dae --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Xib/ListInteractor.xib @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Xib/Thinline.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Xib/Thinline.xib new file mode 100644 index 000000000..44ae252b8 --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Xib/Thinline.xib @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/Resources/_iOS/_Xib/Version.xib b/PlatformParticles/PlatformParticles/Resources/_iOS/_Xib/Version.xib new file mode 100644 index 000000000..beea44eea --- /dev/null +++ b/PlatformParticles/PlatformParticles/Resources/_iOS/_Xib/Version.xib @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformParticles/PlatformParticles/_Actions/ConfirmAction.swift b/PlatformParticles/PlatformParticles/_Actions/ConfirmAction.swift new file mode 100644 index 000000000..b63980502 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Actions/ConfirmAction.swift @@ -0,0 +1,65 @@ +// +// ConfirmAction.swift +// PlatformParticles +// +// Created by Qiang Huang on 9/15/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import Utilities + +open class ConfirmAction: NSObject, NavigableProtocol { + open var confirmation: PrompterProtocol? + public var completion: RoutingCompletionBlock? + + deinit { + confirmation?.dismiss() + complete(successful: false) + } + + @objc open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if request?.path == "/confirm" { + let title = parser.asString(request?.params?["title"]) + let text = parser.asString(request?.params?["text"]) + let confirm = parser.asString(request?.params?["confirm"]) + self.confirm(title: title, text: text, confirm: confirm, completion: completion) + } else { + completion?(nil, false) + } + } + + open func confirm(title: String?, text: String?, confirm: String?, completion: RoutingCompletionBlock?) { + if text != nil || title != nil, let prompter = PrompterFactory.shared?.prompter() { + prompter.set(title: title, message: text, style: .error) + self.completion = completion + var actions = [PrompterAction]() + actions.append(PrompterAction(title: confirm ?? "Yes", style: .destructive, enabled: true, selection: { [weak self] in + self?.complete(successful: true) + })) + actions.append(PrompterAction(title: "Cancel", style: .cancel, enabled: true, selection: { [weak self] in + self?.complete(successful: false) + })) + prompter.prompt(actions) + confirmation = prompter + } else { + completion?(nil, false) + } + } + + open func confirm(confirmation: PrompterProtocol, actions: [PrompterAction], completion: RoutingCompletionBlock?) { + self.confirmation = confirmation + var actions = actions + actions.append(PrompterAction.cancel(selection: { [weak self] in + self?.complete(successful: false) + })) + confirmation.prompt(actions) + } + + open func complete(successful: Bool) { + confirmation = nil + completion?(nil, successful) + completion = nil + } +} diff --git a/PlatformParticles/PlatformParticles/_Actions/ConfirmationAction.swift b/PlatformParticles/PlatformParticles/_Actions/ConfirmationAction.swift new file mode 100644 index 000000000..52e3cb843 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Actions/ConfirmationAction.swift @@ -0,0 +1,40 @@ +// +// ConfirmationAction.swift +// PlatformParticles +// +// Created by Qiang Huang on 1/21/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import Utilities + +open class ConfirmationAction: NSObject, NavigableProtocol { + open var confirmation: PrompterProtocol? + public var completion: RoutingCompletionBlock? + + deinit { + confirmation?.dismiss() + complete(successful: false) + } + + @objc open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + completion?(nil, false) + } + + open func confirm(confirmation: PrompterProtocol, actions: [PrompterAction], completion: RoutingCompletionBlock?) { + self.confirmation = confirmation + var actions = actions + actions.append(PrompterAction.cancel(selection: { [weak self] in + self?.complete(successful: false) + })) + confirmation.prompt(actions) + } + + open func complete(successful: Bool) { + confirmation = nil + completion?(nil, successful) + completion = nil + } +} diff --git a/PlatformParticles/PlatformParticles/_Actions/DismissAction.swift b/PlatformParticles/PlatformParticles/_Actions/DismissAction.swift new file mode 100644 index 000000000..06bd52744 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Actions/DismissAction.swift @@ -0,0 +1,38 @@ +// +// DismissAction.swift +// PlatformParticles +// +// Created by Qiang Huang on 9/15/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import Utilities + +public class DismissActionBuilder: NSObject, ObjectBuilderProtocol { + public func build() -> T? { + let action = DismissAction() + return action as? T + } +} + +private class DismissAction: NSObject, NavigableProtocol { + @objc open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if request?.path == "/action/dismiss" { + let viewController = UIViewController.topmost() + if viewController?.navigationController?.topViewController == viewController, + viewController?.navigationController?.viewControllers.count ?? 0 > 1 { + viewController?.navigationController?.popViewController(animated: animated) + } else if viewController?.presentingViewController !== nil { + viewController?.dismiss(animated: true, completion: { + completion?(nil, true) + }) + } else if viewController?.floatingParent !== nil { + viewController?.move(to: .tip) + } + } else { + completion?(nil, false) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Actions/SafariAction.swift b/PlatformParticles/PlatformParticles/_Actions/SafariAction.swift new file mode 100644 index 000000000..7b327800e --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Actions/SafariAction.swift @@ -0,0 +1,29 @@ +// +// SafariAction.swift +// PlatformParticles +// +// Created by John Huang on 4/25/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + + +import ParticlesKit +import RoutingKit +import Utilities + +public class SafariActionBuilder: NSObject, ObjectBuilderProtocol { + public func build() -> T? { + let action = SafariAction() + return action as? T + } +} + +open class SafariAction: NSObject, NavigableProtocol { + @objc open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if let url = request?.url { + URLHandler.shared?.open(url, completionHandler: nil) + } else { + completion?(nil, false) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Actions/_Permissions/BluetoothPermissionAction.swift b/PlatformParticles/PlatformParticles/_Actions/_Permissions/BluetoothPermissionAction.swift new file mode 100644 index 000000000..ccbde152f --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Actions/_Permissions/BluetoothPermissionAction.swift @@ -0,0 +1,19 @@ +// +// CalendarAuthorizationAction.swift +// PlatformParticles +// +// Created by Qiang Huang on 8/10/19. +// Copyright © 2019 Qiang Huang. All rights reserved. +// + +import Utilities + +public class BluetoothAuthorizationAction: PrivacyPermissionAction { + public override var path: String? { + return "/authorization/bluetooth" + } + + public override func authorization() -> PrivacyPermission? { + return BluetoothPermission.shared + } +} diff --git a/PlatformParticles/PlatformParticles/_Actions/_Permissions/CalendarPermissionAction.swift b/PlatformParticles/PlatformParticles/_Actions/_Permissions/CalendarPermissionAction.swift new file mode 100644 index 000000000..b6592df85 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Actions/_Permissions/CalendarPermissionAction.swift @@ -0,0 +1,19 @@ +// +// CalendarAuthorizationAction.swift +// PlatformParticles +// +// Created by Qiang Huang on 8/10/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +public class CalendarAuthorizationAction: PrivacyPermissionAction { + public override var path: String? { + return "/authorization/calendar" + } + + public override func authorization() -> PrivacyPermission? { + return CalendarPermission.shared + } +} diff --git a/PlatformParticles/PlatformParticles/_Actions/_Permissions/CameraPermissionAction.swift b/PlatformParticles/PlatformParticles/_Actions/_Permissions/CameraPermissionAction.swift new file mode 100644 index 000000000..c5608a56e --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Actions/_Permissions/CameraPermissionAction.swift @@ -0,0 +1,31 @@ +// +// CameraAuthorizationAction.swift +// PlatformParticles +// +// Created by Qiang Huang on 8/5/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIAppToolkits +import Utilities + +public class CameraPermissionAction: PrivacyPermissionAction { + override public var primer: String? { + return nil +// return "/primer/camera" + } + + override public var path: String? { + return "/authorization/camera" + } + + override public init() { + super.init() + required = true + } + + override public func authorization() -> PrivacyPermission? { + return CameraPermission.shared + } +} diff --git a/PlatformParticles/PlatformParticles/_Actions/_Permissions/LocationPermissionAction.swift b/PlatformParticles/PlatformParticles/_Actions/_Permissions/LocationPermissionAction.swift new file mode 100644 index 000000000..f2a858459 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Actions/_Permissions/LocationPermissionAction.swift @@ -0,0 +1,20 @@ +// +// LocationAuthorizationAction.swift +// PlatformParticles +// +// Created by Qiang Huang on 8/10/19. +// Copyright © 2019 Qiang Huang. All rights reserved. +// + +import ParticlesKit +import Utilities + +public class LocationPermissionAction: PrivacyPermissionAction { + override public var path: String? { + return "/authorization/location" + } + + override public func authorization() -> PrivacyPermission? { + return LocationPermission.shared + } +} diff --git a/PlatformParticles/PlatformParticles/_Actions/_Permissions/NotificationPermissionAction.swift b/PlatformParticles/PlatformParticles/_Actions/_Permissions/NotificationPermissionAction.swift new file mode 100644 index 000000000..b613b5561 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Actions/_Permissions/NotificationPermissionAction.swift @@ -0,0 +1,24 @@ +// +// NotificationPermissionAction.swift +// PlatformParticles +// +// Created by Qiang Huang on 8/5/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +public class NotificationPermissionAction: PrivacyPermissionAction { + override public var primer: String? { + return "/primer/notification" + } + + override public var path: String? { + return "/authorization/notification" + } + + override public func authorization() -> PrivacyPermission? { + return NotificationPermission.shared + } +} diff --git a/PlatformParticles/PlatformParticles/_Actions/_Permissions/PhotoAlbumsPermissionAction.swift b/PlatformParticles/PlatformParticles/_Actions/_Permissions/PhotoAlbumsPermissionAction.swift new file mode 100644 index 000000000..057c5af16 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Actions/_Permissions/PhotoAlbumsPermissionAction.swift @@ -0,0 +1,25 @@ +// +// CameraAuthorizationAction.swift +// PlatformParticles +// +// Created by Qiang Huang on 8/5/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +public class PhotoAlbumsPermissionAction: PrivacyPermissionAction { + override public var primer: String? { + return nil +// return "/primer/album" + } + + override public var path: String? { + return "/authorization/album" + } + + override public func authorization() -> PrivacyPermission? { + return PhotoAlbumsPermission.shared + } +} diff --git a/PlatformParticles/PlatformParticles/_Actions/_Permissions/PrivacyPermissionAction.swift b/PlatformParticles/PlatformParticles/_Actions/_Permissions/PrivacyPermissionAction.swift new file mode 100644 index 000000000..e1d32b56b --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Actions/_Permissions/PrivacyPermissionAction.swift @@ -0,0 +1,88 @@ +// +// PrivacyAuthorizationAction.swift +// PlatformParticles +// +// Created by Qiang Huang on 8/5/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import Utilities + +open class PrivacyPermissionAction: NSObject, NavigableProtocol { + @IBInspectable public var required: Bool = false + public var primer: String? { + return nil + } + + private var completion: RoutingCompletionBlock? + private var pending: PrivacyPermission? { + didSet { + changeObservation(from: oldValue, to: pending, keyPath: #keyPath(PrivacyPermission.authorization)) { [weak self] _, _, _, _ in + if let self = self { + switch self.pending?.authorization { + case .unknown: + break + + case .notDetermined: + if let primer = self.primer { + Router.shared?.navigate(to: RoutingRequest(path: primer, params: nil), animated: true, completion: nil) + } else { + self.pending?.promptToAuthorize() + } + + case .restricted: + if self.required { + self.completion?(nil, false) + self.completion = nil + if let primer = self.primer { + Router.shared?.navigate(to: RoutingRequest(path: primer, params: nil), animated: true, completion: nil) + } else { + self.pending?.promptWithRestriction() + } + } else { + self.completion?(nil, false) + self.completion = nil + } + + case .authorized: + self.completion?(nil, true) + self.completion = nil + + default: + if self.required { + self.completion?(nil, false) + self.completion = nil + if let primer = self.primer { + Router.shared?.navigate(to: RoutingRequest(path: primer, params: nil), animated: true, completion: nil) + } else { + self.pending?.promptToSettings() + } + } else { + self.completion?(nil, false) + self.completion = nil + } + } + } + } + } + } + + open var path: String? { + return nil + } + + open func authorization() -> PrivacyPermission? { + return nil + } + + open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if request?.path == path, let authorization = authorization() { + self.completion = completion + pending = authorization + } else { + completion?(nil, false) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_AppDelegate/ParticlesAppDelegate.swift b/PlatformParticles/PlatformParticles/_AppDelegate/ParticlesAppDelegate.swift new file mode 100644 index 000000000..1afc1cacf --- /dev/null +++ b/PlatformParticles/PlatformParticles/_AppDelegate/ParticlesAppDelegate.swift @@ -0,0 +1,177 @@ +// +// ParticlesAppDelegate.swift +// RetslyPlatformParticles +// +// Created by Qiang Huang on 12/3/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import PlatformRouting +import RoutingKit +import UIToolkits +import Utilities + +#if _iOS + import COSTouchVisualizer + import UIAppToolkits +#endif + +open class ParticlesAppDelegate: RoutingAppDelegate { + #if _iOS + #if _APPCLIP + open var window: UIWindow? + #else + open lazy var window: UIWindow? = { + if parser.asString(DebugSettings.shared?.debug?["show_touch"]) == "1" { + let window = COSTouchVisualizerWindow(frame: UIScreen.main.bounds) + window.touchVisualizerWindowDelegate = self + window.backgroundColor = UIColor.black + return window + } else { + return UIWindow() + } + }() + #endif + + #else + open lazy var window: UIWindow? = { + let window = UIWindow() + window.backgroundColor = UIColor.black + return window + }() + #endif + + override public init() { + super.init() + let injection = self.injection() + Injection.inject(injeciton: injection) { + ReachabilityMessage.shared.connectivityXib = "Connectivity" + } + } + + open func injection() -> ParticlesPlatformAppInjection { + return ParticlesPlatformAppInjection() + } + + public func add(tracking: TrackingProtocol) { + if let composite = Tracking.shared as? CompositeTracking { + composite.add(tracking) + } else { + if let existing = Tracking.shared { + let composite = compositeTracking() + composite.add(existing) + composite.add(tracking) + Tracking.shared = composite + } else { + Tracking.shared = tracking + } + } + } + + open func compositeTracking() -> CompositeTracking { + return CompositeTracking() + } + + public func add(errorLogging: ErrorLoggingProtocol) { + if let composite = ErrorLogging.shared as? CompositeErrorLogging { + composite.add(errorLogging) + } else { + if let existing = ErrorLogging.shared { + let composite = CompositeErrorLogging() + composite.add(existing) + composite.add(errorLogging) + ErrorLogging.shared = composite + } else { + ErrorLogging.shared = errorLogging + } + } + } + + override open func startup(completion: @escaping () -> Void) { + inject { [weak self] in + if let self = self { + #if DEBUG + self.add(tracking: DebugTracking()) + self.add(errorLogging: DebugErrorLogging()) + #else + #endif + completion() + } + } + } + + open func inject(completion: @escaping () -> Void) { + injectFeatures { [weak self] in + Injection.shared?.injectParsers() + self?.injectAppStart { [weak self] in + self?.injectAuth() + self?.injectLocation() + completion() + } + } + } + + open func injectFeatures(completion: @escaping () -> Void) { + Injection.shared?.injectFeatures(completion: completion) + } + + open func injectAppStart(completion: @escaping () -> Void) { + Injection.shared?.injectAppStart(completion: completion) + } + + open func injectAuth() { + } + + open func injectLocation() { + Console.shared.log("injectLocation") +// let locationService = RealLocation() +// locationService.authorization = LocationPermission.shared +// LocationProvider.shared = locationService + } + + override open func routingHistory() -> [RoutingRequest]? { + return nil + // return RoutingHistory.shared.history() + } + + // Reports app open from deep link for iOS 10 or later + override open func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + Attributer.shared?.application(application, open: url, options: options) + return super.application(application, open: url, options: options) + } + + override open func applicationDidBecomeActive(_ application: UIApplication) { + super.applicationDidBecomeActive(application) + Attributer.shared?.launch() + } + + override open func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + Attributer.shared?.application(application, continue: userActivity, restorationHandler: restorationHandler) + return super.application(application, continue: userActivity, restorationHandler: restorationHandler) + } + + // Reports app open from deep link from apps which do not support Universal Links (Twitter) and for iOS8 and below + override open func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { + Attributer.shared?.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation) + return super.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation) + } + + // Reports app open from a Universal Link for iOS 9 or later + override open func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { + Attributer.shared?.application(application, continue: userActivity, restorationHandler: restorationHandler) + return super.application(application, continue: userActivity, restorationHandler: restorationHandler) + } +} + +#if _iOS + extension ParticlesAppDelegate: COSTouchVisualizerWindowDelegate { + open func touchVisualizerWindowShouldShowFingertip(_ window: COSTouchVisualizerWindow!) -> Bool { + return true + } + + open func touchVisualizerWindowShouldAlwaysShowFingertip(_ window: COSTouchVisualizerWindow!) -> Bool { + return true + } + } +#endif diff --git a/PlatformParticles/PlatformParticles/_Attribution/Attribution.swift b/PlatformParticles/PlatformParticles/_Attribution/Attribution.swift new file mode 100644 index 000000000..19854a1e8 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Attribution/Attribution.swift @@ -0,0 +1,32 @@ +// +// AnalyticsProtocol.swift +// TrackingKit +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public protocol AttributionProtocol: NSObjectProtocol { + func launch() + + // Reports app open from a Universal Link for iOS 9 or later + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) + + // Reports app open from deep link from apps which do not support Universal Links (Twitter) and for iOS8 and below + func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) + + // Reports app open from deep link for iOS 10 or later + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) + + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) +} + +public class Attributer { + private static var _shared: AttributionProtocol? + public static var shared: AttributionProtocol? { + get { return _shared } + set { _shared = newValue } + } +} diff --git a/PlatformParticles/PlatformParticles/_Auth/AuthLoginPresenter.swift b/PlatformParticles/PlatformParticles/_Auth/AuthLoginPresenter.swift new file mode 100644 index 000000000..8615d6036 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Auth/AuthLoginPresenter.swift @@ -0,0 +1,78 @@ +// +// AuthLoginPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 9/15/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits +import Utilities + +public class AuthLoginPresenter: ObjectPresenter { + @objc public dynamic var auth: AuthService? { + didSet { + if auth !== oldValue { + changeObservation(from: oldValue, to: auth, keyPath: #keyPath(AuthService.provider)) { [weak self] _, _, _, _ in + if let self = self { + self.provider = self.auth?.provider + } + } + } + } + } + + @objc public dynamic var provider: AuthProviderProtocol? { + didSet { + changeObservation(from: oldValue, to: provider, keyPath: #keyPath(AuthProviderProtocol.token)) { [weak self] _, _, _, _ in + DispatchQueue.main.async { [weak self] in + if let self = self { + self.loggedIn = (self.provider?.token != nil) + } + } + } + } + } + + @objc public dynamic var loggedIn: Bool = false { + didSet { + if loggedIn != oldValue { + updateLoggedIn() + } else if !updated { + updateLoggedIn() + updated = true + } + } + } + + private var updated: Bool = false + + @IBOutlet private var view: UIView? + + private var embedded: UIView? { + didSet { + if embedded !== oldValue { + oldValue?.removeFromSuperview() + if let view = view, let embedded = embedded { + embedded.frame = view.bounds + view.addSubview(embedded) + } + } + } + } + + override public func awakeFromNib() { + super.awakeFromNib() + auth = AuthService.shared + } + + private func updateLoggedIn() { + embedded = nil + if let xib: String = loggedIn ? provider?.logoutXib : provider?.loginXib { + if let embedded: UIView = XibLoader.load(from: xib) { + self.embedded = embedded + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Auth/AuthLoginPrimerViewController.swift b/PlatformParticles/PlatformParticles/_Auth/AuthLoginPrimerViewController.swift new file mode 100644 index 000000000..51f95cf58 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Auth/AuthLoginPrimerViewController.swift @@ -0,0 +1,52 @@ +// +// AuthLoginPrimerViewController.swift +// PlatformParticles +// +// Created by Qiang Huang on 9/15/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import ParticlesKit +import PlatformRouting +import RoutingKit +import UIToolkits +import Utilities + +public class AuthLoginPrimerViewController: NavigableViewController { + @IBOutlet public var presenter: AuthLoginPresenter? { + didSet { + changeObservation(from: oldValue, to: presenter, keyPath: #keyPath(AuthLoginPresenter.loggedIn)) { [weak self] _, _, _, _ in + if let self = self { + self.loggedIn = self.presenter?.loggedIn ?? false + } + } + } + } + + public var loggedIn: Bool = false { + didSet { + if loggedIn != oldValue { + if loggedIn { + dismiss(nil) + } + } + } + } + + private var completion: RoutingCompletionBlock? + + override public func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if request?.path == "/login" { + routingRequest = request + self.completion = completion + } else { + completion?(nil, false) + } + } + + override public func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + completion?(nil, loggedIn) + completion = nil + } +} diff --git a/PlatformParticles/PlatformParticles/_Background/BackgroundTasksPoolInteractor.swift b/PlatformParticles/PlatformParticles/_Background/BackgroundTasksPoolInteractor.swift new file mode 100644 index 000000000..7be44c6b5 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Background/BackgroundTasksPoolInteractor.swift @@ -0,0 +1,40 @@ +// +// BackgroundTasksPoolInteractor.swift +// PlatformParticles +// +// Created by Qiang Huang on 8/5/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit + +internal class BackgroundTask: NSObject, ModelObjectProtocol { + var identifier: String + var progress: NSNumber? + + public init(identifier idText: String) { + identifier = idText + super.init() + } +} + +internal class BackgroundTasksPoolInteractor: DataPoolInteractor { + public var tasks: [String: BackgroundTask]? { + return data as? [String: BackgroundTask] + } + + public func add(task: BackgroundTask) { + willChangeValue(forKey: "data") + if data == nil { + data = [:] + } + data?[task.identifier] = task + didChangeValue(forKey: "data") + } + + public func remove(identifier: String) { + willChangeValue(forKey: "data") + data?[identifier] = nil + didChangeValue(forKey: "data") + } +} diff --git a/PlatformParticles/PlatformParticles/_Cache/JsonAppGroupFileCaching.swift b/PlatformParticles/PlatformParticles/_Cache/JsonAppGroupFileCaching.swift new file mode 100644 index 000000000..438dd741a --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Cache/JsonAppGroupFileCaching.swift @@ -0,0 +1,72 @@ +// +// JsonAppGroupFileCaching.swift +// PlatformParticles +// +// Created by Qiang Huang on 12/30/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +open class JsonAppGroupFileCaching: NSObject, JsonCachingProtocol { + @objc public dynamic var isLoading: Bool = false + + public var priority: Int = 0 + + public var debouncer: Debouncer = Debouncer() + public var groupUrl: URL? + public var document: JsonDocument? + + public func file(path: String) -> URL? { + return groupUrl?.appendingPathComponent(path).appendingPathComponent("data.json") + } + + public init(appGroup: String, priority: Int = 0) { + super.init() + groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) + self.priority = priority + } + + open func read(path: String, completion: @escaping JsonReadCompletionHandler) { + if let handler = debouncer.debounce() { + handler.run({ [weak self] in + if let self = self, let file = self.file(path: path), FileManager.default.fileExists(atPath: file.path) { + self.document = JsonDocument(fileURL: file) + self.document?.open(completionHandler: { [weak self] (success: Bool) -> Void in + if let self = self { + if success { + let data = self.document?.data + self.document = nil + completion(data, nil) + } else { + self.document = nil + completion(nil, nil) + } + } + }) + } else { + completion(nil, nil) + } + }, delay: 0.5) + } + } + + open func write(path: String, data: Any?, completion: JsonWriteCompletionHandler?) { + if let file = file(path: path) { + _ = Directory.ensure(file.deletingLastPathComponent().path) + let writeOption: UIDocument.SaveOperation = FileManager.default.fileExists(atPath: file.path) ? .forOverwriting : .forCreating + document = JsonDocument(fileURL: file) + document?.data = data + document?.save(to: file, for: writeOption, completionHandler: { (success: Bool) in + if success { + completion?(nil) + } else { + completion?(NSError(domain: "file", code: 0, userInfo: nil)) + } + }) + } else { + completion?(nil) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Cache/JsonDocument.swift b/PlatformParticles/PlatformParticles/_Cache/JsonDocument.swift new file mode 100644 index 000000000..565269205 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Cache/JsonDocument.swift @@ -0,0 +1,29 @@ +// +// JsonDocument.swift +// PlatformParticles +// +// Created by Qiang Huang on 12/30/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIKit + +open class JsonDocument: UIDocument { + public var data: Any? + + override open func contents(forType typeName: String) throws -> Any { + if let data = data { + return try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted) + } else { + return Data() + } + } + + override open func load(fromContents contents: Any, ofType typeName: String?) throws { + if let json = contents as? Data { + data = try? JSONSerialization.jsonObject(with: json, options: []) + } else { + data = nil + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Common/AppInfoPresenter.swift b/PlatformParticles/PlatformParticles/_Common/AppInfoPresenter.swift new file mode 100644 index 000000000..976d697b4 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Common/AppInfoPresenter.swift @@ -0,0 +1,36 @@ +// +// AppInfoPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 9/2/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits + +@objc public class AppInfoPresenter: ObjectPresenter { + public var appInfo: AppInfo? { + return model as? AppInfo + } + + public override var model: ModelObjectProtocol? { + didSet { + if appInfo !== oldValue { + update() + } + } + } + + @IBOutlet var versionLabel: LabelProtocol? { + didSet { + if versionLabel !== oldValue { + update() + } + } + } + + private func update() { + versionLabel?.text = appInfo?.version + } +} diff --git a/PlatformParticles/PlatformParticles/_Error/BannerErrorAlert.swift b/PlatformParticles/PlatformParticles/_Error/BannerErrorAlert.swift new file mode 100644 index 000000000..7d7c49feb --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Error/BannerErrorAlert.swift @@ -0,0 +1,101 @@ +// +// BannerErrorAlert.swift +// PlatformParticles +// +// Created by Qiang Huang on 1/21/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import AVFoundation +import ParticlesKit +import SwiftMessages +import UIToolkits +import Utilities +import Combine + +open class BannerErrorAlert: NSObject, ErrorInfoProtocol, CombineObserving { + public var cancellableMap = [AnyKeyPath : AnyCancellable]() + + public var appState: AppState? { + didSet { + didSetAppState(oldValue: oldValue) + } + } + + public var pending: ErrorInfoData? + + public func configuration(type: EInfoType?, time: Double?) -> SwiftMessages.Config { + var config = SwiftMessages.defaultConfig + config.presentationContext = .window(windowLevel: .statusBar) + if let time = time { + config.duration = .seconds(seconds: time) + } else { + config.duration = .forever + } + return config + } + + open func info(data: ErrorInfoData) { + if let messageView = view(title: data.title, message: data.message, type: data.type, error: data.error) { + if let actions = data.actions { + if actions.count == 1 { + messageView.button?.isHidden = false + if let action = actions.first { + messageView.button?.buttonTitle = action.text + messageView.buttonTapHandler = { _ in + SwiftMessages.hide() + action.action(messageView.button) + } + } + } else { + for action in actions { + let button = UIButton() + button.buttonTitle = action.text + button.addTarget(action, action: #selector(ErrorAction.action(_:))) + messageView.addSubview(button) + } + } + } + SwiftMessages.hide() + let config = configuration(type: data.type, time: data.time) + SwiftMessages.show(config: config, view: messageView) + if data.error != nil { + AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) + } + } + } + + private func view(title: String?, message: String?, type: EInfoType?, error: Error?) -> MessageView? { + let message = self.message(message: message, error: error) ?? error?.localizedDescription + if title != nil || message != nil { + let view = MessageView.viewFromNib(layout: .cardView) + let type = self.type(type: type, error: error) + switch type { + case .warning: + view.configureTheme(.warning) + + case .error: + view.configureTheme(.error) + + case .success: + view.configureTheme(.success) + + case .info: + fallthrough + case .wait: + fallthrough + default: + view.configureTheme(.info) + } + view.configureDropShadow() + view.configureContent(title: title ?? "", body: message ?? "") + view.button?.isHidden = true + return view + } + return nil + } + + public func clear() { + SwiftMessages.hide() + } +} diff --git a/PlatformParticles/PlatformParticles/_Error/ErrorAlert.swift b/PlatformParticles/PlatformParticles/_Error/ErrorAlert.swift new file mode 100644 index 000000000..7ec61095c --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Error/ErrorAlert.swift @@ -0,0 +1,45 @@ +// +// ErrorAlert.swift +// PlatformParticles +// +// Created by Qiang Huang on 9/9/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits +import Utilities +import Combine + +public class ErrorAlert: NSObject, ErrorInfoProtocol, CombineObserving { + public var cancellableMap = [AnyKeyPath : AnyCancellable]() + + public var pending: ErrorInfoData? + + public var appState: AppState? { + didSet { + didSetAppState(oldValue: oldValue) + } + } + + public func info(data: ErrorInfoData) { + if let prompter = PrompterFactory.shared?.prompter() { + prompter.set(title: data.title, message: self.message(message: data.message, error: data.error), style: .error) + var promptActions = [PrompterAction]() + if let actions = data.actions { + for action in actions { + promptActions.append(PrompterAction(title: action.text, style: .normal, selection: { + action.handler() + })) + } + promptActions.append(PrompterAction.cancel()) + } else { + promptActions.append(PrompterAction.cancel(title: "OK")) + } + prompter.prompt(promptActions) + } + } + + public func clear() { + } +} diff --git a/PlatformParticles/PlatformParticles/_Geofencing/Geofencing.swift b/PlatformParticles/PlatformParticles/_Geofencing/Geofencing.swift new file mode 100644 index 000000000..ccc0ef378 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Geofencing/Geofencing.swift @@ -0,0 +1,77 @@ +// +// Geofencing.swift +// PlatformParticles +// +// Created by Qiang Huang on 10/7/19. +// Copyright © 2019 Qiang Huang. All rights reserved. +// + +import ParticlesKit + +@objc public class Geofencing: DictionaryEntity { + @objc public dynamic var address: String? { + get { + return parser.asString(data?["address"]) + } + set { + willChangeValue(forKey: "address") + force.data?["address"] = newValue + didChangeValue(forKey: "address") + } + } + + @objc public dynamic var unit: String? { + get { + return parser.asString(data?["unit"]) + } + set { + willChangeValue(forKey: "unit") + force.data?["unit"] = newValue + didChangeValue(forKey: "unit") + } + } + + @objc public dynamic var lat: NSNumber? { + get { + return parser.asNumber(data?["lat"]) + } + set { + willChangeValue(forKey: "lat") + force.data?["lat"] = newValue + didChangeValue(forKey: "lat") + } + } + + @objc public dynamic var lng: NSNumber? { + get { + return parser.asNumber(data?["lng"]) + } + set { + willChangeValue(forKey: "lng") + force.data?["lng"] = newValue + didChangeValue(forKey: "lng") + } + } + + @objc public dynamic var radius: NSNumber? { + get { + return parser.asNumber(data?["radius"]) + } + set { + willChangeValue(forKey: "radius") + force.data?["radius"] = newValue + didChangeValue(forKey: "radius") + } + } + + @objc public dynamic var token: String? { + get { + return parser.asString(data?["token"]) + } + set { + willChangeValue(forKey: "token") + force.data?["token"] = newValue + didChangeValue(forKey: "token") + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Geofencing/GeofencingInteractor.swift b/PlatformParticles/PlatformParticles/_Geofencing/GeofencingInteractor.swift new file mode 100644 index 000000000..f269a7aaf --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Geofencing/GeofencingInteractor.swift @@ -0,0 +1,163 @@ +// +// GeofencingInteractor.swift +// PlatformParticles +// +// Created by Qiang Huang on 10/7/19. +// Copyright © 2019 Qiang Huang. All rights reserved. +// + +import CoreLocation +import ParticlesKit +import UserNotifications +import Utilities + +@objc public final class GeofencingInteractor: DataPoolInteractor, RegionMonitorProtocol { + @objc public var current: Set? + @objc public var radius: Double = 200 + @objc public var text: String = "" + + private var locationManager: CLLocationManager? + + public init(radius: Double, text: String) { + self.radius = radius + self.text = text + super.init() + locationManager = LocationProvider.shared?.locationManager + } + + public func monitor(lat: Double, lng: Double, callbackUrl: String?) { + if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) && fence(lat: lat, lng: lng) == nil { + let fence = Geofencing() + fence.lat = NSNumber(value: lat) + fence.lng = NSNumber(value: lng) + fence.radius = NSNumber(value: min(radius, locationManager?.maximumRegionMonitoringDistance ?? 1000.0)) + fence.token = UUID().uuidString + + let circular = CLCircularRegion(center: CLLocationCoordinate2D(latitude: lat, longitude: lng), radius: fence.radius?.doubleValue ?? 0.0, identifier: fence.token ?? "") + circular.notifyOnEntry = true + circular.notifyOnExit = false + locationManager?.startMonitoring(for: circular) + + UNUserNotificationCenter.current().getNotificationSettings {[weak self] settings in + if let self = self, settings.authorizationStatus == .authorized { + let content = UNMutableNotificationContent() + content.title = "Arrived" + content.body = self.text + if let callbackUrl = callbackUrl { + content.userInfo = ["link": callbackUrl] + } + +// let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false) + let trigger = UNLocationNotificationTrigger(region: circular, repeats: false) + let request = UNNotificationRequest(identifier: fence.token ?? "", + content: content, trigger: trigger) + UNUserNotificationCenter.current().add(request) { error in + if let error = error { + Console.shared.log("\(error)") + } + } + } + } + } + } + + public func clear() { + if let regions = locationManager?.monitoredRegions { + for region in regions { + locationManager?.stopMonitoring(for: region) + } + } + UNUserNotificationCenter.current().removeAllDeliveredNotifications() + UNUserNotificationCenter.current().removeAllPendingNotificationRequests() + data = nil + current = nil + } + + public func enter(lat: Double, lng: Double) { + if current?.first(where: { (point) -> Bool in + point.latitude == lat && point.longitude == lng + }) == nil { + let point = MapPoint(latitude: lat, longitude: lng) + if current == nil { + current = [point] + } else { + willChangeValue(forKey: "current") + current?.insert(MapPoint(latitude: lat, longitude: lng)) + didChangeValue(forKey: "current") + } + } + } + + public func exit(lat: Double, lng: Double) { + if let current = current { + for point in current { + if point.latitude == lat && point.longitude == lng { + self.current?.remove(point) + } + } + } + } + + public var fences: [String: Geofencing]? { + return data as? [String: Geofencing] + } + + public override init() { + super.init(key: "geofencing", default: nil) + } + + public override func createLoader() -> LoaderProtocol? { + return LoaderProvider.shared?.loader(tag: "geofencing", cache: self) + } + + public override func entity(from data: [String: Any]?) -> ModelObjectProtocol? { + if let token = parser.asString(data?["token"]) { + return fences?[token] + } + return nil + } + + private func fence(lat: Double, lng: Double) -> Geofencing? { + return fences?.first(where: { (arg0) -> Bool in + let (_, value) = arg0 + return value.lat?.doubleValue == lat && value.lng?.doubleValue == lng + })?.value + } + + private func fence(token: String) -> Geofencing? { + return fences?[token] + } + + private func add(fence: Geofencing) { + if let token = fence.token { + if data == nil { + data = [:] + } + data?[token] = fence + } + save() + } + + private func remove(fence: Geofencing) { + if let token = fence.token { + data?.removeValue(forKey: token) + } + save() + } + + private func remove(lat: Double, lng: Double) { + if let fence = fence(lat: lat, lng: lng) { + remove(fence: fence) + } + } + + private func remove(token: String) { + if let fence = fence(token: token) { + remove(fence: fence) + } + } + + public override func save() { + loader?.save(object: data) + } +} diff --git a/PlatformParticles/PlatformParticles/_Grid Presenter/GridPresenter+iOS.swift b/PlatformParticles/PlatformParticles/_Grid Presenter/GridPresenter+iOS.swift new file mode 100644 index 000000000..9065e8d7a --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Grid Presenter/GridPresenter+iOS.swift @@ -0,0 +1,15 @@ +// +// GridPresenter+iOS.swift +// PlatformParticles +// +// Created by Qiang Huang on 1/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit + +extension GridPresenter { + @objc open var icon: UIImage? { + return nil + } +} diff --git a/PlatformParticles/PlatformParticles/_Grid Presenter/NativeGridPresenter.swift b/PlatformParticles/PlatformParticles/_Grid Presenter/NativeGridPresenter.swift new file mode 100644 index 000000000..6b4c0c7ad --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Grid Presenter/NativeGridPresenter.swift @@ -0,0 +1,28 @@ +// +// NativeGridPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 1/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits + +open class NativeGridPresenter: GridPresenter { + @IBOutlet open var view: ViewProtocol? { + didSet { + updateVisibility() + } + } + + open override var visible: Bool? { + didSet { + updateVisibility() + } + } + + open func updateVisibility() { + view?.visible = visible ?? false + } +} diff --git a/PlatformParticles/PlatformParticles/_Grid Presenter/XibGridPresenter.swift b/PlatformParticles/PlatformParticles/_Grid Presenter/XibGridPresenter.swift new file mode 100644 index 000000000..d6d2cb212 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Grid Presenter/XibGridPresenter.swift @@ -0,0 +1,31 @@ +// +// XibGridPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 1/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits +import Utilities + +open class XibGridPresenter: NativeGridPresenter, XibPresenterProtocol { + public var xibCache: XibPresenterCache = XibPresenterCache() + + @IBInspectable public var xibMap: String? { + didSet { + xibCache.xibMap = xibMap + } + } + + public var xibRegister: XibRegisterProtocol? + + public func xib(object: ModelObjectProtocol?) -> String? { + if let xibFile = xibCache.xib(object: object) { + xibRegister?.registerXibIfNeeded(xibFile) + return xibFile + } + return nil + } +} diff --git a/PlatformParticles/PlatformParticles/_Grid Presenter/_Collection View/CollectionViewGridPresenter.swift b/PlatformParticles/PlatformParticles/_Grid Presenter/_Collection View/CollectionViewGridPresenter.swift new file mode 100644 index 000000000..458c1839c --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Grid Presenter/_Collection View/CollectionViewGridPresenter.swift @@ -0,0 +1,146 @@ +// +// CollectionViewGridPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 1/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits + +open class CollectionViewGridPresenter: XibGridPresenter, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { + public var width: Int { + return interactor?.width ?? 0 + } + + public var height: Int { + return interactor?.height ?? 0 + } + + @IBOutlet public var collectionView: UICollectionView? { + didSet { + if collectionView !== oldValue { + oldValue?.dataSource = nil + oldValue?.delegate = nil + collectionView?.dataSource = self + collectionView?.delegate = self + collectionViewXibRegister.collectionView = collectionView + if interactor != nil { + refresh(animated: true) + } + } + } + } + + override open var visible: Bool? { + didSet { + if visible != oldValue, let collectionView = collectionView { + UIView.animate(collectionView, type: .fade, direction: .none, duration: UIView.defaultAnimationDuration, animations: { + collectionView.isHidden = !(self.visible ?? false) + }, completion: nil) + } + } + } + + private var collectionViewXibRegister: CollectionViewXibRegister = CollectionViewXibRegister() + + override open var interactor: ModelGridProtocol? { + didSet { + refresh(animated: true) + } + } + + override open func awakeFromNib() { + super.awakeFromNib() + xibRegister = collectionViewXibRegister + } + + override open var title: String? { + return "Cards" + } + + override open var icon: UIImage? { + return UIImage.named("view_cards", bundles: Bundle.particles) + } + + override open func refresh(animated: Bool) { + if animated { + UIView.animate(collectionView, type: .fade, direction: .none, duration: UIView.defaultAnimationDuration, animations: { + self.collectionView?.reloadData() + }, completion: nil) + } else { + collectionView?.reloadData() + } + } + + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return (interactor?.width ?? 0) * (interactor?.height ?? 0) + } + + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + if let object = self.object(at: indexPath.row), let xib = xib(object: object) { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: xib, for: indexPath) + if let presenterCell = cell as? ObjectPresenterCollectionViewCell { + presenterCell.xib = xib + presenterCell.model = object + } + return cell + } + return UICollectionViewCell() + } + + open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if let cell = collectionView.cellForItem(at: indexPath) { + UserInteraction.shared.sender = cell + } + if let object = self.object(at: indexPath.row) { + let selection: SelectionHandlerProtocol = selectionHandler ?? SelectionHandler.standard + selection.select(object) + } + collectionView.deselectItem(at: indexPath, animated: true) + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let numberPerRow = width + if let flow = collectionViewLayout as? UICollectionViewFlowLayout, numberPerRow != 0 { + let viewSize = collectionView.frame.size + let usefulSize = CGSize(width: viewSize.width - flow.sectionInset.left - flow.sectionInset.right, height: viewSize.height - flow.sectionInset.top - flow.sectionInset.bottom) + + let itemWidth = (usefulSize.width + flow.minimumInteritemSpacing) / CGFloat(numberPerRow) - flow.minimumInteritemSpacing + return CGSize(width: itemWidth, height: itemWidth) + } + return CGSize.zero + } + + override open func change(to new: [[ModelObjectProtocol]]) { + super.change(to: new) + } + + override open func updateLayout() { + collectionView?.collectionViewLayout.invalidateLayout() + } + + private func y(index: Int) -> Int { + let width = self.width + let height = self.height + if width != 0 && height != 0 { + let y = index / width + return y < height ? y : 0 + } + return 0 + } + + private func x(index: Int) -> Int { + let width = self.width + let height = self.height + if width != 0 && height != 0 { + return index % width + } + return 0 + } + + private func object(at index: Int) -> ModelObjectProtocol? { + return object(x: x(index: index), y: y(index: index)) + } +} diff --git a/PlatformParticles/PlatformParticles/_Line Presenter/ObjectCompositeLinePresenter.swift b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectCompositeLinePresenter.swift new file mode 100644 index 000000000..0aeea4998 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectCompositeLinePresenter.swift @@ -0,0 +1,23 @@ +// +// ObjectCompositeLinePresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 6/14/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import ParticlesKit + +open class ObjectCompositeLinePresenter: ObjectLinePresenter { + @objc open dynamic var compositeLine: ObjectCompositeLineInteractor? { + return model as? ObjectCompositeLineInteractor + } + @IBOutlet var presenter1: ObjectValueLinePresenter? + @IBOutlet var presenter2: ObjectValueLinePresenter? + + open override func didSetModel(oldValue: ModelObjectProtocol?) { + super.didSetModel(oldValue: oldValue) + presenter1?.model = compositeLine?.interactor1 + presenter2?.model = compositeLine?.interactor2 + } +} diff --git a/PlatformParticles/PlatformParticles/_Line Presenter/ObjectEditLinePresenter.swift b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectEditLinePresenter.swift new file mode 100644 index 000000000..9cec0a513 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectEditLinePresenter.swift @@ -0,0 +1,50 @@ +// +// ObjectEditLinePresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 6/10/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import UIToolkits +import Utilities + +open class ObjectEditLinePresenter: ObjectValueLinePresenter { + @IBOutlet var textField: UITextField? { + didSet { + if textField !== oldValue { + oldValue?.removeTarget() + textField?.add(target: self, action: #selector(edit(_:)), for: UIControl.Event.editingChanged) + } + } + } + + private var editDebouncer = Debouncer() + + override open func didSetLineValue(oldValue: Any?) { + textField?.text = text + } + + override open func didSetTitle(oldValue: String?) { + if let titleLabel = titleLabel { + titleLabel.text = title + } else { + textField?.placeholder = title + } + } + + @IBAction func edit(_ sender: Any?) { + let handler = editDebouncer.debounce() + handler?.run({ [weak self] in + self?.reallyEdit() + }, delay: 0.5) + } + + open func reallyEdit() { + if let text = textField?.text, text != "" { + self.text = text + } else { + self.text = nil + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Line Presenter/ObjectLinePresenter.swift b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectLinePresenter.swift new file mode 100644 index 000000000..eae29d072 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectLinePresenter.swift @@ -0,0 +1,46 @@ +// +// ObjectLinePresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 6/10/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import ParticlesKit + +@objc open class ObjectLinePresenter: ObjectViewPresenter { + @IBOutlet var titleLabel: UILabel? + + public var line: ObjectLineInteractor? { + return model as? ObjectLineInteractor + } + + @objc open dynamic var title: String? { + didSet { + didSetTitle(oldValue: oldValue) + } + } + + @objc open dynamic var formatter: ValueFormatterProtocol? { + didSet { + didSetFormatter(oldValue: oldValue) + } + } + + open override func didSetModel(oldValue: ModelObjectProtocol?) { + super.didSetModel(oldValue: oldValue) + changeObservation(from: oldValue, to: line, keyPath: #keyPath(ObjectLineInteractor.title)) { [weak self] _, _, _, _ in + self?.title = self?.line?.title + } + changeObservation(from: oldValue, to: line, keyPath: #keyPath(ObjectLineInteractor.formatter)) { [weak self] _, _, _, _ in + self?.formatter = self?.line?.formatter + } + } + + open func didSetTitle(oldValue: String?) { + titleLabel?.text = title + } + + open func didSetFormatter(oldValue: ValueFormatterProtocol?) { + } +} diff --git a/PlatformParticles/PlatformParticles/_Line Presenter/ObjectOptionsLinePresenter.swift b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectOptionsLinePresenter.swift new file mode 100644 index 000000000..705b35514 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectOptionsLinePresenter.swift @@ -0,0 +1,54 @@ +// +// ObjectOptionsLinePresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 6/11/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits +import Utilities + +open class ObjectOptionsLinePresenter: ObjectValueLinePresenter { + private var optionsInteractor: ObjectOptionsLineInteractor? { + return valueLine as? ObjectOptionsLineInteractor + } + + @IBOutlet var selector: UISegmentedControl? { + didSet { + if selector !== oldValue { + oldValue?.removeTarget() + populateSelector() + selector?.add(target: self, action: #selector(option(_:)), for: UIControl.Event.valueChanged) + } + } + } + + open override func didSetModel(oldValue: ModelObjectProtocol?) { + super.didSetModel(oldValue: oldValue) + changeObservation(from: oldValue, to: optionsInteractor, keyPath: "strings") { [weak self] _, _, _, _ in + self?.populateSelector() + } + } + + private func populateSelector() { + selector?.removeAllSegments() + if let selector = selector, let strings = optionsInteractor?.strings { + for string in strings { + selector.insertSegment(withTitle: string, at: selector.numberOfSegments, animated: false) + } + } + didSetLineValue(oldValue: nil) + } + + override open func didSetLineValue(oldValue: Any?) { + if let index = optionsInteractor?.selectionIndex, selector?.selectedIndex != index { + selector?.selectedIndex = index + } + } + + @IBAction func option(_ sender: Any?) { + optionsInteractor?.selectionIndex = selector?.selectedIndex + } +} diff --git a/PlatformParticles/PlatformParticles/_Line Presenter/ObjectSwitchLinePresenter.swift b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectSwitchLinePresenter.swift new file mode 100644 index 000000000..bfa24c941 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectSwitchLinePresenter.swift @@ -0,0 +1,35 @@ +// +// ObjectSwitchLinePresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 6/10/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import UIToolkits +import Utilities + +open class ObjectSwitchLinePresenter: ObjectValueLinePresenter { + @IBOutlet var checkbox: UISwitch? { + didSet { + if checkbox !== oldValue { + oldValue?.removeTarget() + checkbox?.add(target: self, action: #selector(check(_:)), for: UIControl.Event.valueChanged) + } + } + } + + override open func didSetLineValue(oldValue: Any?) { + let on = (lineValue as? NSNumber)?.boolValue ?? false + if checkbox?.isOn != on { + checkbox?.isOn = on + } + } + + @IBAction func check(_ sender: Any?) { + if let isOn = checkbox?.isOn, let valueField = valueLine?.valueField { + let value = NSNumber(value: isOn) + obj?.setValue(value, forKey: valueField) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Line Presenter/ObjectTextLinePresenter.swift b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectTextLinePresenter.swift new file mode 100644 index 000000000..42ad9f8af --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectTextLinePresenter.swift @@ -0,0 +1,18 @@ +// +// ObjectTextLinePresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 6/10/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import UIToolkits +import Utilities + +open class ObjectTextLinePresenter: ObjectValueLinePresenter { + @IBOutlet var textLabel: UILabel? + + override open func didSetLineValue(oldValue: Any?) { + textLabel?.text = text + } +} diff --git a/PlatformParticles/PlatformParticles/_Line Presenter/ObjectValueLinePresenter.swift b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectValueLinePresenter.swift new file mode 100644 index 000000000..43e1e4bd1 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectValueLinePresenter.swift @@ -0,0 +1,70 @@ +// +// ObjectValueLinePresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 6/10/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import ParticlesKit + +open class ObjectValueLinePresenter: ObjectLinePresenter { + @objc open dynamic var valueLine: ObjectValueLineInteractor? { + return model as? ObjectValueLineInteractor + } + + @objc open dynamic var obj: NSObject? { + didSet { + didSetObj(oldValue: oldValue) + } + } + + @objc open dynamic var lineValue: Any? { + didSet { + didSetLineValue(oldValue: oldValue) + } + } + + open var text: String? { + get { + if let lineValue = lineValue { + if let text = formatter?.text(value: lineValue) { + return text + } else { + return parser.asString(lineValue) + } + } else { + return nil + } + } + set { + if let obj = valueLine?.obj2, let valueField = valueLine?.valueField { + if let newValue = newValue { + if let value = formatter?.value(text: newValue) { + obj.setValue(value, forKey: valueField) + } else { + obj.setValue(newValue, forKey: valueField) + } + } else { + obj.setValue(nil, forKey: valueField) + } + } + } + } + + override open func didSetModel(oldValue: ModelObjectProtocol?) { + super.didSetModel(oldValue: oldValue) + changeObservation(from: oldValue, to: valueLine, keyPath: #keyPath(ObjectValueLineInteractor.obj2)) { [weak self] _, _, _, _ in + self?.obj = self?.valueLine?.obj + } + changeObservation(from: oldValue, to: valueLine, keyPath: #keyPath(ObjectValueLineInteractor.lineValue)) { [weak self] _, _, _, _ in + self?.lineValue = self?.valueLine?.lineValue + } + } + + open func didSetObj(oldValue: NSObject?) { + } + + open func didSetLineValue(oldValue: Any?) { + } +} diff --git a/PlatformParticles/PlatformParticles/_Line Presenter/ObjectWebLinePresenter.swift b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectWebLinePresenter.swift new file mode 100644 index 000000000..554877a59 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Line Presenter/ObjectWebLinePresenter.swift @@ -0,0 +1,23 @@ +// +// ObjectWebLinePresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 6/24/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities +import WebKit + +public class ObjectWebLinePresenter: ObjectValueLinePresenter { + @IBOutlet var webView: WKWebView? + + override open func didSetLineValue(oldValue: Any?) { + if let urlString = text, let url = URL(string: urlString) { + webView?.load(URLRequest(url: url)) + } else if let url = URL(string: "blank") { + webView?.load(URLRequest(url: url)) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/ListPresenter+iOS.swift b/PlatformParticles/PlatformParticles/_List Presenter/ListPresenter+iOS.swift new file mode 100644 index 000000000..a3b888f26 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/ListPresenter+iOS.swift @@ -0,0 +1,15 @@ +// +// ListPresenter+iOS.swift +// PlatformParticles +// +// Created by Qiang Huang on 12/6/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit + +extension ListPresenter { + @objc open var icon: UIImage? { + return nil + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/NativeListPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/NativeListPresenter.swift new file mode 100644 index 000000000..3687879b7 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/NativeListPresenter.swift @@ -0,0 +1,197 @@ +// +// NativeListPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 12/21/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Differ +import ParticlesCommonModels +import ParticlesKit +import UIToolkits +import Utilities + +open class NativeListPresenter: ListPresenter { + let kDebouncerDelay: Double = 0.0523 + @IBOutlet open var view: ViewProtocol? { + didSet { + updateVisibility() + } + } + + override open var visible: Bool { + didSet { + updateVisibility() + } + } + + @IBOutlet open var nullView: ViewProtocol? { + didSet { + nullView?.visible = true + } + } + + @IBOutlet open var loadingView: ViewProtocol? { + didSet { + loadingView?.visible = false + } + } + + @IBOutlet open var loadingSpinner: SpinnerProtocol? { + didSet { + loadingSpinner?.spinning = false + } + } + + private var browsingListInteractor: BrowsingListInteractor? { + didSet { + didSetBrowsingListInteractor(oldValue: oldValue) + } + } + + @objc public dynamic var isReady: Bool = true { + didSet { + didSetIsReady(oldValue: oldValue) + } + } + + public var updateDebouncer = Debouncer() + + override open func didSetCurrent(oldValue: [ModelObjectProtocol]?) { + super.didSetCurrent(oldValue: oldValue) + nullView?.visible = (current?.count ?? 0 == 0) + } + + override open func didSetInteractor(oldValue: ListInteractor?) { + super.didSetInteractor(oldValue: oldValue) + browsingListInteractor = interactor as? BrowsingListInteractor + } + + open func didSetBrowsingListInteractor(oldValue: BrowsingListInteractor?) { + changeObservation(from: oldValue, to: browsingListInteractor, keyPath: #keyPath(BrowsingListInteractor.isReady)) { [weak self] _, _, _, _ in + self?.isReady = self?.browsingListInteractor?.isReady ?? true + } + } + + open func didSetIsReady(oldValue: Bool) { + loadingView?.visible = !isReady + loadingSpinner?.spinning = !isReady + } + + open func updateVisibility() { + view?.visible = visible + } + + override open func update(move: Bool) { + if let handler = updateDebouncer.debounce() { + let pending = self.pending + let current = self.current + if let pending = pending, let current = current { + if move { + var diff: ExtendedDiff? + handler.run(background: { [weak self] in + if let self = self { + diff = self.extendedDiff(pending: pending, current: current) + } + }, final: { [weak self] in + if let self = self { + if let diff = diff { + if diff.elements.count > 0 { + self.update(diff: diff) { [weak self] in + self?.current = pending + } + self.updateCompleted(firstContent: false) + } + } else { + self.current = pending + self.refresh(animated: false) { [weak self] in + self?.updateCompleted(firstContent: false) + } + } + } + }, delay: kDebouncerDelay) + } else { + var diff: Diff? + var patches: [Patch]? + handler.run(background: { [weak self] in + if let self = self { + diff = self.diff(pending: pending, current: current) + } + }, then: { [weak self] in + if let self = self, let diff = diff { + patches = self.patches(diff: diff, pending: pending, current: current) + } + }, final: { [weak self] in + if let self = self { + self.current = pending + if let patches = patches, let diff = diff { + self.update(diff: diff, patches: patches, current: current) + self.updateCompleted(firstContent: false) + } else { + self.refresh(animated: false) { [weak self] in + self?.updateCompleted(firstContent: false) + } + } + } + }, delay: kDebouncerDelay) + } + } else { + handler.cancel() + self.current = pending + refresh(animated: false) { [weak self] in + self?.updateCompleted(firstContent: true) + } + } + } + } + + open func refresh(animated: Bool, completion: (() -> Void)?) { + completion?() + } + + open func updateCompleted(firstContent: Bool) { + } + + open func diff(pending: [ModelObjectProtocol]?, current: [ModelObjectProtocol]?) -> Diff? { + if let pending = pending, let current = current { + return current.diff(pending) { (object1, object2) -> Bool in + object1 === object2 + } + } + return nil + } + + open func extendedDiff(pending: [ModelObjectProtocol]?, current: [ModelObjectProtocol]?) -> ExtendedDiff? { + if let pending = pending, let current = current { + return current.extendedDiff(pending) { (object1, object2) -> Bool in + object1 === object2 + } + } + return nil + } + + open func patches(diff: Diff?, pending: [ModelObjectProtocol]?, current: [ModelObjectProtocol]?) -> [Patch]? { + if let diff = diff, let pending = pending, let current = current { + return diff.patch(from: current, to: pending) { (element1, element2) -> Bool in + switch (element1, element2) { + case let (.insert(at1), .insert(at2)): + return at1 < at2 + case (.insert, .delete): + return false + case (.delete, .insert): + return true + case let (.delete(at1), .delete(at2)): + return at1 > at2 + } + } + } + return nil + } + + open func update(diff: Diff, patches: [Patch], current: [ModelObjectProtocol]?) { + } + + open func update(diff: ExtendedDiff, updateData: () -> Void) { + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/XibListPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/XibListPresenter.swift new file mode 100644 index 000000000..1a882d453 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/XibListPresenter.swift @@ -0,0 +1,128 @@ +// +// XibListPresenter.swift +// PresenterLib +// +// Created by Qiang Huang on 10/9/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits +import Utilities + +open class XibListPresenter: NativeListPresenter, XibPresenterProtocol { + @IBOutlet open var selector: SegmentedProtocol? { + didSet { + if selector !== oldValue { + oldValue?.removeTarget() + selector?.add(target: self, action: #selector(scroll(_:)), for: .valueChanged) + } + } + } + + public var syncingSelector: Bool = false + + public var xibCache: XibPresenterCache = XibPresenterCache() + + @IBInspectable public var xibMap: String? { + didSet { + xibCache.xibMap = xibMap + } + } + + public var xibRegister: XibRegisterProtocol? + public var headerXibRegister: XibRegisterProtocol? + + public func xib(object: ModelObjectProtocol?) -> String? { + if let xibFile = xibCache.xib(object: object) { + xibRegister?.registerXibIfNeeded(xibFile) + return xibFile + } + return nil + } + + public func headerXib(object: ModelObjectProtocol?) -> String? { + if let xibFile = xibCache.xib(object: object) { + headerXibRegister?.registerXibIfNeeded(xibFile) + return xibFile + } + return nil + } + + public func defaultSize(at index: Int) -> CGSize? { + if let object = self.object(at: index), let xib = self.xib(object: object) { + return defaultSize(xib: xib) + } + return nil + } + + open override func updateCompleted(firstContent: Bool) { + syncSelector() + } + + open func syncSelector() { + if let selector = selector { + if let titles = current?.map({ obj -> String in + if let title = obj.displayTitle { + return title ?? "-" + } else { + return "-" + } + }) { + selector.fill(titles: titles) +// DispatchQueue.main.async {[weak self] in +// self?.selectCurrent() +// } + } else { + selector.fill(titles: nil) + } + } + } + + // get current visible item and set the selector index + open func selectCurrent() { + if !syncingSelector, let selector = selector, let visibleIndice = visibleIndice(), selector.selectedIndex != visibleIndice.first { + syncingSelector = true + scrollSelectTo(index: visibleIndice.first) { [weak self] in + self?.syncingSelector = false + } + } + } + + // scroll to item when selector index changed + @IBAction open func scroll(_ sender: Any?) { + if !syncingSelector, let index = selector?.selectedIndex { + syncingSelector = true + selectScrollTo(index: index) { [weak self] in + self?.syncingSelector = false + } + } + } + + open func isVisible(index: Int?, in indice: [Int]?) -> Bool { + if let indice = indice, indice.count > 0 { + if let index = index { + return indice.contains(index) + } else { + return false + } + } else { + return true + } + } + + open func visibleIndice() -> [Int]? { + return nil + } + + open func selectScrollTo(index: Int, completion: @escaping () -> Void) { + completion() + } + + open func scrollSelectTo(index: Int?, completion: @escaping () -> Void) { + if let index = index { + selector?.selectedIndex = index + } + completion() + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Calendar/CalendarViewListPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Calendar/CalendarViewListPresenter.swift new file mode 100644 index 000000000..ea439572b --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Calendar/CalendarViewListPresenter.swift @@ -0,0 +1,196 @@ +// +// CalendarViewListPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 12/21/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import JTCalendar +import ParticlesKit + +open class CalendarViewListPresenter: NativeListPresenter, JTCalendarDelegate { + open var dates: [Date: [DateModelObjectProtocol]]? { + didSet { + manager?.reload() + updateSelected() + } + } + + private var selected: Date? { + didSet { + if selected != oldValue { + updateSelected() + } + } + } + + @IBOutlet public var menu: JTCalendarMenuView? { + didSet { + if menu !== oldValue { + updateManager() + } + } + } + + @IBOutlet public var calendar: JTHorizontalCalendarView? { + didSet { + if calendar !== oldValue { + updateManager() + } + } + } + + @IBOutlet public var selectedListPresenter: ListPresenter? { + didSet { + selectedListPresenter?.visible = true + updateSelected() + } + } + + public var manager: JTCalendarManager? { + didSet { + if manager !== oldValue { + oldValue?.delegate = nil + manager?.delegate = self + } + } + } + + override open var title: String? { + return "Calendar" + } + + override open var icon: UIImage? { + return UIImage.named("view_calendar", bundles: Bundle.particles) + } + + private func updateManager() { + if let menu = menu, let calendar = calendar { + manager = JTCalendarManager() + manager?.menuView = menu + manager?.contentView = calendar + let now = DateService.shared?.now() ?? Date() + manager?.setDate(now) + } else { + manager = nil + } + } + + @objc(calendar:prepareDayView:) + public func calendar(_ calendar: JTCalendarManager?, prepareDayView dayView: (UIView & JTCalendarDay)?) { + if let dayView = dayView, let day = dayView.date() { + let hasData = (dates?[day] != nil) + let selectedMonth = !dayView.isFromAnotherMonth() + let now = DateService.shared?.now() ?? Date() + let today = manager?.dateHelper?.date(now, isTheSameDayThan: day) ?? false + let selected = manager?.dateHelper?.date(self.selected, isTheSameDayThan: day) ?? false + if today && self.selected == nil { + self.selected = day + } + + update(dayView: dayView as! JTCalendarDayView, today: today, selectedMonth: selectedMonth, selected: selected, hasData: hasData) + } + } + + @objc func update(dayView: JTCalendarDayView, today: Bool, selectedMonth: Bool, selected: Bool, hasData: Bool) { + /* Appearance we can set: + textLabel: font and color + circleView: visibility and background color + dotView: visibility and background color + */ + /* + Use dot to indicate whether there is data + Use circle to indicate selected date or today + Use font color to indicate whether it is in the selected month + */ + + let showCircle = selected || today + + dayView.dotView.isHidden = !hasData + dayView.dotView.backgroundColor = showCircle ? UIColor.white : UIColor.orange + + dayView.circleView.isHidden = !showCircle + if showCircle { + if today { + dayView.circleView.backgroundColor = UIColor.blue + } else { + dayView.circleView.backgroundColor = UIColor.red + } + } + + if showCircle { + dayView.textLabel.textColor = selectedMonth ? UIColor.white : UIColor.lightGray + } else { + dayView.textLabel.textColor = selectedMonth ? UIColor.black : UIColor.gray + } + } + + @objc(calendar:didTouchDayView:) + public func calendar(_ calendar: JTCalendarManager?, didTouchDayView dayView: (UIView & JTCalendarDay)?) { + if let dayView = dayView as? JTCalendarDayView { + // Use to indicate the selected date + selected = dayView.date() + if let _ = selected { + // Animation for the circleView + dayView.circleView.transform = + CGAffineTransform.identity.scaledBy(x: 0.1, y: 0.1) + UIView.transition(with: dayView, duration: 0.3, options: [], animations: { + dayView.circleView.transform = CGAffineTransform.identity + self.manager?.reload() + }) + + // Load the previous or next page if touch a day from another month + if let manager = manager, let date = calendar?.date(), let dayViewDate = dayView.date { + if !(manager.dateHelper?.date(date, isTheSameMonthThan: dayView.date()) ?? false) { + if calendar?.date().compare(dayViewDate) == .orderedAscending { + self.calendar?.loadNextPageWithAnimation() + } else { + self.calendar?.loadPreviousPageWithAnimation() + } + } + } + } + } + } + + override open func update() { + current = pending + refresh(animated: true, completion: nil) + } + + override open func refresh(animated: Bool, completion: (() -> Void)?) { + update(with: current) + completion?() + } + + open func update(with objects: [ModelObjectProtocol]?) { + if let objects = objects { + var dates: [Date: [DateModelObjectProtocol]] = [:] + for item in objects { + if let dateItem = item as? (DateModelObjectProtocol), let date = dateItem.date { + var list = dates[date] + if list == nil { + list = [] + } + list?.append(dateItem) + dates[date] = list + } + } + self.dates = dates + } else { + dates = nil + } + } + + open func updateSelected() { + if let selected = selected { + let items = dates?[selected] + let interactor = ListInteractor() + interactor.list = items + selectedListPresenter?.interactor = interactor + } else { + selectedListPresenter?.interactor = nil + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Carousel/CarouselListPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Carousel/CarouselListPresenter.swift new file mode 100644 index 000000000..3204ac857 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Carousel/CarouselListPresenter.swift @@ -0,0 +1,135 @@ +// +// CarouselListPresenter.swift +// PresenterLib +// +// Created by Qiang Huang on 11/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Differ +import iCarousel +import ParticlesKit +import UIToolkits +import Utilities + +open class CarouselListPresenter: XibListPresenter, iCarouselDataSource, iCarouselDelegate { + @IBOutlet var carousel: iCarousel? { + didSet { + if carousel !== oldValue { + oldValue?.dataSource = nil + oldValue?.delegate = nil + carousel?.dataSource = self + carousel?.delegate = self + carousel?.type = type + } + } + } + + var type: iCarouselType = .coverFlow { + didSet { + carousel?.type = type + } + } + + @IBInspectable var intType: Int { + get { return type.rawValue } + set { type = iCarouselType(rawValue: newValue) ?? .linear } + } + + @IBInspectable var margin: CGFloat = 0.0 + @IBInspectable var proportional: Bool = false + + override open var title: String? { + return "Carousel" + } + + override open var icon: UIImage? { + return UIImage.named("view_carousel", bundles: Bundle.particles) + } + + public func numberOfItems(in carousel: iCarousel) -> Int { + return interactor?.list?.count ?? 0 + } + + public func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView { + let object = self.object(at: index) + if let xib = xib(object: object) { + if let loadedView: ObjectPresenterView = XibLoader.load(from: xib) { + let itemWidth: CGFloat = width(carousel: carousel) + if proportional { + loadedView.frame = CGRect(x: 0, y: 0, width: itemWidth, height: loadedView.frame.size.height / loadedView.frame.size.width * itemWidth) + } else { + loadedView.frame = CGRect(x: 0, y: 0, width: itemWidth, height: loadedView.frame.size.height) + } + loadedView.model = object + return loadedView + } + } + return UIView() + } + + public func carouselDidEndScrollingAnimation(_ carousel: iCarousel) { + selectCurrent() + } + + public func carousel(_ carousel: iCarousel, didSelectItemAt index: Int) { + select(index: index, completion: nil) + } + + open func width(carousel: iCarousel) -> CGFloat { + return carousel.bounds.size.width - margin * 2 + } + + override open func update() { + if carousel != nil { + update(move: false) + } else { + current = pending + } + } + + override open func update(diff: Diff, patches: [Patch], current: [ModelObjectProtocol]?) { + for change in patches { + switch change { + case let .deletion(index): + carousel?.removeItem(at: index, animated: true) + + case let .insertion(index: index, element: _): + carousel?.insertItem(at: index, animated: true) + } + } + } + + override open func refresh(animated: Bool, completion: (() -> Void)?) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { [weak self] in + if let self = self { + self.carousel?.reloadData() + completion?() + } + } + } + + public func scroll(to index: Int) { + carousel?.scrollToItem(at: index, animated: true) + } + + override open func updateLayout() { + refresh(animated: true, completion: nil) + } + + override open func visibleIndice() -> [Int]? { + if (interactor?.list?.count ?? 0) > 0 { + if let index = carousel?.currentItemIndex { + return (index != -1) ? [index] : nil + } + } + return nil + } + + override open func selectScrollTo(index: Int, completion: @escaping () -> Void) { + scroll(to: index) + DispatchQueue.main.asyncAfter(deadline: .now() + UIView.defaultAnimationDuration) { + completion() + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Collection/CollectionViewListPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Collection/CollectionViewListPresenter.swift new file mode 100644 index 000000000..9a7261f6b --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Collection/CollectionViewListPresenter.swift @@ -0,0 +1,297 @@ +// +// CollectionViewListPresenter.swift +// PresenterLib +// +// Created by Qiang Huang on 10/12/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Differ +import ParticlesKit +import UIToolkits + +open class CollectionViewListPresenter: XibListPresenter, UICollectionViewDataSource, UICollectionViewDelegate, ScrollingProtocol { + @IBInspectable @objc public dynamic var autoScroll: Bool = false + @objc public dynamic var isAtEnd: Bool = true + + @IBInspectable var sectionHeaderXib: String? + @IBInspectable var sectionFooterXib: String? + @IBOutlet @objc open dynamic var collectionView: UICollectionView? { + didSet { + if collectionView !== oldValue { + oldValue?.dataSource = nil + oldValue?.delegate = nil + collectionView?.dataSource = self + collectionView?.delegate = self + collectionViewXibRegister.collectionView = collectionView + setupLayout() + for section in sections { + section.collectionView = collectionView + section.selectionHandler = selectionHandler + } + if interactor != nil { + refresh(animated: false) { [weak self] in + self?.updateCompleted(firstContent: true) + } + } + } + } + } + + override open var selectionHandler: SelectionHandlerProtocol? { + didSet { + if selectionHandler !== oldValue { + for section in sections { + section.collectionView = collectionView + section.selectionHandler = selectionHandler + } + } + } + } + + internal var flowLayout: UICollectionViewFlowLayout? { + return collectionView?.collectionViewLayout as? UICollectionViewFlowLayout + } + + @IBInspectable var intMode: Int { + get { return mode.rawValue } + set { mode = PresenterMode(rawValue: newValue) ?? .linear } + } + + public var mode: PresenterMode = .linear { + didSet { + if mode != oldValue { + if let handler = updateDebouncer.debounce() { + handler.run({ [weak self] in + if let self = self { + self.current = self.pending + self.refresh(animated: true) { [weak self] in + self?.updateCompleted(firstContent: false) + } + } + }, delay: nil) + } + } + } + } + + public var initialPosition: Int? + + private var collectionViewXibRegister: CollectionViewXibRegister = CollectionViewXibRegister() + public var sections: [CollectionViewSectionListPresenter] = [] + @objc override open dynamic var current: [ModelObjectProtocol]? { + didSet { + switch mode { + case .sections: + if let list = current as? [ListInteractor] { + var index = 0 + self.sections = list.map({ (child: ListInteractor) -> CollectionViewSectionListPresenter in + let tableSection = section(for: child, index: index) + index += 1 + return tableSection + }) + } + + case .linear: + self.sections.first?.current = current + } + } + } + + override open var interactor: ListInteractor? { + didSet { + switch mode { + case .linear: + sections.removeAll() + if let interactor = interactor { + sections.append(section(for: interactor, index: 0)) + } + refresh(animated: false) { [weak self] in + self?.updateCompleted(firstContent: true) + } + + default: + break + } + } + } + + override open var title: String? { + return "Cards" + } + + override open var icon: UIImage? { + return UIImage.named("view_cards", bundles: Bundle.particles) + } + + open func setupLayout() { + if #available(iOS 11.0, *) { + flowLayout?.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + } + } + + internal func section(for interactor: ListInteractor, index: Int) -> CollectionViewSectionListPresenter { + let section = CollectionViewSectionListPresenter() + section.collectionView = collectionView + section.xibRegister = collectionViewXibRegister + section.xibMap = xibMap + section.sequence = index + section.interactor = interactor + section.selectionHandler = selectionHandler + section.sectionHeaderXib = sectionHeaderXib + section.sectionFooterXib = sectionFooterXib + return section + } + + override open func update() { + if collectionView != nil { + if mode == .sections { + current = pending + refresh(animated: true, completion: nil) + } else { + update(move: true) + } + } else { + current = pending + } + } + + override open func update(diff: Diff, patches: [Patch], current: [ModelObjectProtocol]?) { + collectionView?.performBatchUpdates({ + for change in patches { + switch change { + case let .deletion(index): + collectionView?.deleteSections(IndexSet(integer: index)) + + case let .insertion(index: index, element: _): + collectionView?.insertSections(IndexSet(integer: index)) + } + } + }, completion: nil) + } + + override open func refresh(animated: Bool, completion: (() -> Void)?) { + UIView.animate(collectionView, type: animated ? .fade : .none, direction: .none, duration: UIView.defaultAnimationDuration, animations: { [weak self] in + if let self = self { + self.reloadData() + if self.autoScroll { + self.scrollToEnd(animated: false) + } else if let index = self.initialPosition { +// self.reallyScrollTo(index: index, animated: false) +// self.initialPosition = nil + } + completion?() + } + }, completion: nil) + } + + open func reloadData() { + collectionView?.reloadData() + } + + public func numberOfSections(in collectionView: UICollectionView) -> Int { + if interactor == nil { + return 0 + } else { + return mode == .sections ? sections.count : 1 + } + } + + open func object(indexPath: IndexPath) -> ModelObjectProtocol? { + switch mode { + case .linear: + return sections.first?.object(at: indexPath.row) + + case .sections: + return sections.at(indexPath.section)?.object(at: indexPath.row) + } + } + + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + if mode == .sections { + let sectionPresenter = sections[section] + return sectionPresenter.count ?? 0 + } else { + return sections.first?.count ?? 0 + } + } + + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + if let section = (mode == .sections) ? sections[indexPath.section] : sections.first { + return section.cell(indexPath: indexPath) ?? UICollectionViewCell() + } else { + return UICollectionViewCell() + } + } + + open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if let cell = collectionView.cellForItem(at: indexPath) { + UserInteraction.shared.sender = cell + } + if let section = (mode == .sections) ? sections[indexPath.section] : sections.first { + section.navigate(to: indexPath) + } + collectionView.deselectItem(at: indexPath, animated: true) + } + + override open func updateLayout() { + collectionView?.collectionViewLayout.invalidateLayout() + } + + private func lastIndexPath() -> IndexPath? { + if let lastIndex = sections.lastIndex(where: { (section) -> Bool in + (section.current?.count ?? 0) > 0 + }), let last = sections.at(lastIndex), let count = last.current?.count, count > 0 { + return IndexPath(item: count - 1, section: lastIndex) + } else { + return nil + } + } + + public func scrollToEnd(animated: Bool) { + if animated { + DispatchQueue.main.async { [weak self] in + if let self = self { + if let indexPath = self.lastIndexPath() { + self.collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: animated) + } + } + } + } else { + if let indexPath = lastIndexPath() { + collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: animated) + scrollToEnd(animated: true) + } + } + } + + override open func selectScrollTo(index: Int, completion: @escaping () -> Void) { + if current?.count ?? 0 > index { + reallyScrollTo(index: index, animated: true) + DispatchQueue.main.asyncAfter(deadline: .now() + UIView.defaultAnimationDuration) { + completion() + } + } else { + initialPosition = index + completion() + } + } + + private func reallyScrollTo(index: Int, animated: Bool) { + if mode == .sections { + collectionView?.scrollToItem(at: IndexPath(row: NSNotFound, section: index), at: .left, animated: animated) + } else { + collectionView?.scrollToItem(at: IndexPath(row: index, section: 0), at: .centeredHorizontally, animated: animated) + } + } + + public func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) { + let cell = collectionView.cellForItem(at: indexPath) + (cell as? ObjectPresenterCollectionViewCell)?.isCellHighlighted = true + } + + public func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) { + let cell = collectionView.cellForItem(at: indexPath) + (cell as? ObjectPresenterCollectionViewCell)?.isCellHighlighted = false + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Collection/CollectionViewSectionListPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Collection/CollectionViewSectionListPresenter.swift new file mode 100644 index 000000000..90436c871 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Collection/CollectionViewSectionListPresenter.swift @@ -0,0 +1,139 @@ +// +// CollectionViewSectionListPresenter.swift +// PresenterLib +// +// Created by Qiang Huang on 10/12/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Differ +import ParticlesKit +import UIKit +import UIToolkits +import Utilities + +open class CollectionViewSectionListPresenter: XibListPresenter { + @IBOutlet var collectionView: UICollectionView? + @IBInspectable var sectionHeaderXib: String? + @IBInspectable var sectionFooterXib: String? + + override open func update() { + if collectionView != nil { + update(move: true) + } else { + current = pending + } + } + + override open func update(diff: ExtendedDiff, updateData: () -> Void) { + if let collectionView = collectionView, + sequence < collectionView.numberOfSections { + collectionView.apply(diff, updateData: { + updateData() + }, completion: nil, indexPathTransform: { (indexPath) -> IndexPath in + IndexPath(row: indexPath.row, section: self.sequence) + }) + } else { + updateData() + } + } + + override open func refresh(animated: Bool, completion: (() -> Void)?) { + if animated { + UIView.animate(collectionView, type: .fade, direction: .none, duration: UIView.defaultAnimationDuration, animations: { [weak self] in + if let self = self { + self.collectionView?.reloadData() + completion?() + } + }, completion: nil) + } else { + collectionView?.reloadData() + completion?() + } + } + + open func cell(indexPath: IndexPath) -> UICollectionViewCell? { + let object = self.object(at: indexPath.row) + return cell(object: object, indexPath: indexPath) + } + + open func cell(object: ModelObjectProtocol?, indexPath: IndexPath) -> UICollectionViewCell? { + if let xib = xib(object: object), let collectionView = collectionView { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: xib, for: indexPath) + if let presenterCell = cell as? ObjectPresenterCollectionViewCell { + presenterCell.xib = xib + presenterCell.model = object + } + return cell + } + return nil + } + + open func navigate(to indexPath: IndexPath) { + select(index: indexPath.row, completion: nil) + } + + open func size(at index: Int, layout: UICollectionViewFlowLayout, style: CardStyle, proportional: Bool) -> CGSize { + if let collectionView = collectionView { + let viewSize = collectionView.frame.size + let usefulSize = CGSize(width: viewSize.width - layout.sectionInset.left - layout.sectionInset.right, height: viewSize.height - layout.sectionInset.top - layout.sectionInset.bottom) + switch style { + case .full: + return usefulSize + + case .width: + if let size = defaultSize(at: index) { + if layout.scrollDirection == .horizontal { + return adjust(size: size, height: usefulSize.height, proportional: proportional) + } else { + return adjust(size: size, width: usefulSize.width, proportional: proportional) + } + } + break + + default: + if let size = defaultSize(at: index) { + if layout.scrollDirection == .horizontal { + var rows = Int((usefulSize.height + layout.minimumInteritemSpacing) / (size.height + layout.minimumInteritemSpacing)) + if rows == 0 { + rows = 1 + } + return adjust(size: size, height: (usefulSize.height + layout.minimumInteritemSpacing) / CGFloat(rows) - layout.minimumInteritemSpacing - 1.0, proportional: proportional) + } else { + var columns = Int((usefulSize.width + layout.minimumInteritemSpacing) / (size.width + layout.minimumInteritemSpacing)) + if columns == 0 { + columns = 1 + } + return adjust(size: size, width: (usefulSize.width + layout.minimumInteritemSpacing) / CGFloat(columns) - layout.minimumInteritemSpacing - 1.0, proportional: proportional) + } + } + break + } + } + return CGSize.zero + } + + private func adjust(size: CGSize, width: CGFloat, proportional: Bool) -> CGSize { + if proportional { + if size.width != 0 { + return CGSize(width: width, height: width * size.height / size.width) + } else { + return CGSize.zero + } + } else { + return CGSize(width: width, height: size.height) + } + } + + private func adjust(size: CGSize, height: CGFloat, proportional: Bool) -> CGSize { + if proportional { + if size.height != 0 { + return CGSize(width: height * size.width / size.height, height: height) + } else { + return CGSize.zero + } + } else { + return CGSize(width: size.width, height: height) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Collection/CollectionViewXibRegister.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Collection/CollectionViewXibRegister.swift new file mode 100644 index 000000000..d702322d8 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Collection/CollectionViewXibRegister.swift @@ -0,0 +1,22 @@ +// +// CollectionViewXibRegister.swift +// PresenterLib +// +// Created by Qiang Huang on 10/12/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +internal class CollectionViewXibRegister: NSObject & XibRegisterProtocol { + private let kCellName = "CollectionViewCell" + internal weak var collectionView: UICollectionView? + internal var registeredXibs: Set = [] + + internal func register(xib: String) { + if let nib = UINib.safeLoad(xib: kCellName, bundles: Bundle.particles) { + collectionView?.register(nib, forCellWithReuseIdentifier: xib) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Collection/RigidCollectionViewListPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Collection/RigidCollectionViewListPresenter.swift new file mode 100644 index 000000000..33a6bdbf0 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Collection/RigidCollectionViewListPresenter.swift @@ -0,0 +1,144 @@ +// +// RigidCollectionViewListPresenter.swift +// PresenterLib +// +// Created by Qiang Huang on 10/12/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import UIKit + +public enum CardStyle: Int { + case full + case width + case auto +} + +open class RigidCollectionViewListPresenter: CollectionViewListPresenter, UICollectionViewDelegateFlowLayout { + @IBInspectable var intStyle: Int { + get { return style.rawValue } + set { style = CardStyle(rawValue: newValue) ?? .full } + } + + @IBInspectable var proportional: Bool = false + + override public var mode: PresenterMode { + didSet { + if mode != oldValue { + updatePagerVisibility() + } + } + } + + public var style: CardStyle = .full { + didSet { + if style != oldValue { + updatePagerVisibility() + } + } + } + + @IBOutlet var pager: UIPageControl? { + didSet { + if pager !== oldValue { + oldValue?.removeTarget() + pager?.addTarget(self, action: #selector(page(_:))) + updatePagerVisibility() + updatePage() + } + } + } + + @IBOutlet var nextButton: UIButton? { + didSet { + if nextButton !== oldValue { + oldValue?.removeTarget() + nextButton?.addTarget(self, action: #selector(next(_:))) + updateNext() + } + } + } + + override open var current: [ModelObjectProtocol]? { + didSet { + updatePagerVisibility() + updatePage() + } + } + + override open func setupLayout() { + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + if let section = (mode == .sections) ? sections[indexPath.section] : sections.first, let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout { + return section.size(at: indexPath.row, layout: flowLayout, style: style, proportional: proportional) + } + return CGSize.zero + } + + public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + if pager?.currentPage != indexPath.row { + pager?.currentPage = indexPath.row + } + } + + private func updatePagerVisibility() { + pager?.visible = (current != nil && sections.count == 1 && style == .full) + } + + private func updatePage() { + if pager?.visible ?? false { + pager?.numberOfPages = sections.first?.count ?? 0 + pager?.currentPage = collectionView?.indexPathsForVisibleItems.first?.row ?? 0 + } + } + + private func updateNext() { + let current = collectionView?.indexPathsForVisibleItems.first?.row ?? 0 + let total = sections.first?.count ?? 0 + nextButton?.visible = (current != total - 1) + } + + @IBAction func page(_ sender: Any?) { + if pager?.visible ?? false, let page = pager?.currentPage { + collectionView?.scrollToItem(at: IndexPath(row: page, section: 0), at: .left, animated: true) + } + } + + @IBAction func next(_ sender: Any?) { + let current = collectionView?.indexPathsForVisibleItems.first?.row ?? 0 + let total = sections.first?.count ?? 0 + if current < total - 1 { + collectionView?.scrollToItem(at: IndexPath(row: current + 1, section: 0), at: .left, animated: true) + } + } + + override open func refresh(animated: Bool, completion: (() -> Void)?) { + if let initialPosition = initialPosition { + view?.visible = false + reloadData() + completion?() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { [weak self] in + if let self = self { + self.collectionView?.scrollToItem(at: IndexPath(row: initialPosition, section: 0), at: .left, animated: false) + self.initialPosition = nil + if let view = self.view as? UIView { + UIView.animate(view, type: .fade, direction: .none, duration: UIView.defaultAnimationDuration, animations: { /* [weak self] in */ + view.visible = true + }, completion: nil) + } + } + } + } else { + super.refresh(animated: animated, completion: completion) + } + } +} + +public extension RigidCollectionViewListPresenter { + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + updatePage() + updateNext() + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/CandleStickGraphingPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/CandleStickGraphingPresenter.swift new file mode 100644 index 000000000..1f02550c7 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/CandleStickGraphingPresenter.swift @@ -0,0 +1,64 @@ +// +// CandleStickGraphingPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 3/6/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Charts +import Differ +import ParticlesKit +import UIToolkits +import Utilities + +open class CandleStickGraphingPresenter: GraphingPresenter { + public var candleChartView: CandleStickChartView? { + return chartView as? CandleStickChartView + } + + open override func setup(chartView: ChartViewBase?) { + super.setup(chartView: chartView) + + setupXAxis(xAxis: chartView?.xAxis) + setupLeftAxis(leftAxis: candleChartView?.leftAxis) + setupRightAxis(rightAxis: candleChartView?.rightAxis) + } + + open override func setupChart(chartView: ChartViewBase?) { + super.setupChart(chartView: chartView) + + candleChartView?.drawBordersEnabled = drawBorders || drawGrid + candleChartView?.drawGridBackgroundEnabled = false + candleChartView?.drawMarkers = false + + candleChartView?.setScaleEnabled(true) + + candleChartView?.dragEnabled = true + candleChartView?.pinchZoomEnabled = true + candleChartView?.renderer = CandleStickGraphingRenderer(view: candleChartView!, minValue: 0, maxValue: 90000) + } + + open override func setupXAxis(xAxis: XAxis?) { + super.setupXAxis(xAxis: xAxis) + } + + open func setupLeftAxis(leftAxis: YAxis?) { + leftAxis?.enabled = false + leftAxis?.axisMinimum = 0.0 + } + + open func setupRightAxis(rightAxis: YAxis?) { + rightAxis?.drawAxisLineEnabled = true + rightAxis?.drawLabelsEnabled = drawYAxisText + rightAxis?.drawGridLinesEnabled = drawGrid + rightAxis?.drawLimitLinesBehindDataEnabled = false + rightAxis?.drawGridLinesBehindDataEnabled = false + rightAxis?.labelTextColor = labelColor ?? UIColor.label + rightAxis?.axisMinimum = 0.0 + } + + override open func setupLegend(legend: Legend?) { + super.setupLegend(legend: legend) + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/CombinedGraphingPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/CombinedGraphingPresenter.swift new file mode 100644 index 000000000..c9e8f2ee1 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/CombinedGraphingPresenter.swift @@ -0,0 +1,136 @@ +// +// CombinedGraphingPresenter.swift +// CombinedGraphingPresenter +// +// Created by Qiang Huang on 8/16/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Charts +import Differ +import ParticlesKit +import UIToolkits +import Utilities + +open class CombinedGraphingPresenter: GraphingPresenter { + private var candleStickList: CandleStickListProviderProtocol? + + public var combinedChartView: CombinedChartView? { + return chartView as? CombinedChartView + } + + override open func didSetPresenters(oldValue: [GraphingListPresenter]?) { + super.didSetPresenters(oldValue: oldValue) + candleStickList = presenters?.first(where: { listPresenter in + listPresenter is CandleStickListProviderProtocol + }) as? CandleStickListProviderProtocol + } + + override open func setup(chartView: ChartViewBase?) { + super.setup(chartView: chartView) + (chartView as? BarLineChartViewBase)?.autoScaleMinMaxEnabled = false + + setupXAxis(xAxis: chartView?.xAxis) + setupLeftAxis(leftAxis: combinedChartView?.leftAxis) + setupRightAxis(rightAxis: combinedChartView?.rightAxis) + + combinedChartView?.scaleXEnabled = true + combinedChartView?.scaleYEnabled = false + combinedChartView?.highlightPerDragEnabled = true + } + + override open func setupXAxis(xAxis: XAxis?) { + super.setupXAxis(xAxis: xAxis) + } + + open func setupLeftAxis(leftAxis: YAxis?) { + leftAxis?.enabled = true + leftAxis?.drawLabelsEnabled = drawYAxisText + leftAxis?.drawGridLinesEnabled = drawGrid + leftAxis?.drawLimitLinesBehindDataEnabled = false + leftAxis?.drawGridLinesBehindDataEnabled = false + leftAxis?.labelTextColor = labelColor ?? UIColor.label + leftAxis?.spaceBottom = 0.8 + leftAxis?.labelPosition = outsideYAxisText ? .outsideChart : .insideChart + leftAxis?.drawAxisLineEnabled = false + leftAxis?.valueFormatter = yAxisFormatter + } + + open func setupRightAxis(rightAxis: YAxis?) { + rightAxis?.enabled = true + rightAxis?.drawLabelsEnabled = false + rightAxis?.drawGridLinesEnabled = drawGrid + rightAxis?.drawLimitLinesBehindDataEnabled = false + rightAxis?.drawGridLinesBehindDataEnabled = false + rightAxis?.labelTextColor = labelColor ?? UIColor.label + rightAxis?.drawAxisLineEnabled = false + rightAxis?.labelPosition = outsideYAxisText ? .outsideChart : .insideChart + rightAxis?.axisMinimum = 0.0 + rightAxis?.spaceTop = 9 + } + + override open func setupLegend(legend: Legend?) { + super.setupLegend(legend: legend) + } + + override open func graphingDataSets() -> [String: [ChartDataSet]] { + var result = super.graphingDataSets() + var hasCandles = false + for (key, datasets) in result { + for dataset in datasets { + switch key { + case "candle": + fallthrough + case "line": + hasCandles = true + dataset.axisDependency = .left + + case "bar": + dataset.axisDependency = .right + + default: + break + } + } + } + if !hasCandles { + result.removeAll() + } + return result + } + + override open func apply(datasets: [String: [ChartDataSet]]) { + var chartData = [ChartData]() + for (key, value) in datasets { + if let data = data(dataSets: value, type: key) { + chartData.append(data) + } + } + if chartData.count > 0 { + if chartData.count == 1 { + chartData.first?.setValueFont(.systemFont(ofSize: 7, weight: .light)) + chartView?.data = chartData.first + } else { + let combinedChartData = CombinedChartData() + if let candleDataSets = datasets["candle"] as? [CandleChartDataSet] { + combinedChartData.candleData = CandleChartData(dataSets: candleDataSets) + } + if let lineDataSets = datasets["line"] as? [LineChartDataSet] { + combinedChartData.lineData = LineChartData(dataSets: lineDataSets) + } + if let barDataSets = datasets["bar"] as? [BarChartDataSet] { + combinedChartData.barData = BarChartData(dataSets: barDataSets) + } + combinedChartData.setValueFont(valueFont ?? .systemFont(ofSize: 7, weight: .light)) + chartView?.data = combinedChartData + } + chartView?.xAxis.setLabelCount(6, force: true) + combinedChartView?.setVisibleXRange(minXRange: 8, maxXRange: 40) + combinedChartView?.moveViewToX(10000) + combinedChartView?.setVisibleXRange(minXRange: 8, maxXRange: 160) + combinedChartView?.scale() + } else { + chartView?.clear() + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/GraphingListPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/GraphingListPresenter.swift new file mode 100644 index 000000000..924466f98 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/GraphingListPresenter.swift @@ -0,0 +1,216 @@ +// +// GraphingListPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 3/6/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Charts +import Differ +import ParticlesKit +import UIKit +import UIToolkits +import Utilities + +open class GraphingListPresenter: NativeListPresenter { + @IBInspectable public dynamic var limit: Int = 0 + @IBInspectable public dynamic var label: String? + @IBInspectable public dynamic var color: UIColor? { + didSet { + didSetColor(oldValue: oldValue) + } + } + + public var allowColorOverride = true + + @IBInspectable public dynamic var showLabel: Bool = false + @IBInspectable public dynamic var highlightEnabled: Bool = false + @IBInspectable public dynamic var highlightLineWidth: CGFloat = 0.5 + @IBInspectable public dynamic var highlightColor: UIColor? + @IBInspectable public dynamic var highlightPhase: CGFloat = 0.0 + @IBInspectable public dynamic var highlightDash: CGFloat = 0.0 + + private var pendingGraphingSet: ParticlesChartDataSetProtocol? { + didSet { + didSetPendingGraphingSet(oldValue: oldValue) + } + } + + @objc public dynamic var graphingSet: ChartDataSet? + + @objc public dynamic var colors: [UIColor]? + + @objc public dynamic var graphingLimitsProvider: GraphingLimitsProviderProtocol? + + private func didSetPendingGraphingSet(oldValue: ParticlesChartDataSetProtocol?) { + changeObservation(from: oldValue, to: pendingGraphingSet, keyPath: "syncing") { [weak self] _, _, _, animated in + if animated { + if let dataSet = (self?.pendingGraphingSet as? ParticlesChartDataSetProtocol), dataSet.syncing == false { + self?.graphingSet = self?.pendingGraphingSet + if let color = self?.color, self?.allowColorOverride ?? false { + self?.graphingSet?.colors = [color] + } + } + } else { + self?.graphingSet = nil + } + } + } + + override open func update() { + current = pending + if let current = current, current.count > 0 { + if let interactor = interactor, pendingGraphingSet === nil { + let dataSet = graphingDataSet() + dataSet?.limit = limit + dataSet?.listInteractor = interactor + pendingGraphingSet = dataSet + } + } else { + pendingGraphingSet = nil + } + } + + override open func filter(items: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + if let linearItmes = items { + return linearItmes.sorted(by: { (item1, item2) -> Bool in + item1.order?(ascending: item2) ?? true + }) + } else { + return items + } + } + + open func graphingDataSet() -> ParticlesChartDataSetProtocol? { + return nil + } + + open func didSetColor(oldValue: UIColor?) { + if let color = color, allowColorOverride { + graphingSet?.colors = [color] + } + } +} + +@objc public class LineGraphingListPresenter: GraphingListPresenter { + @IBInspectable public var autoColor: Bool = false + @IBInspectable public var increasingColor: UIColor? + @IBInspectable public var decreasingColor: UIColor? + @IBInspectable public var drawFilled: Bool = false + @IBInspectable public var stepped: Bool = false + @IBInspectable public var smooth: Bool = false + @IBInspectable public var lineWidth: CGFloat = 2.0 + @IBInspectable public var fillAlpha: CGFloat = 0.3 + @IBInspectable public var circleRadius: CGFloat = 4.0 + @IBInspectable public var circleHoleRadius: CGFloat = 4.0 + + override open func graphingDataSet() -> ParticlesChartDataSetProtocol? { + let dataSet = ParticlesLineChartDataSet() + dataSet.mode = stepped ? .stepped : (smooth ? .horizontalBezier : .linear) + dataSet.lineWidth = lineWidth + dataSet.lineCapType = .round + dataSet.drawCirclesEnabled = false + dataSet.drawValuesEnabled = false + dataSet.circleRadius = circleRadius + dataSet.circleHoleRadius = circleHoleRadius + if let color = self.color ?? UIColor(named: "Light Text") { + dataSet.setColor(color) + dataSet.fill = .fillWithCGColor(color.cgColor) + } + + dataSet.drawFilledEnabled = drawFilled + dataSet.fillAlpha = fillAlpha + dataSet.highlightEnabled = highlightEnabled + dataSet.highlightColor = highlightColor ?? UIColor.label + dataSet.highlightLineDashPhase = highlightPhase + dataSet.highlightLineDashLengths = [highlightDash] + dataSet.highlightLineWidth = highlightLineWidth + dataSet.label = label + dataSet.axisDependency = YAxis.AxisDependency.left + dataSet.drawVerticalHighlightIndicatorEnabled = true + dataSet.drawHorizontalHighlightIndicatorEnabled = false + return dataSet + } + + override public func update() { + if let increasingColor = increasingColor, let decreasingColor = decreasingColor { + allowColorOverride = true + if let first = interactor?.list?.first as? LinearGraphingObjectProtocol, let last = interactor?.list?.last as? LinearGraphingObjectProtocol, let firstY = first.lineY?.doubleValue, let lastY = last.lineY?.doubleValue { + if lastY > firstY { + color = increasingColor + } else if lastY < firstY { + color = decreasingColor + } + } + allowColorOverride = false + } + super.update() + } + +} + +@objc public class BarGraphingListPresenter: GraphingListPresenter { + @IBInspectable public var increasingColor: UIColor? + @IBInspectable public var decreasingColor: UIColor? + + override open func graphingDataSet() -> ParticlesChartDataSetProtocol? { + let dataSet = ParticlesBarChartDataSet() + dataSet.barBorderWidth = 0.5 + dataSet.drawValuesEnabled = false + dataSet.axisDependency = YAxis.AxisDependency.right + dataSet.highlightEnabled = highlightEnabled + dataSet.highlightColor = highlightColor ?? UIColor.label + dataSet.highlightLineDashPhase = highlightPhase + dataSet.highlightLineDashLengths = [highlightDash] + dataSet.highlightLineWidth = highlightLineWidth + dataSet.increasingColor = increasingColor + dataSet.decreasingColor = decreasingColor + + return dataSet + } +} + +@objc public class CandleStickGraphingListPresenter: GraphingListPresenter { + @IBInspectable var lineWidth: CGFloat = 1.0 + + @IBInspectable public var increasingColor: UIColor? + @IBInspectable public var decreasingColor: UIColor? + @IBInspectable public var neutralColor: UIColor? + + override open func graphingDataSet() -> ParticlesChartDataSetProtocol? { + let dataSet = ParticlesCandleChartDataSet() + dataSet.increasingColor = increasingColor + dataSet.decreasingColor = decreasingColor + dataSet.shadowColorSameAsCandle = true + dataSet.neutralColor = neutralColor + dataSet.increasingFilled = true + dataSet.decreasingFilled = true + dataSet.axisDependency = YAxis.AxisDependency.left + dataSet.drawValuesEnabled = false + dataSet.formLineWidth = lineWidth + dataSet.shadowWidth = 1.0 + dataSet.highlightColor = highlightColor ?? .label + dataSet.highlightEnabled = highlightEnabled + dataSet.highlightLineDashPhase = highlightPhase + dataSet.highlightLineDashLengths = [highlightDash] + dataSet.highlightLineWidth = highlightLineWidth + dataSet.drawVerticalHighlightIndicatorEnabled = true + dataSet.drawHorizontalHighlightIndicatorEnabled = false + return dataSet + } +} + +@objc public class PieGraphingListPresenter: GraphingListPresenter { + override open func graphingDataSet() -> ParticlesChartDataSetProtocol? { + let dataSet = ParticlesPieChartDataSet() + dataSet.drawIconsEnabled = false + dataSet.drawValuesEnabled = false + dataSet.sliceSpace = 1 + dataSet.selectionShift = 0 + if let colors = colors { + dataSet.colors = colors + } + return dataSet + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/GraphingPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/GraphingPresenter.swift new file mode 100644 index 000000000..002ca514e --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/GraphingPresenter.swift @@ -0,0 +1,216 @@ +// +// GraphingPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 7/21/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Charts +import Differ +import ParticlesKit +import UIKit +import UIToolkits +import Utilities + +@objc public protocol GraphingDelegate { + @objc func didLoad(presenter: GraphingPresenter, view: ChartViewBase?, data: ChartData?) +} + +open class GraphingPresenter: NSObject { + @IBOutlet public weak var chartDelegate: ChartViewDelegate? { + didSet { + chartView?.delegate = chartDelegate + } + } + + @IBOutlet public weak var graphingDelegate: GraphingDelegate? = nil + + @IBOutlet public var xAxisFormatter: IAxisValueFormatter? { + didSet { + didSetXAxisFormatter(oldValue: oldValue) + } + } + + @IBOutlet public var yAxisFormatter: IAxisValueFormatter? { + didSet { + didSetYAxisFormatter(oldValue: oldValue) + } + } + + @IBInspectable public var panEnabled: Bool = true + @IBInspectable public var highlightDistance: Double = 500.0 + @IBInspectable public var drawXAxisLine: Bool = false + @IBInspectable public var valueFont: UIFont? + @IBInspectable public var legendFont: UIFont? + @IBInspectable public var axisFont: UIFont? + @IBInspectable public var labelColor: UIColor? + @IBInspectable public var drawXAxisText: Bool = false + @IBInspectable public var drawYAxisText: Bool = false + @IBInspectable public var outsideXAxisText: Bool = false + @IBInspectable public var outsideYAxisText: Bool = false + @IBInspectable public var drawGrid: Bool = false + @IBInspectable public var drawBorders: Bool = false + @IBInspectable public var lineWidth: Int = 1 + @IBOutlet public var view: UIView? + @IBOutlet public var chartView: ChartViewBase? { + didSet { + didSetChartView(oldValue: oldValue) + } + } + + @IBOutlet open var presenters: [GraphingListPresenter]? { + didSet { + didSetPresenters(oldValue: oldValue) + } + } + + private var graphDebouncer: Debouncer = Debouncer() + + open func didSetPresenters(oldValue: [GraphingListPresenter]?) { + if let oldValue = oldValue { + for presenter in oldValue { + changeObservation(from: presenter, to: nil, keyPath: #keyPath(GraphingListPresenter.graphingSet)) { _, _, _, _ in + } + } + } + if let presenters = presenters { + for presenter in presenters { + changeObservation(from: nil, to: presenter, keyPath: #keyPath(GraphingListPresenter.graphingSet)) { [weak self] _, _, _, _ in + self?.displayGraphing(animated: false) + } + } + } + } + + open func didSetXAxisFormatter(oldValue: IAxisValueFormatter?) { + if xAxisFormatter !== oldValue { + chartView?.xAxis.valueFormatter = xAxisFormatter + } + } + + open func didSetYAxisFormatter(oldValue: IAxisValueFormatter?) { + } + + open func displayGraphing(animated: Bool) { + let dataSets = graphingDataSets() + apply(datasets: dataSets) + Console.shared.log("Graphing: Applying Data") + +// throttle.debounce()?.run({[weak self] in +// if let dataSets = self?.graphingDataSets() { +// self?.apply(datasets: dataSets) +// } +// }, delay: 0.0) + } + + open func graphingDataSets() -> [String: [ChartDataSet]] { + var datasets = [String: [ChartDataSet]]() + + if let presenters = presenters { + for presenter in presenters { + if let dataSet = presenter.graphingSet, dataSet.entries.count > 0 { + (dataSet as? ParticlesChartDataSetProtocol)?.presenter.object = self + if let candle = dataSet as? CandleChartDataSet { + datasets = add(dataSets: datasets, type: "candle", dataSet: candle) + } else if let bar = dataSet as? BarChartDataSet { + datasets = add(dataSets: datasets, type: "bar", dataSet: bar) + } else if let line = dataSet as? LineChartDataSet { + datasets = add(dataSets: datasets, type: "line", dataSet: line) + } else if let pie = dataSet as? PieChartDataSet { + datasets = add(dataSets: datasets, type: "pie", dataSet: pie) + } + } + } + } + return datasets + } + + open func apply(datasets: [String: [ChartDataSet]]) { + var chartData = [ChartData]() + for (key, value) in datasets { + if let data = data(dataSets: value, type: key) { + chartData.append(data) + } + } + chartData.first?.setValueFont(valueFont ?? .systemFont(ofSize: 7, weight: .light)) + chartView?.data = chartData.first + graphingDelegate?.didLoad(presenter: self, view: chartView, data: chartView?.data) + } + + private func add(dataSets: [String: [ChartDataSet]], type: String, dataSet: ChartDataSet) -> [String: [ChartDataSet]] { + var dataSets = dataSets + if var modifying = dataSets[type] { + modifying.append(dataSet) + dataSets[type] = modifying + } else { + dataSets[type] = [dataSet] + } + return dataSets + } + + internal func data(dataSets: [ChartDataSet], type: String) -> ChartData? { + switch type { + case "bar": + return BarChartData(dataSets: dataSets) + + case "line": + return LineChartData(dataSets: dataSets) + + case "candle": + return CandleChartData(dataSets: dataSets) + + case "pie": + return PieChartData(dataSets: dataSets) + + default: + return nil + } + } + + open func didSetChartView(oldValue: ChartViewBase?) { + if chartView !== oldValue { + setup(chartView: chartView) + } + } + + open func setup(chartView: ChartViewBase?) { + chartView?.delegate = chartDelegate + + setupChart(chartView: chartView) + setupLegend(legend: chartView?.legend) + } + + open func setupChart(chartView: ChartViewBase?) { + chartView?.noDataText = "" + chartView?.chartDescription?.enabled = false + chartView?.drawMarkers = false + chartView?.highlightPerTapEnabled = false + chartView?.maxHighlightDistance = highlightDistance + } + + open func setupLegend(legend: Legend?) { + legend?.enabled = false + legend?.horizontalAlignment = .center + legend?.verticalAlignment = .top + legend?.orientation = .horizontal + legend?.drawInside = true + legend?.font = legendFont ?? UIFont.systemFont(ofSize: 8) + } + + open func setupXAxis(xAxis: XAxis?) { + xAxis?.labelFont = valueFont ?? UIFont.systemFont(ofSize: 8) + xAxis?.labelTextColor = labelColor ?? UIColor.label + xAxis?.drawLabelsEnabled = drawXAxisText + xAxis?.drawGridLinesEnabled = drawGrid + xAxis?.drawGridLinesBehindDataEnabled = false + xAxis?.drawLimitLinesBehindDataEnabled = false + xAxis?.valueFormatter = xAxisFormatter + xAxis?.labelPosition = outsideXAxisText ? .bottom : .bottomInside + xAxis?.valueFormatter = xAxisFormatter + xAxis?.drawAxisLineEnabled = drawXAxisLine + xAxis?.forceLabelsEnabled = true + xAxis?.granularityEnabled = true + xAxis?.centerAxisLabelsEnabled = true + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/LineGraphingPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/LineGraphingPresenter.swift new file mode 100644 index 000000000..99c3b1b48 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/LineGraphingPresenter.swift @@ -0,0 +1,77 @@ +// +// LineGraphingPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 3/6/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Charts +import Differ +import ParticlesKit +import UIToolkits +import Utilities + +open class LineGraphingPresenter: GraphingPresenter { + @IBInspectable public var doubleTapToZoomEnabled: Bool = false + @IBInspectable public var drawFilled: Bool = false + public var lineChartView: LineChartView? { + return chartView as? LineChartView + } + + override open func setup(chartView: ChartViewBase?) { + super.setup(chartView: chartView) + + setupXAxis(xAxis: chartView?.xAxis) + setupLeftAxis(leftAxis: lineChartView?.leftAxis) + setupRightAxis(rightAxis: lineChartView?.rightAxis) + } + + override open func setupChart(chartView: ChartViewBase?) { + super.setupChart(chartView: chartView) + + lineChartView?.doubleTapToZoomEnabled = doubleTapToZoomEnabled + lineChartView?.autoScaleMinMaxEnabled = true + lineChartView?.highlightPerDragEnabled = true + lineChartView?.drawBordersEnabled = drawBorders || drawGrid + lineChartView?.drawGridBackgroundEnabled = false + + lineChartView?.dragEnabled = panEnabled + lineChartView?.setScaleEnabled(true) + } + + override open func setupXAxis(xAxis: XAxis?) { + super.setupXAxis(xAxis: xAxis) + + lineChartView?.xAxis.drawLabelsEnabled = drawXAxisText + lineChartView?.xAxis.drawGridLinesEnabled = drawGrid + lineChartView?.xAxis.labelTextColor = labelColor ?? UIColor.label + } + + open func setupLeftAxis(leftAxis: YAxis?) { + leftAxis?.drawLabelsEnabled = drawYAxisText + leftAxis?.drawGridLinesEnabled = drawGrid + leftAxis?.drawLimitLinesBehindDataEnabled = false + leftAxis?.drawGridLinesBehindDataEnabled = false + leftAxis?.labelTextColor = labelColor ?? UIColor.label + leftAxis?.labelPosition = outsideYAxisText ? .outsideChart : .insideChart + leftAxis?.valueFormatter = yAxisFormatter + leftAxis?.drawAxisLineEnabled = false + leftAxis?.drawZeroLineEnabled = true + leftAxis?.zeroLineDashLengths = [4.0] + } + + open func setupRightAxis(rightAxis: YAxis?) { + rightAxis?.enabled = false + } + + override open func didSetYAxisFormatter(oldValue: IAxisValueFormatter?) { + if yAxisFormatter !== oldValue { + lineChartView?.leftAxis.valueFormatter = yAxisFormatter + } + } + + override open func setupLegend(legend: Legend?) { + super.setupLegend(legend: legend) + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/PieGraphingPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/PieGraphingPresenter.swift new file mode 100644 index 000000000..d855f80a8 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/PieGraphingPresenter.swift @@ -0,0 +1,28 @@ +// +// PieGraphingPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 3/6/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Charts +import Differ +import ParticlesKit +import UIToolkits +import Utilities + +open class PieGraphingPresenter: GraphingPresenter { + public var pieChartView: PieChartView? { + return chartView as? PieChartView + } + + override open func setupChart(chartView: ChartViewBase?) { + super.setupChart(chartView: chartView) + pieChartView?.drawHoleEnabled = true + pieChartView?.holeRadiusPercent = 0.7 + pieChartView?.drawMarkers = true + pieChartView?.drawEntryLabelsEnabled = drawXAxisText + pieChartView?.drawCenterTextEnabled = false + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesBarChartDataEntry.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesBarChartDataEntry.swift new file mode 100644 index 000000000..2512d04de --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesBarChartDataEntry.swift @@ -0,0 +1,31 @@ +// +// ParticlesCandleChartDataEntry.swift +// PlatformParticles +// +// Created by Qiang Huang on 10/8/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Charts +import ParticlesKit +import Utilities + +@objc public class ParticlesBarChartDataEntry: BarChartDataEntry, ParticlesChartDataEntryProtocol { + public var dataSet: Weak = Weak() + public var notifierDebouncer: Debouncer = Debouncer() + + private var barData: BarGraphingObjectProtocol? { + return model as? BarGraphingObjectProtocol + } + + override open func sync() { + if let graphing = model as? BarGraphingObjectProtocol { + if let value = graphing.graphingX?.doubleValue { + x = value + } + if let value = graphing.barY?.doubleValue { + y = value + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesCandleChartDataEntry.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesCandleChartDataEntry.swift new file mode 100644 index 000000000..a72fb78f9 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesCandleChartDataEntry.swift @@ -0,0 +1,41 @@ +// +// ParticlesCandleChartDataEntry.swift +// PlatformParticles +// +// Created by Qiang Huang on 10/8/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Charts +import ParticlesKit +import Utilities + +@objc public class ParticlesCandleChartDataEntry: CandleChartDataEntry, ParticlesChartDataEntryProtocol { + public var dataSet: Weak = Weak() + public var notifierDebouncer: Debouncer = Debouncer() + + private var candleData: CandleGraphingObjectProtocol? { + return model as? CandleGraphingObjectProtocol + } + + override open func sync() { + if let graphing = model as? CandleGraphingObjectProtocol { + if let value = graphing.graphingX?.doubleValue { + x = value + } + if let value = graphing.candleHigh?.doubleValue { + high = value + } + if let value = graphing.candleLow?.doubleValue { + low = value + } + if let value = graphing.candleOpen?.doubleValue { + open = value + } + if let value = graphing.candleClose?.doubleValue { + close = value + y = value + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesChartDataEntryProtocol.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesChartDataEntryProtocol.swift new file mode 100644 index 000000000..4978719f9 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesChartDataEntryProtocol.swift @@ -0,0 +1,67 @@ +// +// ParticlesChartDataEntryProtocol.swift +// PlatformParticles +// +// Created by Qiang Huang on 10/29/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Charts +import ObjectiveC +import ParticlesKit +import UIKit +import Utilities + +extension ChartDataEntry { + private var weakModel: Weak? { + get { + return data as? Weak + } + set { + data = newValue + } + } + + @objc public var model: ModelObjectProtocol? { + get { + return weakModel?.object + } + set { + if model !== newValue { + let oldValue = model + if let newValue = newValue { + weakModel = Weak(newValue) + } else { + weakModel = nil + } + didSetModel(oldValue: oldValue) + } + } + } + + @objc open func didSetModel(oldValue: ModelObjectProtocol?) { + sync() + } + + @objc open func sync() { + } +} + +public protocol ParticlesChartDataEntryProtocol: ChartDataEntry { + var dataSet: Weak { get set } + var notifierDebouncer: Debouncer { get } +} + +public extension ParticlesChartDataEntryProtocol { + var color: UIColor? { + return nil + } + + func notify() { + if let particlesDataSet = dataSet.object as? ParticlesChartDataSetProtocol { +// particlesDataSet.notify() + } else { +// dataSet.object?.notifyDataSetChanged() + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesLineChartDataEntry.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesLineChartDataEntry.swift new file mode 100644 index 000000000..36e864154 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesLineChartDataEntry.swift @@ -0,0 +1,31 @@ +// +// ParticlesCandleChartDataEntry.swift +// PlatformParticles +// +// Created by Qiang Huang on 10/8/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Charts +import ParticlesKit +import Utilities + +@objc public class ParticlesLineChartDataEntry: ChartDataEntry, ParticlesChartDataEntryProtocol { + public var dataSet: Weak = Weak() + public var notifierDebouncer: Debouncer = Debouncer() + + private var lineData: LinearGraphingObjectProtocol? { + return model as? LinearGraphingObjectProtocol + } + + override open func sync() { + if let graphing = model as? LinearGraphingObjectProtocol { + if let value = graphing.graphingX?.doubleValue { + x = value + } + if let value = graphing.lineY?.doubleValue { + y = value + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesPieChartDataEntry.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesPieChartDataEntry.swift new file mode 100644 index 000000000..4f1867141 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataEntry/ParticlesPieChartDataEntry.swift @@ -0,0 +1,31 @@ +// +// ParticlesCandleChartDataEntry.swift +// PlatformParticles +// +// Created by Qiang Huang on 10/8/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Charts +import ParticlesKit +import Utilities + +@objc public class ParticlesPieChartDataEntry: PieChartDataEntry, ParticlesChartDataEntryProtocol { + public var dataSet: Weak = Weak() + public var notifierDebouncer: Debouncer = Debouncer() + + private var pieData: PieGraphingObjectProtocol? { + return model as? PieGraphingObjectProtocol + } + + override open func sync() { + if let graphing = model as? PieGraphingObjectProtocol { + if let value = graphing.pieLabel { + label = value + } + if let value = graphing.pieY?.doubleValue { + y = value + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesBarChartDataSet.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesBarChartDataSet.swift new file mode 100644 index 000000000..a150201d6 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesBarChartDataSet.swift @@ -0,0 +1,106 @@ +// +// ParticlesBarChartDataSet.swift +// PlatformParticles +// +// Created by Qiang Huang on 11/4/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Charts +import ParticlesKit +import UIKit +import Utilities + +@objc public class ParticlesBarChartDataSet: BarChartDataSet, ParticlesChartDataSetProtocol { + public var syncDebouncer = Debouncer() + + public var limit: Int = 0 + @objc public dynamic var syncing: Bool = false + + public var increasingColor: UIColor? + public var decreasingColor: UIColor? + + private lazy var leading: ChartDataEntry = { + BarChartDataEntry(x: -3, y: 0) + }() + + private lazy var tailing: ChartDataEntry = { + BarChartDataEntry(x: 0, y: 0) + }() + + public var presenter = Weak() + + public var notifierDebouncer = Debouncer() + + public var listInteractor: ListInteractor? { + didSet { + changeObservation(from: oldValue, to: listInteractor, keyPath: #keyPath(ListInteractor.list)) { [weak self] _, _, _, _ in + self?.sync() + } + } + } + + public func entry() -> ParticlesChartDataEntryProtocol { + return ParticlesBarChartDataEntry() + } + + public func replace(entries: [ChartDataEntry]) { + if entries.count > 0, let first = entries.first as? BarChartDataEntry, let last = entries.last as? BarChartDataEntry { + leading.x = first.x - 4 + tailing.x = last.x + 4 + var modified = entries + modified.insert(leading, at: 0) + modified.append(tailing) + replaceEntries(modified) + if let colors = calculateColors(entries: modified) { + self.colors = colors + } + } else { + replaceEntries(entries) + if let colors = calculateColors(entries: entries) { + self.colors = colors + } + } + } + + private func calculateColors(entries: [ChartDataEntry]) -> [UIColor]? { + if let increasingColor = increasingColor, let decreasingColor = decreasingColor { + var colors = [UIColor]() + var previous: ParticlesChartDataEntryProtocol? + for i in 0 ..< entries.count { + if let entry = entries[i] as? ParticlesChartDataEntryProtocol { + if increasing(entry: entry, previous: previous) { + colors.append(increasingColor) + } else { + colors.append(decreasingColor) + } + previous = entry + } else { + // first entry is place holder + colors.append(UIColor.clear) + } + } + return colors + } else { + return nil + } + } + + private func increasing(entry: ParticlesChartDataEntryProtocol?, previous: ParticlesChartDataEntryProtocol?) -> Bool { + if let thisCandle = entry?.model as? CandleGraphingObjectProtocol { + if let previousCandle = previous?.model as? CandleGraphingObjectProtocol { + return (thisCandle.candleClose?.doubleValue ?? Double.zero) >= (previousCandle.candleClose?.doubleValue ?? Double.zero) + } else { + return (thisCandle.candleClose?.doubleValue ?? Double.zero) >= (thisCandle.candleOpen?.doubleValue ?? Double.zero) + } + } else if let thisLinear = entry?.model as? LinearGraphingObjectProtocol { + if let previousLinear = previous?.model as? LinearGraphingObjectProtocol { + return (thisLinear.lineY?.doubleValue ?? Double.zero) >= (previousLinear.lineY?.doubleValue ?? Double.zero) + } else { + return true + } + } else { + return true + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesCandleChartDataSet.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesCandleChartDataSet.swift new file mode 100644 index 000000000..156c6b7b8 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesCandleChartDataSet.swift @@ -0,0 +1,38 @@ +// +// ParticlesCandleChartDataSet.swift +// PlatformParticles +// +// Created by Qiang Huang on 11/4/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Charts +import ParticlesKit +import Utilities + +@objc public class ParticlesCandleChartDataSet: CandleChartDataSet, ParticlesChartDataSetProtocol { + public var syncDebouncer = Debouncer() + + public var limit: Int = 0 + @objc public dynamic var syncing: Bool = false + + public var presenter = Weak() + + public var notifierDebouncer = Debouncer() + + public var listInteractor: ListInteractor? { + didSet { + changeObservation(from: oldValue, to: listInteractor, keyPath: #keyPath(ListInteractor.list)) { [weak self] _, _, _, _ in + self?.sync() + } + } + } + + public func entry() -> ParticlesChartDataEntryProtocol { + return ParticlesCandleChartDataEntry() + } + + public func notify() { + presenter.object?.displayGraphing(animated: false) + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesChartDataSetProtocol.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesChartDataSetProtocol.swift new file mode 100644 index 000000000..5960404df --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesChartDataSetProtocol.swift @@ -0,0 +1,137 @@ +// +// DataSetNotifierProtocol.swift +// PlatformParticles +// +// Created by Qiang Huang on 11/4/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Charts +import CoreVideo +import ObjectiveC +import ParticlesKit +import Utilities + +@objc public class GraphingLimit: NSObject { + var label: String? + var value: NSNumber? + var color: String? +} + +@objc public protocol GraphingLimitsProviderProtocol: NSObjectProtocol { + var limits: [GraphingLimit]? { get set } +} + +public protocol ParticlesChartDataSetProtocol: ChartDataSet { + var limit: Int { get set } + var presenter: Weak { get set } + var notifierDebouncer: Debouncer { get } + + var syncing: Bool { get set } + var syncDebouncer: Debouncer { get set } + + var listInteractor: ListInteractor? { get set } + func entry() -> ParticlesChartDataEntryProtocol + func replace(entries: [ChartDataEntry]) + func filter(list: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? + func sort(list: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? + func notify() +} + +public extension ParticlesChartDataSetProtocol { + func notify() { + if !syncing { + // Console.shared.log("Graphing: Syncing Data") + presenter.object?.chartView?.notifyDataSetChanged() + (presenter.object?.chartView as? BarLineChartViewBase)?.scale() + } + } + + func replace(entries: [ChartDataEntry]) { + replaceEntries(entries) + } + + func filter(list: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + return list + } + + func sort(list: [ModelObjectProtocol]?) -> [ModelObjectProtocol]? { + if let list = list { + if let first = (list.first as? GraphingObjectProtocol)?.graphingX?.doubleValue, let last = (list.last as? GraphingObjectProtocol)?.graphingX?.doubleValue { + if first < last { + return list + } else { + return Array(list.reversed()) + } + } else { + return list + } + } else { + return nil + } + } + + func sync() { + let list = listInteractor?.list + let existing = entries + var sorted: [ModelObjectProtocol]? + var entries: [ChartDataEntry]? + + syncing = true + syncDebouncer.debounce()?.run(backgrounds: [{ [weak self] in + sorted = self?.sort(list: self?.filter(list: list)) + }, { [weak self] in + entries = self?.dataEntries(list: sorted, existing: existing) + }], final: { [weak self] in + if let entries = entries { + self?.replace(entries: entries) + } + self?.syncing = false + self?.notify() + }, delay: nil) + } + + func dataEntries(list: [ModelObjectProtocol]?, existing: [ChartDataEntry]) -> [ChartDataEntry]? { + if let list = list { + var cursor: Int? + var current: ChartDataEntry? + if existing.count > 0 { + cursor = 0 + current = existing[cursor!] + } + var entries = [ChartDataEntry]() +// var limit = limit +// if limit == 0 || limit > list.count { +// limit = list.count +// } + for i in 0 ..< list.count { + let model = list[i] + var found: ChartDataEntry? + while found === nil, current !== nil { + if current?.model === model { + found = current + } + if cursor! < existing.count - 1 { + cursor = cursor! + 1 + current = existing[cursor!] + } else { + cursor = nil + current = nil + } + } + if let found = found { + found.sync() + entries.append(found) + } else { + let entry = self.entry() + entry.model = model + entry.dataSet.object = self + entries.append(entry) + } + } + return entries + } else { + return nil + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesLineChartDataSet.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesLineChartDataSet.swift new file mode 100644 index 000000000..6e22980ff --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesLineChartDataSet.swift @@ -0,0 +1,34 @@ +// +// ParticlesLineChartDataSet.swift +// PlatformParticles +// +// Created by Qiang Huang on 11/4/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Charts +import ParticlesKit +import Utilities + +@objc public class ParticlesLineChartDataSet: LineChartDataSet, ParticlesChartDataSetProtocol { + public var syncDebouncer = Debouncer() + + public var limit: Int = 0 + @objc public dynamic var syncing: Bool = false + + public var presenter = Weak() + + public var notifierDebouncer = Debouncer() + + public var listInteractor: ListInteractor? { + didSet { + changeObservation(from: oldValue, to: listInteractor, keyPath: #keyPath(ListInteractor.list)) { [weak self] _, _, _, _ in + self?.sync() + } + } + } + + public func entry() -> ParticlesChartDataEntryProtocol { + return ParticlesLineChartDataEntry() + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesPieChartDataSet.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesPieChartDataSet.swift new file mode 100644 index 000000000..90cd54797 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_DataSet/ParticlesPieChartDataSet.swift @@ -0,0 +1,34 @@ +// +// ParticlesPieChartDataSet.swift +// PlatformParticles +// +// Created by Qiang Huang on 11/4/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Charts +import ParticlesKit +import Utilities + +@objc public class ParticlesPieChartDataSet: PieChartDataSet, ParticlesChartDataSetProtocol { + public var syncDebouncer = Debouncer() + + public var limit: Int = 0 + @objc public dynamic var syncing: Bool = false + + public var presenter = Weak() + + public var notifierDebouncer = Debouncer() + + public var listInteractor: ListInteractor? { + didSet { + changeObservation(from: oldValue, to: listInteractor, keyPath: #keyPath(ListInteractor.list)) { [weak self] _, _, _, _ in + self?.sync() + } + } + } + + public func entry() -> ParticlesChartDataEntryProtocol { + return ParticlesPieChartDataEntry() + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_Extensions/BarLineChartViewBase+Scaling.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_Extensions/BarLineChartViewBase+Scaling.swift new file mode 100644 index 000000000..0df990054 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_Extensions/BarLineChartViewBase+Scaling.swift @@ -0,0 +1,15 @@ +// +// BarLineChartViewBase+Scaling.swift +// PlatformParticles +// +// Created by Qiang Huang on 10/10/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Charts + +public extension BarLineChartViewBase { + func scale() { + autoScale() + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_Formatter/GraphingAxisFormatter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_Formatter/GraphingAxisFormatter.swift new file mode 100644 index 000000000..fbaef053b --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_Formatter/GraphingAxisFormatter.swift @@ -0,0 +1,16 @@ +// +// GraphingAxisFormatter.swift +// PlatformParticles +// +// Created by Qiang Huang on 9/29/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Charts +import Foundation + +@objc open class GraphingAxisFormater: NSObject, IAxisValueFormatter { + open func stringForValue(_ value: Double, axis _: AxisBase?) -> String { + return "" + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_Formatter/TimeAxisFormatter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_Formatter/TimeAxisFormatter.swift new file mode 100644 index 000000000..ad0c412be --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_Formatter/TimeAxisFormatter.swift @@ -0,0 +1,115 @@ +// +// TimeAxisFormater.swift +// PlatformParticles +// +// Created by Qiang Huang on 9/28/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Charts +import Foundation + +@objc public enum DateTimeResolution: Int { + case minute1 + case minute5 + case minute15 + case minute30 + case hour1 + case hour4 + case day1 +} + +@objc public class TimeAxisFormatter: GraphingAxisFormater { + public static var minuteFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.hour, .minute] + return formatter + }() + + public static var hourFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.hour, .minute] + return formatter + }() + + public static var dayFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.day] + return formatter + }() + + public static var monthFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.month] + return formatter + }() + + @objc public dynamic var resolution: DateTimeResolution = .day1 + + override open func stringForValue(_ value: Double, axis _: AxisBase?) -> String { + let datetime = Date(timeIntervalSince1970: value) + + return formatString(datetime: datetime) ?? "" + } + + public func formatString(datetime: Date) -> String? { + let components = Calendar.current.dateComponents([.day, .hour, .minute], from: datetime) + switch resolution { + case .minute1: + if let minute = components.minute, minute % 5 == 0 { + return type(of: self).minuteFormatter.string(from: components) + } else { + return nil + } + + case .minute5: + if let minute = components.minute, minute % 30 == 0 { + return type(of: self).minuteFormatter.string(from: components) + } else { + return nil + } + + case .minute15: + if let minute = components.minute, minute == 0 { + return type(of: self).hourFormatter.string(from: components) + } else { + return nil + } + + case .minute30: + if let minute = components.minute, let hour = components.hour, minute == 0, hour % 3 == 0 { + return type(of: self).hourFormatter.string(from: components) + } else { + return nil + } + + case .hour1: + if let minute = components.minute, let hour = components.hour, minute == 0, hour % 6 == 0 { + return type(of: self).hourFormatter.string(from: components) + } else { + return nil + } + + case .hour4: + if let minute = components.minute, let hour = components.hour, minute == 0, hour == 0 { + return type(of: self).dayFormatter.string(from: components) + } else { + return nil + } + + case .day1: + if let minute = components.minute, let hour = components.hour, let day = components.day, minute == 0, hour == 0, (day - 1) % 4 == 0 { + if day == 1 { + return type(of: self).monthFormatter.string(from: components) + } else { + return type(of: self).dayFormatter.string(from: components) + } + } else { + return nil + } + + default: + return nil + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_Renderer/CandleStickGraphingRenderer.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_Renderer/CandleStickGraphingRenderer.swift new file mode 100644 index 000000000..4a422e50b --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Graphing/_Renderer/CandleStickGraphingRenderer.swift @@ -0,0 +1,103 @@ +// +// CandleStickGraphingRenderer.swift +// PlatformParticles +// +// Created by Qiang Huang on 9/29/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Charts +import Foundation + +class CandleStickGraphingRenderer: CandleStickChartRenderer { + private var _xBounds = XBounds() // Reusable XBounds object + + private var minValue: Double + private var maxValue: Double + + // New constructor + init(view: CandleStickChartView, minValue: Double, maxValue: Double) { + self.minValue = minValue + self.maxValue = maxValue + + super.init(dataProvider: view, animator: view.chartAnimator, viewPortHandler: view.viewPortHandler) + } + + // Override draw function + override func drawValues(context: CGContext) { + guard + let dataProvider = dataProvider, + let candleData = dataProvider.candleData + else { return } + + guard isDrawingValuesAllowed(dataProvider: dataProvider) else { return } + + let dataSets = candleData.dataSets + + let phaseY = animator.phaseY + + var pt = CGPoint() + + for i in 0 ..< dataSets.count { + guard let dataSet = dataSets[i] as? IBarLineScatterCandleBubbleChartDataSet + else { continue } + + let valueFont = dataSet.valueFont + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + let valueToPixelMatrix = trans.valueToPixelMatrix + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + let lineHeight = valueFont.lineHeight + let yOffset: CGFloat = lineHeight + 5.0 + + for j in stride(from: _xBounds.min, through: _xBounds.range + _xBounds.min, by: 1) { + guard let e = dataSet.entryForIndex(j) as? CandleChartDataEntry else { break } + + guard e.high == maxValue || e.low == minValue else { continue } + + pt.x = CGFloat(e.x) + if e.high == maxValue { + pt.y = CGFloat(e.high * phaseY) + } else if e.low == minValue { + pt.y = CGFloat(e.low * phaseY) + } + pt = pt.applying(valueToPixelMatrix) + + if !viewPortHandler.isInBoundsRight(pt.x) { + break + } + + if !viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y) { + continue + } + + if dataSet.isDrawValuesEnabled { + // In this part we draw min and max values + var textValue: String? + var align: NSTextAlignment = .center + if e.high == maxValue { + pt.y -= yOffset + textValue = "← " + String(maxValue) + align = .left + } else if e.low == minValue { + pt.y += yOffset / 5 + textValue = String(minValue) + " →" + align = .right + } + + if let textValue = textValue { + ChartUtils.drawText( + context: context, + text: textValue, + point: CGPoint( + x: pt.x, + y: pt.y), + align: align, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: dataSet.valueTextColorAt(j)]) + } + } + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Manager/BarButtonSwitchedListPresenterManager.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Manager/BarButtonSwitchedListPresenterManager.swift new file mode 100644 index 000000000..a705a93ee --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Manager/BarButtonSwitchedListPresenterManager.swift @@ -0,0 +1,59 @@ +// +// BarButtonSwitchedListPresenterManager.swift +// PresenterLib +// +// Created by John Huang on 10/12/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits + +@objc open class BarButtonSwitchedListPresenterManager: ListPresenterManager { + @objc open dynamic override var current: ListPresenter? { + didSet { + updateBarButton() + } + } + + @IBOutlet public var barButton: ButtonProtocol? { + didSet { + if barButton !== oldValue { + oldValue?.removeTarget() + barButton?.addTarget(self, action: #selector(view(_:))) + } + updateBarButton() + } + } + + private func updateBarButton() { + if let barButton = barButton { + if let icon = current?.icon { + barButton.buttonTitle = nil + barButton.buttonImage = icon + } else if let title = current?.title { + barButton.buttonTitle = title + barButton.buttonImage = nil + } else { + barButton.buttonTitle = nil + barButton.buttonImage = nil + } + } + } + + @IBAction public func view(_ sender: Any?) { + let count = presenters?.count ?? 0 + if count > 0 { + var next: Int = 0 + if let index = index { + next = index.intValue + 1 + } + if next >= count { + next = 0 + } + index = NSNumber(value: next) + } else { + index = nil + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Manager/GridPresenterManager.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Manager/GridPresenterManager.swift new file mode 100644 index 000000000..88caa272b --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Manager/GridPresenterManager.swift @@ -0,0 +1,103 @@ +// +// GridPresenterManager.swift +// PlatformParticles +// +// Created by Qiang Huang on 2/15/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits + +@objc open class GridPresenterManager: NSObject, GridPresenterManagerProtocol { + @IBOutlet var view: UIView? + private var _presenters: [GridPresenter]? + @IBOutlet public var loadingPresenter: GridPresenter? { + didSet { + loadingPresenter?.interactor = gridInteractor + } + } + + @IBOutlet public var presenters: [GridPresenter]? { + get { + return _presenters + } + set { + _presenters = newValue?.sorted(by: { (presenter1, presenter2) -> Bool in + presenter1.sequence < presenter2.sequence + }) + if let _presenters = _presenters { + for presenter in _presenters { + presenter.visible = false + } + } + if let count = _presenters?.count { + index = (count > 0) ? 0 : nil + } else { + index = nil + } + } + } + + @IBOutlet public var gridInteractor: GridInteractor? { + didSet { + loadingPresenter?.interactor = gridInteractor + current?.interactor = gridInteractor + } + } + + private var switching: Bool = false + public var index: NSNumber? { + didSet { + if index != oldValue { + if let index = index { + current = presenters?[index.intValue] + } else { + current = nil + } + } + } + } + + public var current: GridPresenter? { + didSet { + if current != oldValue { + animateSwitch(from: oldValue, to: current) + } + } + } + + public func animateSwitch(from oldValue: GridPresenter?, to newValue: GridPresenter?) { + if !switching { + switching = true + UIView.animate(view, type: .flip, direction: .left, duration: UIView.defaultAnimationDuration, animations: { + oldValue?.visible = false + oldValue?.interactor = nil + newValue?.visible = true + newValue?.interactor = self.gridInteractor + self.switching = false + if self.current != newValue { + self.animateSwitch(from: newValue, to: self.current) + } + }, completion: nil) + } + } + + open func updateLayout() { + if let presenters = presenters { + for presenter in presenters { + presenter.updateLayout() + } + } + } + + open func show(view: String?) { + if let view = view { + if let index = presenters?.first(where: { (presenter) -> Bool in + presenter.title == view + }) { + current = index + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Manager/ListLoadingPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Manager/ListLoadingPresenter.swift new file mode 100644 index 000000000..5be5c5911 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Manager/ListLoadingPresenter.swift @@ -0,0 +1,56 @@ +// +// File.swift +// ParticlesKit +// +// Created by Qiang Huang on 12/23/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits + +@objc open class ListLoadingPresenter: ListPresenter { + @IBOutlet var view: ViewProtocol? + @IBOutlet var spinner: SpinnerProtocol? + @IBOutlet var label: LabelProtocol? + @IBInspectable public var loadingText: String? + @IBInspectable public var noDataText: String? + + open override var interactor: ListInteractor? { + didSet { + if interactor !== oldValue { + let loadingKeyPath = #keyPath(ListInteractor.loading) + let itemsKeyPath = #keyPath(ListInteractor.list) + changeObservation(from: oldValue, to: interactor, keyPath: loadingKeyPath) { [weak self] _, _, change, _ in + self?.updateLoading() + } + changeObservation(from: oldValue, to: interactor, keyPath: itemsKeyPath) { [weak self] _, _, change, _ in + self?.updateLoading() + } + } + } + } + + open override func awakeFromNib() { + super.awakeFromNib() + DispatchQueue.main.async {[weak self] in + self?.updateLoading() + } + } + + open func updateLoading() { + if let items = interactor?.list { + if items.count > 0 { + view?.visible = false + } else { + spinner?.spinning = false + label?.text = noDataText?.localized + view?.visible = true + } + } else { + spinner?.spinning = true + label?.text = loadingText?.localized + view?.visible = true + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Manager/ListPresenterManager.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Manager/ListPresenterManager.swift new file mode 100644 index 000000000..fbbf8c661 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Manager/ListPresenterManager.swift @@ -0,0 +1,183 @@ +// +// ListPresenterManager.swift +// PresenterLib +// +// Created by John Huang on 10/12/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits + +@objc open class ListPresenterManager: NSObject, ListPresenterManagerProtocol { + @IBOutlet public var view: UIView? + private var _presenters: [ListPresenter]? + @IBOutlet public var loadingPresenter: ListPresenter? { + didSet { + loadingPresenter?.interactor = listInteractor + } + } + + @IBOutlet open var presenters: [ListPresenter]? { + get { + return _presenters + } + set { + _presenters = newValue?.sorted(by: { (presenter1, presenter2) -> Bool in + presenter1.sequence < presenter2.sequence + }) + if let _presenters = _presenters { + for presenter in _presenters { + presenter.visible = false + } + } + if let count = _presenters?.count { + index = (count > 0) ? 0 : nil + } else { + index = nil + } + } + } + + @IBOutlet open var listInteractor: ListInteractor? { + didSet { + loadingPresenter?.interactor = listInteractor + current?.interactor = list(for: current) + } + } + + @IBInspectable open var flat: Bool = false { + didSet { + if flat != oldValue { + updateFlat() + } + } + } + + @IBInspectable open var stacked: Bool = false { + didSet { + if stacked != oldValue { + updateFlat() + } + } + } + + @IBOutlet open var flatConstraints: [NSLayoutConstraint]? { + didSet { + updateFlat() + } + } + + @IBOutlet open var stackedConstraints: [NSLayoutConstraint]? { + didSet { + updateFlat() + } + } + + public var switching: Bool = false + public var index: NSNumber? { + didSet { + if index != oldValue { + if let index = index { + current = presenters?[index.intValue] + } else { + current = nil + } + } + } + } + + @objc open dynamic var current: ListPresenter? { + didSet { + if current != oldValue { + animateSwitch(from: oldValue, to: current) + } + } + } + + open func animateSwitch(from oldValue: ListPresenter?, to newValue: ListPresenter?) { + if !switching { + switching = true + UIView.animate(view, type: .flip, direction: .left, duration: UIView.defaultAnimationDuration, animations: { + self.set(listPresenter: oldValue, visible: false) + self.set(listPresenter: newValue, visible: true) + self.switching = false + if self.current != newValue { + self.animateSwitch(from: newValue, to: self.current) + } + }, completion: nil) + } + } + + open func set(listPresenter: ListPresenter?, visible: Bool) { + if let listPresenter = listPresenter, listPresenter.visible != visible { + listPresenter.visible = visible + if visible { + listPresenter.interactor = list(for: listPresenter) + } + } + } + + open func list(for presenter: ListPresenter?) -> ListInteractor? { + return listInteractor + } + + open func updateLayout() { + if let presenters = presenters { + for presenter in presenters { + presenter.updateLayout() + } + } + } + + open func updateFlat() { + var horizontalPriority: Float = 749 + var veriticalPriority: Float = 749 + if flat { + if stacked { + veriticalPriority = 751 + } else { + horizontalPriority = 751 + } + } + if let constraints = flatConstraints { + for constraint in constraints { + constraint.priority = UILayoutPriority(rawValue: horizontalPriority) + } + } + if let constraints = stackedConstraints { + for constraint in constraints { + constraint.priority = UILayoutPriority(rawValue: veriticalPriority) + } + } + if let presenters = presenters { + for presenter in presenters { + if flat { + set(listPresenter: presenter, visible: true) + } else { + set(listPresenter: presenter, visible: presenter == current) + } + } + } + } + + open func show(view: String?) { + if let view = view { + if let index = presenters?.first(where: { (presenter) -> Bool in + presenter.title == view + }) { + current = index + } + } + } + + open func show(presenter: ListPresenter?) { + if let presenter = presenter { + if let index = presenters?.first(where: { (item) -> Bool in + item === presenter + }) { + current = index + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Stack/StackViewListPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Stack/StackViewListPresenter.swift new file mode 100644 index 000000000..9d575a138 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Stack/StackViewListPresenter.swift @@ -0,0 +1,52 @@ +// +// StackViewListPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 10/30/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits +import Utilities + +open class StackViewListPresenter: XibListPresenter { + @IBOutlet public var stack: UIStackView? + + override open var title: String? { + return "List" + } + + override open var icon: UIImage? { + return UIImage.named("view_carousel", bundles: Bundle.particles) + } + + override open func update() { + let firstContent = (current == nil) + current = pending + refresh(animated: true) { [weak self] in + self?.updateCompleted(firstContent: firstContent) + } + } + + override open func refresh(animated: Bool, completion: (() -> Void)?) { + if let subviews = stack?.arrangedSubviews { + for subview in subviews { + stack?.removeArrangedSubview(subview) + subview.removeFromSuperview() + } + } + if let list = interactor?.list { + for object in list { + if let xib = xib(object: object), let subview: UIView = XibLoader.load(from: xib) { + if let presenterview = subview as? ObjectPresenterView { + presenterview.model = object + presenterview.layoutIfNeeded() + } + stack?.addArrangedSubview(subview) + } + } + } + completion?() + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Table/LikedTableViewListPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Table/LikedTableViewListPresenter.swift new file mode 100644 index 000000000..c59ad15ee --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Table/LikedTableViewListPresenter.swift @@ -0,0 +1,72 @@ +// +// LikedTableViewListPresenter.swift +// PresenterLib +// +// Created by Qiang Huang on 11/9/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import Utilities +import ParticlesCommonModels + +open class LikedTableViewListPresenter: TableViewListPresenter { + @IBInspectable public var likePath: String? + @IBInspectable public var dislikePath: String? + + @IBOutlet open var likedManager: LikedObjectsProtocol? + + func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + if let entity = object(indexPath: indexPath), let key = entity.key, let actions = swipeActions(key: key) { + let swipeConfig = UISwipeActionsConfiguration(actions: actions) + return swipeConfig + } + return nil + } + + open func swipeActions(key: String?) -> [UIContextualAction]? { + if let key = key { + if let like = likeAction(key: key) { + var actions = [like] + if let dislike = dislikeAction(key: key) { + actions.append(dislike) + } + return actions + } + } + return nil + } + + open func likeAction(key: String) -> UIContextualAction? { + if let path = likePath { + let likeAction = UIContextualAction(style: .normal, title: "Like") { /* [weak self] */ _, _, completionHandler in + Router.shared?.navigate(to: RoutingRequest(path: path, params: ["key": key]), animated: false, completion: { _, _ in + completionHandler(true) + }) + } + let liked = (likedManager ?? (interactor as? FilteredListInteractor)?.liked)?.liked(key: key) ?? false + likeAction.image = UIImage(systemName: liked ? "star.fill" : "star") + return likeAction + } else { + return nil + } + } + + open func dislikeAction(key: String) -> UIContextualAction? { + if let path = dislikePath { + let dislikeAction = UIContextualAction(style: .normal, title: "Dislike") { /* [weak self] */ _, _, completionHandler in + + Router.shared?.navigate(to: RoutingRequest(path: path, params: ["key": key]), animated: false, completion: { _, _ in + completionHandler(true) + }) + } + let disliked = (likedManager ?? (interactor as? FilteredListInteractor)?.liked)?.disliked(key: key) ?? false + dislikeAction.title = disliked ? "It's\nOK" : "Not\nInterested" + dislikeAction.backgroundColor = UIColor.red + return dislikeAction + } else { + return nil + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewHeaderXibRegister.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewHeaderXibRegister.swift new file mode 100644 index 000000000..4ce6d6d81 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewHeaderXibRegister.swift @@ -0,0 +1,19 @@ +// +// TableViewHeaderXibRegister.swift +// PlatformParticles +// +// Created by John Huang on 3/8/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import ParticlesKit +import Utilities + +internal class TableViewHeaderXibRegister: NSObject & XibRegisterProtocol { + internal weak var tableView: UITableView? + internal var registeredXibs: Set = [] + + internal func register(xib: String) { + tableView?.register(XibTableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: xib) + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewListPresenter+Actions.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewListPresenter+Actions.swift new file mode 100644 index 000000000..0bffa63ba --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewListPresenter+Actions.swift @@ -0,0 +1,33 @@ +// +// TableViewListPresenter+Actions.swift +// PlatformParticles +// +// Created by Qiang Huang on 10/18/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import UIToolkits + +extension TableViewListPresenter { + public func action(request: RoutingRequest?, text: String?, image: String?, color: String?, tint: String?) -> UIContextualAction? { + return action(request: request, text: text, image: image, color: ColorPalette.shared.color(system: color), tint: ColorPalette.shared.color(system: tint)) + } + + public func action(request: RoutingRequest?, text: String?, image: String?, color: UIColor?, tint: UIColor?) -> UIContextualAction? { + if let request = request { + let action = UIContextualAction(style: .normal, title: text) {[weak self] _, _, _ in + self?.tableView?.setEditing(false, animated: true) + Router.shared?.navigate(to: request, animated: true, completion: { _, _ in + }) + } + if let image = image { + action.image = UIImage.named(image, bundles: Bundle.particles)?.tint(color: tint) + } + action.backgroundColor = color + return action + } + return nil + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewListPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewListPresenter.swift new file mode 100644 index 000000000..ba2ede7b8 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewListPresenter.swift @@ -0,0 +1,622 @@ +// +// TableViewListPresenter.swift +// PresenterLib +// +// Created by John Huang on 10/10/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Differ +import ParticlesKit +import UIKit +import UIToolkits +import Utilities + +open class TableViewListPresenter: XibListPresenter, UITableViewDataSource, UITableViewDelegate, ScrollingProtocol { + @IBInspectable @objc public dynamic var upsideDown: Bool = false { + didSet { + if upsideDown != oldValue { + orientTableView() + } + } + } + + @IBInspectable @objc public dynamic var autoScroll: Bool = false + @objc public dynamic var isAtEnd: Bool = true + @objc public dynamic var hasPendingUpdate: Bool = false + @objc public dynamic var isScrolling: Bool = false { + didSet { + didSetIsScrolling(oldValue: oldValue) + } + } + + private var atEndDebouncer: Debouncer = Debouncer() + + @IBInspectable var parallax: Bool = false { + didSet { + tableViewXibRegister.parallax = parallax + } + } + + override open var selectionHandler: SelectionHandlerProtocol? { + didSet { + if selectionHandler !== oldValue { + for section in sections { + section.tableView = tableView + section.selectionHandler = selectionHandler + } + } + } + } + + @IBInspectable var pullDownToRefresh: Bool = false { + didSet { + if pullDownToRefresh != oldValue { + refreshControl = pullDownToRefresh ? UIRefreshControl() : nil + } + } + } + + open var refreshControl: UIRefreshControl? { + didSet { + if refreshControl !== oldValue { + if let refreshControl = refreshControl { + refreshControl.addTarget(self, action: #selector(pullDownToRefresh(_:)), for: .valueChanged) + } + tableView?.refreshControl = refreshControl + } + } + } + + @IBInspectable var disclosure: Bool = false { + didSet { + if let cells = tableView?.visibleCells { + for cell in cells { + cell.accessoryType = accessory(for: cell) + } + } + } + } + + @IBInspectable public var cellBackgroundColor: UIColor? + @IBInspectable var sectionHeaderXib: String? + @IBInspectable var sectionFooterXib: String? + @IBInspectable var headerXib: String? + @IBInspectable var footerXib: String? + @IBInspectable var intMode: Int { + get { return mode.rawValue } + set { mode = PresenterMode(rawValue: newValue) ?? .linear } + } + + @IBInspectable var animatedChange: Bool = false { + didSet { + if animatedChange != oldValue { + for section in sections { + section.animatedChange = animatedChange + } + } + } + } + + @IBInspectable var scrollToSelection: Bool = false { + didSet { + if scrollToSelection != oldValue { + for section in sections { + section.scrollToSelection = scrollToSelection && visible + } + } + } + } + + override open var visible: Bool { + didSet { + if visible != oldValue { + for section in sections { + section.scrollToSelection = scrollToSelection && visible + } + } + } + } + + @IBOutlet open var tableView: UITableView? { + didSet { + if tableView !== oldValue { + oldValue?.dataSource = nil + oldValue?.delegate = nil + tableView?.dataSource = self + tableView?.delegate = self + tableViewXibRegister.tableView = tableView + tableViewHeaderXibRegister.tableView = tableView + for section in sections { + section.tableView = tableView + section.selectionHandler = selectionHandler + } + if interactor != nil { + refresh(animated: false) { [weak self] in + self?.updateCompleted(firstContent: true) + } + } + if let headerView: UIView = XibLoader.load(from: headerXib) { + headerView.autoresizingMask = .flexibleWidth + headerView.translatesAutoresizingMaskIntoConstraints = true + tableView?.tableHeaderView = headerView + } + if let footerView: UIView = XibLoader.load(from: footerXib) { + footerView.autoresizingMask = .flexibleWidth + footerView.translatesAutoresizingMaskIntoConstraints = true + tableView?.tableFooterView = footerView + } + tableView?.refreshControl = refreshControl + tableView?.sectionHeaderTopPadding = 0 + orientTableView() + } + } + } + + private var debouncer: Debouncer = Debouncer() + + public var mode: PresenterMode = .linear { + didSet { + if mode != oldValue { + if let handler = updateDebouncer.debounce() { + handler.run({ [weak self] in + if let self = self { + self.current = self.pending + self.refresh(animated: true) { [weak self] in + self?.updateCompleted(firstContent: false) + } + } + }, delay: nil) + } + } + } + } + + private var tableViewXibRegister: TableViewXibRegister = TableViewXibRegister() + private var tableViewHeaderXibRegister: TableViewHeaderXibRegister = TableViewHeaderXibRegister() + + open var sections: [TableViewSectionListPresenter] = [] + + override open var title: String? { + return "List" + } + + override open var icon: UIImage? { + return UIImage.named("view_list", bundles: Bundle.particles) + } + + override open func didSetInteractor(oldValue: ListInteractor?) { + super.didSetInteractor(oldValue: oldValue) + + if interactor != oldValue { + switch mode { + case .linear: + sections.removeAll() + if let interactor = interactor { + sections.append(section(for: interactor, index: 0)) + } + refresh(animated: true) { [weak self] in + self?.updateCompleted(firstContent: true) + } + + case .sections: + break + } + } + } + + internal func section(for interactor: ListInteractor, index: Int) -> TableViewSectionListPresenter { + let section = self.section(with: interactor) ?? TableViewSectionListPresenter() + if section.xibRegister !== tableViewXibRegister { + section.xibRegister = tableViewXibRegister + } + if section.headerXibRegister !== tableViewHeaderXibRegister { + section.headerXibRegister = tableViewHeaderXibRegister + } + if section.xibMap != xibMap { + section.xibMap = xibMap + } + if section.sequence != index { + section.sequence = index + } + if section.selectionHandler !== selectionHandler { + section.selectionHandler = selectionHandler + } + if section.sectionHeaderXib != sectionHeaderXib { + section.sectionHeaderXib = sectionHeaderXib + } + if section.sectionFooterXib != sectionFooterXib { + section.sectionFooterXib = sectionFooterXib + } + section.animatedChange = animatedChange + section.scrollToSelection = scrollToSelection + section.autoScroll = autoScroll + if section.tableView !== tableView { + section.tableView = tableView + } + if section.interactor !== interactor { + section.interactor = interactor + } + return section + } + + internal func section(with interactor: ListInteractor) -> TableViewSectionListPresenter? { + return sections.first(where: { (presenter) -> Bool in + presenter.interactor === interactor + }) + } + + open func object(indexPath: IndexPath) -> ModelObjectProtocol? { + switch mode { + case .linear: + return sections.first?.object(at: indexPath.row) + + case .sections: + return sections.at(indexPath.section)?.object(at: indexPath.row) + } + } + + override open func refresh(animated: Bool, completion: (() -> Void)?) { + switch mode { + case .sections: + if let list = current as? [ListInteractor] { + var index = 0 + sections = list.map({ (child: ListInteractor) -> TableViewSectionListPresenter in + let tableSection = section(for: child, index: index) + index += 1 + return tableSection + }) + } else { + sections = [] + } + + case .linear: + sections.first?.current = current + } + + refreshControl?.endRefreshing() + UIView.animate(tableView, type: animated ? .fade : .none, direction: .none, duration: UIView.defaultAnimationDuration, animations: { [weak self] in + if let self = self { + self.tableView?.reloadData() + if self.autoScroll { + self.scrollToEnd(animated: false) + } + completion?() + } + }, completion: nil) + } + + open func numberOfSections(in tableView: UITableView) -> Int { + if interactor == nil { + return 0 + } else { + return mode == .sections ? sections.count : 1 + } + } + + open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if mode == .sections { + let sectionPresenter = sections[section] + return sectionPresenter.count ?? 0 + } else { + return sections.first?.count ?? 0 + } + } + + open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if let section = (mode == .sections) ? sections[indexPath.section] : sections.first { + let cell = section.cell(indexPath: indexPath) ?? UITableViewCell() + if tableView.estimatedRowHeight <= 0 { + tableView.estimatedRowHeight = cell.frame.size.height + } + cell.accessoryType = accessory(for: cell) + if let cellBackgroundColor = cellBackgroundColor { + cell.backgroundColor = cellBackgroundColor + } + if ((cell as? ObjectPresenterTableViewCell)?.presenterView as? ObjectPresenterProtocol)?.selectable ?? false { + cell.selectionStyle = tableView.allowsMultipleSelection ? .none : .default + } else { + cell.selectionStyle = .none + } + return cell + } else { + return UITableViewCell() + } + } + + open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let cell = tableView.cellForRow(at: indexPath) { + UserInteraction.shared.sender = cell + } + if let section = (mode == .sections) ? sections[indexPath.section] : sections.first { + section.select(to: indexPath) + } + } + + public func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { + if let section = (mode == .sections && indexPath.section < sections.count) ? sections[indexPath.section] : sections.first { + section.deselect(indexPath: indexPath) + } + } + + open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return UITableView.automaticDimension + } + + open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + return UITableView.automaticDimension +// if let section = (mode == .sections && indexPath.section < sections.count) ? sections[indexPath.section] : sections.first { +// if let size = section.defaultSize(at: indexPath.row) { +// return size.height +// } +// } +// return 44 + } + + open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + if let tableSection = (mode == .sections) ? sections[section] : sections.first { + return tableSection.headerViewSize() ?? defaultSize(xib: tableSection.sectionHeaderXib)?.height ?? 0 + } + return CGFloat.leastNormalMagnitude + } + + open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if let tableSection = (mode == .sections) ? sections[section] : sections.first { + return tableSection.headerView() ?? accessoryView(xib: tableSection.sectionHeaderXib, section: section) + } + return nil + } + + open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + if let tableSection = (mode == .sections) ? sections[section] : sections.first { + if let footerView = tableSection.footerView() { + return footerView.frame.size.height + } else if let size = defaultSize(xib: tableSection.sectionFooterXib) { + return size.height + } + } + return CGFloat.leastNormalMagnitude + } + + open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + if let tableSection = (mode == .sections) ? sections[section] : sections.first { + return tableSection.footerView() ?? accessoryView(xib: tableSection.sectionFooterXib, section: section) + } + return nil + } + + open func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { + return true + } + + open func accessoryView(xib: String?, section: Int) -> UIView? { + if let view: UIView = XibLoader.load(from: xib) { + if let presenterView: ObjectPresenterView = view as? ObjectPresenterView { + if mode == .linear { + presenterView.model = interactor?.parent + } else { + presenterView.model = object(at: section) + } + } + return view + } + return nil + } + + open func accessory(for cell: UITableViewCell) -> UITableViewCell.AccessoryType { + return showDisclosure(cell: cell) ? .disclosureIndicator : .none + } + + open func showDisclosure(cell: UITableViewCell) -> Bool { + if let presentingCell = cell as? ObjectPresenterTableViewCell { + return presentingCell.showDisclosure ?? disclosure + } + return disclosure + } + + private func didSetIsScrolling(oldValue: Bool) { + if isScrolling != oldValue { + Console.shared.log(isScrolling ? "TableView scrolling" : "TableView not scrolling") + if !isScrolling && hasPendingUpdate { + hasPendingUpdate = false + update() + } + } + } + + override open func update() { + if tableView != nil { + if isScrolling { + hasPendingUpdate = true + } else { + let firstContent = (current == nil) + if mode == .sections { + current = pending + refresh(animated: true) { [weak self] in + self?.updateCompleted(firstContent: firstContent) + } + } else { + update(move: true) + updateCompleted(firstContent: firstContent) + } + } + } else { + current = pending + } + } + + override open func update(diff: Diff, patches: [Patch], current: [ModelObjectProtocol]?) { + tableView?.performBatchUpdates({ + for change in patches { + switch change { + case let .deletion(index): + tableView?.deleteSections(IndexSet(integer: index), with: .fade) + + case let .insertion(index: index, element: _): + tableView?.insertSections(IndexSet(integer: index), with: .fade) + } + } + }, completion: nil) + } + + override open func updateLayout() { + if let handler = debouncer.debounce() { + handler.run({ [weak self] in + if let self = self { + self.tableView?.beginUpdates() + self.tableView?.endUpdates() + Console.shared.log("endUpdate/updateLayout") + } + }, delay: 0.5) + } + } + + @objc open func pullDownToRefresh(_ sender: Any?) { + } + + override open func changed(selected: [ModelObjectProtocol]?) { + if let tableSection = (mode == .sections) ? nil : sections.first { + tableSection.changed(selected: selected) + } + } + + override open func visibleIndice() -> [Int]? { + if let indexPaths = tableView?.indexPathsForVisibleRows, indexPaths.count > 0 { + var indice = [Int]() + for indexPath in indexPaths { + if mode == .sections { + if !indice.contains(indexPath.section) { + indice.append(indexPath.section) + } + } else { + indice.append(indexPath.row) + } + } + return indice + } + return nil + } + + override open func selectScrollTo(index: Int, completion: @escaping () -> Void) { + if mode == .sections { + tableView?.scrollToRow(at: IndexPath(row: NSNotFound, section: index), at: .top, animated: true) + } else { + tableView?.scrollToRow(at: IndexPath(row: index, section: 0), at: .top, animated: true) + } + DispatchQueue.main.asyncAfter(deadline: .now() + UIView.defaultAnimationDuration) { + completion() + } + } +} + +extension TableViewListPresenter { + open func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + if upsideDown { + cell.contentView.transform = CGAffineTransform(scaleX: 1, y: -1) + } + if parallax { + (cell as? ParallaxObjectPresenterTableViewCell)?.parallax(animated: true) + } + } + + private func lastIndexPath() -> IndexPath? { + if let lastIndex = sections.lastIndex(where: { (section) -> Bool in + (section.current?.count ?? 0) > 0 + }), let last = sections.at(lastIndex), let count = last.current?.count, count > 0 { + return IndexPath(item: count - 1, section: lastIndex) + } else { + return nil + } + } + + public func isTableAtEnd() -> Bool { + if let indexPath = lastIndexPath() { + if let visible = tableView?.indexPathsForVisibleRows { + return visible.contains(indexPath) + } + } + return true + } + + public func scrollToEnd(animated: Bool) { + if animated { + DispatchQueue.main.async { [weak self] in + if let self = self { + if let indexPath = self.lastIndexPath() { + self.tableView?.scrollToRow(at: indexPath, at: .bottom, animated: animated) + } + } + } + } else { + if let indexPath = lastIndexPath() { + tableView?.scrollToRow(at: indexPath, at: .bottom, animated: animated) + scrollToEnd(animated: true) + } + } + } +} + +extension TableViewListPresenter: UIScrollViewDelegate { + private func calculateAtEnd() { +// let handler = atEndDebouncer.debounce() +// handler?.run({ [weak self] in +// if let self = self { +// self.isAtEnd = self.isTableAtEnd() +// } +// }, delay: 0.25) + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + if parallax { + if let visibleCells = tableView?.visibleCells { + for cell in visibleCells { + (cell as? ParallaxObjectPresenterTableViewCell)?.parallax(animated: true) + } + } + } + calculateAtEnd() + } + + public func scrollViewDidZoom(_ scrollView: UIScrollView) { + calculateAtEnd() + } + + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + isScrolling = true + } + + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + calculateAtEnd() + if !decelerate { scrollViewDidEndScrolling(scrollView) } + } + + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + calculateAtEnd() + selectCurrent() + scrollViewDidEndScrolling(scrollView) + } + + public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + calculateAtEnd() + selectCurrent() + scrollViewDidEndScrolling(scrollView) + } + + public func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { + calculateAtEnd() + } + + public func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) { + calculateAtEnd() + } + + public func scrollViewDidEndScrolling(_ scrollView: UIScrollView) { + isScrolling = false + } + + private func orientTableView() { + if upsideDown { + tableView?.transform = CGAffineTransform(scaleX: 1, y: -1) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewSectionListPresenter.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewSectionListPresenter.swift new file mode 100644 index 000000000..a0516a395 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewSectionListPresenter.swift @@ -0,0 +1,270 @@ +// +// TableViewSectionListPresenter.swift +// PresenterLib +// +// Created by John Huang on 10/9/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Differ +import ParticlesKit +import UIToolkits +import Utilities +import UIKit + +open class TableViewSectionListPresenter: XibListPresenter { + @IBOutlet var tableView: UITableView? + @IBInspectable var sectionHeaderXib: String? + @IBInspectable var sectionFooterXib: String? + @IBInspectable var animatedChange: Bool = false + @IBInspectable var scrollToSelection: Bool = false { + didSet { + if scrollToSelection != oldValue { + if scrollToSelection { + if let pendingScroll = pendingScroll { + if let handler = scrollDebouncer.debounce() { + handler.run({ [weak self] in + self?.scroll(to: pendingScroll) + self?.pendingScroll = nil + }, delay: 0.51) + } + } + } + } + } + } + + private var header: UIView? + public var autoScroll: Bool = false + private var pendingScroll: ModelObjectProtocol? + + override open func update() { + if tableView != nil { + update(move: true) + } else { + current = pending + } + } + + override open func update(diff: ExtendedDiff, updateData: () -> Void) { + let wasAtEnd = isTableAtEnd() + updateData() + if animatedChange { + if let tableView = tableView, + sequence < tableView.numberOfSections { + let animation: DiffRowAnimation = animatedChange ? .fade : .none + tableView.apply(diff, deletionAnimation: animation, insertionAnimation: animation, indexPathTransform: { (indexPath) -> IndexPath in + IndexPath(row: indexPath.row, section: self.sequence) + }) + } + if wasAtEnd && autoScroll { + scrollToEnd(animated: true) + } + } else { + tableView?.reloadData() + if wasAtEnd && autoScroll { + scrollToEnd(animated: false) + } + } + header?.bringToFront() + } + + override open func update(diff: Diff, patches: [Patch], current: [ModelObjectProtocol]?) { + let wasAtEnd = isTableAtEnd() + super.update(diff: diff, patches: patches, current: current) + if wasAtEnd && autoScroll { + scrollToEnd(animated: true) + } + } + + private func lastIndexPath() -> IndexPath? { + if let count = current?.count, count > 0 { + return IndexPath(item: count - 1, section: sequence) + } else { + return nil + } + } + + public func isTableAtEnd() -> Bool { + if let indexPath = lastIndexPath() { + if let visible = tableView?.indexPathsForVisibleRows { + return visible.contains(indexPath) + } + } + return true + } + + public func scrollToEnd(animated: Bool) { + if animated { + DispatchQueue.main.async { [weak self] in + if let self = self { + if let indexPath = self.lastIndexPath() { + self.tableView?.scrollToRow(at: indexPath, at: .bottom, animated: animated) + } + } + } + } else { + if let indexPath = lastIndexPath() { + tableView?.scrollToRow(at: indexPath, at: .bottom, animated: animated) + scrollToEnd(animated: true) + } + } + } + + override open func refresh(animated: Bool, completion: (() -> Void)?) { + if animated { + UIView.animate(tableView, type: .fade, direction: .none, duration: UIView.defaultAnimationDuration, animations: { [weak self] in + if let self = self { + self.tableView?.reloadData() + completion?() + } + }, completion: nil) + } else { + tableView?.reloadData() + completion?() + } + } + + open func cell(indexPath: IndexPath) -> UITableViewCell? { + let object = self.object(at: indexPath.row) + return cell(object: object, indexPath: indexPath) + } + + open func cell(object: ModelObjectProtocol?, indexPath: IndexPath) -> UITableViewCell? { + if let xib = xib(object: object), let tableView = tableView { + let cell = tableView.dequeueReusableCell(withIdentifier: xib, for: indexPath) + (cell as? SelectableProtocol)?.isSelected = tableView.indexPathsForSelectedRows?.contains(indexPath) ?? false + let height = cell.frame.size.height + cell.frame = CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: height) + let backgroundView = UIView() + backgroundView.backgroundColor = UIColor.clear + cell.selectedBackgroundView = backgroundView + if let presenterCell = cell as? ObjectPresenterTableViewCell { + presenterCell.setXib(xib, parentViewController: tableView.viewController()) + let selected = (object as? SelectableProtocol)?.isSelected ?? false + presenterCell.isSelected = selected + presenterCell.model = object + presenterCell.isFirst = indexPath.row == 0 + if let count = current?.count { + presenterCell.isLast = indexPath.row == count - 1 + } else { + presenterCell.isLast = false + } + } + return cell + } + return nil + } + + open func select(to indexPath: IndexPath) { + select(index: indexPath.row) { [weak self] deselect in + if deselect { + self?.tableView?.deselectRow(at: indexPath, animated: true) + } else { + (self?.tableView as? UXTableView)?.updateLayout() + } + } + } + + open func deselect(indexPath: IndexPath) { + deselect(index: indexPath.row) + (tableView as? UXTableView)?.updateLayout() + } + + open func headerView() -> UIView? { + header = headerView(interactor: interactor) + return header + } + + open func headerView(interactor: ListInteractor?) -> UIView? { + if let interactor = interactor, interactor.title != nil, let xib = headerXib(object: interactor) { + let view = tableView?.dequeueReusableHeaderFooterView(withIdentifier: xib) + (view as? XibTableViewHeaderFooterView)?.xib = xib + (view as? ObjectPresenterProtocol)?.model = interactor + return view + } + return nil + } + + open func headerViewSize() -> CGFloat? { + return headerViewSize(interactor: interactor) + } + + open func headerViewSize(interactor: ListInteractor?) -> CGFloat? { + if let interactor = interactor, interactor.title != nil, let xib = xib(object: interactor) { + return defaultSize(xib: xib)?.height + } + return nil + } + + open func footerView() -> UIView? { + return footerView(interactor: interactor) + } + + open func footerView(interactor: ListInteractor?) -> UIView? { + return nil + } + + open func footerViewSize() -> CGFloat? { + return footerViewSize(interactor: interactor) + } + + open func footerViewSize(interactor: ListInteractor?) -> CGFloat? { + return nil + } + + private var scrollDebouncer: Debouncer = Debouncer() + + override open func changed(selected: [ModelObjectProtocol]?) { + if let tableView = tableView { + let selectedIndexPaths = Set(tableView.indexPathsForSelectedRows ?? [IndexPath]()) + for i in 0 ..< (current?.count ?? 0) { + let object = self.object(at: i) + let indexPath = IndexPath(row: i, section: sequence) + if let selected = selected, selected.contains(where: { (item) -> Bool in + item === object + }) { + if !selectedIndexPaths.contains(indexPath) { + tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none) + if let handler = scrollDebouncer.debounce() { + handler.run({ [weak self] in + self?.scroll(to: object) + }, delay: 0.51) + } + } + } else { + if selectedIndexPaths.contains(indexPath) { + tableView.deselectRow(at: IndexPath(row: i, section: sequence), animated: true) + } + } + } + } + } + + open func scroll(to object: ModelObjectProtocol?) { + if scrollToSelection { + if let row = index(of: object) { + let indexPath = IndexPath(row: row, section: sequence) + if let visible = tableView?.indexPathsForVisibleRows, let first = visible.first, let last = visible.last { + if indexPath.section < first.section { + tableView?.scrollToRow(at: indexPath, at: .top, animated: true) + } else if indexPath.section > last.section { + tableView?.scrollToRow(at: indexPath, at: .bottom, animated: true) + } else if indexPath.section == first.section { + if indexPath.row < first.row { + tableView?.scrollToRow(at: indexPath, at: .top, animated: true) + } else if indexPath.row > last.row { + tableView?.scrollToRow(at: indexPath, at: .bottom, animated: true) + } else if indexPath.row == first.row { + tableView?.scrollToRow(at: indexPath, at: .middle, animated: true) + } else if indexPath.row == last.row { + tableView?.scrollToRow(at: indexPath, at: .middle, animated: true) + } + } + } + } + } else { + pendingScroll = object + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewXibRegister.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewXibRegister.swift new file mode 100644 index 000000000..875bfc510 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Table/TableViewXibRegister.swift @@ -0,0 +1,30 @@ +// +// TableViewXibRegister.swift +// PresenterLib +// +// Created by Qiang Huang on 10/11/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +internal class TableViewXibRegister: NSObject & XibRegisterProtocol { + public var parallax: Bool = false + private let kCellName = "TableViewCell" + private let kParallaxCellName = "ParallaxTableViewCell" + internal weak var tableView: UITableView? + internal var registeredXibs: Set = [] + + internal func register(xib: String) { + if parallax { + if let nib = UINib.safeLoad(xib: kParallaxCellName, bundles: Bundle.particles) { + tableView?.register(nib, forCellReuseIdentifier: xib) + } + } else { + if let nib = UINib.safeLoad(xib: kCellName, bundles: Bundle.particles) { + tableView?.register(nib, forCellReuseIdentifier: xib) + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_List Presenter/_Table/XibTableViewHeaderFooterView.swift b/PlatformParticles/PlatformParticles/_List Presenter/_Table/XibTableViewHeaderFooterView.swift new file mode 100644 index 000000000..12c1a1491 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_List Presenter/_Table/XibTableViewHeaderFooterView.swift @@ -0,0 +1,43 @@ +// +// XibTableViewHeaderFooterView.swift +// PlatformParticles +// +// Created by John Huang on 3/8/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import ParticlesKit +import UIKit +import Utilities + +@objc public class XibTableViewHeaderFooterView: UITableViewHeaderFooterView, ObjectPresenterProtocol { + public var model: ModelObjectProtocol? { + didSet { + objectPresenterView?.model = model + } + } + + @objc public dynamic var xib: String? { + didSet { + if xib != oldValue { + objectPresenterView = XibLoader.load(from: xib) + #if DEBUG + accessibilityIdentifier = "xib: \(xib ?? "")" + #endif + } + } + } + + @objc public dynamic var objectPresenterView: ObjectPresenterView? { + didSet { + if objectPresenterView !== oldValue { + oldValue?.removeFromSuperview() + if let objectPresenterView = objectPresenterView { + install(view: objectPresenterView, into: contentView) + } + backgroundColor = UIColor.clear + contentView.backgroundColor = UIColor.clear + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Messages/MessageAction.swift b/PlatformParticles/PlatformParticles/_Messages/MessageAction.swift new file mode 100644 index 000000000..99e784669 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Messages/MessageAction.swift @@ -0,0 +1,61 @@ +// +// MessageAction.swift +// MessageParticles +// +// Created by John Huang on 12/28/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Messages +import ParticlesKit +import RoutingKit +import Utilities + +open class MessageAction: NSObject, NavigableProtocol { + public weak var conversation: MSConversation? + @IBOutlet var interactor: LoadingObjectInteractor? { + didSet { + changeObservation(from: oldValue, to: interactor, keyPath: #keyPath(LoadingObjectInteractor.entity)) { [weak self] _, _, _, _ in + self?.updateMessage() + } + } + } + + public override init() { + super.init() + } + + public func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + } + + open func updateMessage() { + if let conversation = conversation, let entity = interactor?.entity as? (ModelObjectProtocol & RoutingOriginatorProtocol), let request = entity.routingRequest(), let path = request.path { + var components = URLComponents() + components.host = "go.to" + components.scheme = "retslyapp" + components.path = path + var queryItems = [URLQueryItem]() + if let params = request.params { + for param in params { + if let value = parser.asString(param.value) { + queryItems.append(URLQueryItem(name: param.key, value: value)) + } + } + components.queryItems = queryItems + } + + let layout = MSMessageTemplateLayout() + layout.caption = entity.displayTitle ?? "" + + let message = MSMessage(session: conversation.selectedMessage?.session ?? MSSession()) + message.url = components.url! + message.layout = layout + + conversation.insert(message) { error in + if let error = error { + Console.shared.log(error) + } + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Navigation/NavigationObjectPresenter.swift b/PlatformParticles/PlatformParticles/_Navigation/NavigationObjectPresenter.swift new file mode 100644 index 000000000..22f378e59 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Navigation/NavigationObjectPresenter.swift @@ -0,0 +1,98 @@ +// +// NavigationObjectPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 1/30/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import ParticlesCommonModels +import ParticlesKit +import RoutingKit +import UIToolkits + +@objc public class NavigationObjectPresenter: ObjectPresenter { + @IBOutlet public var titleLabel: LabelProtocol? + @IBOutlet public var subtitleLabel: LabelProtocol? + @IBOutlet public var textLabel: LabelProtocol? + @IBOutlet public var subtextLabel: LabelProtocol? + @IBOutlet public var colorView: ViewProtocol? + @IBOutlet public var iconView: CachedImageView? + @IBOutlet public var imageView: CachedImageView? + @IBOutlet public var actionButton: ButtonProtocol? + @IBOutlet public var childrenPresenter: ListPresenter? + @IBOutlet public var actionsPresenter: ListPresenter? + + @objc override public var model: ModelObjectProtocol? { + didSet { + changeObservation(from: oldValue, to: model, keyPath: #keyPath(NavigationModelProtocol.title)) { [weak self] _, _, _, _ in + self?.titleLabel?.text = self?.navigationObject?.title + self?.actionButton?.buttonTitle = self?.navigationObject?.title + } + changeObservation(from: oldValue, to: model, keyPath: #keyPath(NavigationModelProtocol.subtitle)) { [weak self] _, _, _, _ in + self?.subtitleLabel?.text = self?.navigationObject?.subtitle + } + changeObservation(from: oldValue, to: model, keyPath: #keyPath(NavigationModelProtocol.text)) { [weak self] _, _, _, _ in + self?.textLabel?.text = self?.navigationObject?.text + } + changeObservation(from: oldValue, to: model, keyPath: #keyPath(NavigationModelProtocol.subtext)) { [weak self] _, _, _, _ in + self?.subtextLabel?.text = self?.navigationObject?.subtext + } + changeObservation(from: oldValue, to: model, keyPath: #keyPath(NavigationModelProtocol.color)) { [weak self] _, _, _, _ in + self?.colorView?.backgroundColor = ColorPalette.shared.color(system: self?.navigationObject?.color) + } + changeObservation(from: oldValue, to: model, keyPath: #keyPath(NavigationModelProtocol.icon)) { [weak self] _, _, _, _ in + self?.iconView?.imageUrl = self?.navigationObject?.icon + } + changeObservation(from: oldValue, to: model, keyPath: #keyPath(NavigationModelProtocol.image)) { [weak self] _, _, _, _ in + self?.imageView?.imageUrl = self?.navigationObject?.image + } + changeObservation(from: oldValue, to: model, keyPath: #keyPath(NavigationModelProtocol.children)) { [weak self] _, _, _, _ in + self?.syncChildren() + } + changeObservation(from: oldValue, to: model, keyPath: #keyPath(NavigationModelProtocol.actions)) { [weak self] _, _, _, _ in + self?.syncActions() + } + } + } + + public var navigationObject: NavigationModelProtocol? { + return model as? NavigationModelProtocol + } + + @IBOutlet public var children: ListInteractor? { + didSet { + childrenPresenter?.interactor = children + } + } + + @IBOutlet public var actions: ListInteractor? { + didSet { + actionsPresenter?.interactor = actions + } + } + + private func syncChildren() { + if let children = navigationObject?.children { + let list = self.children ?? ListInteractor() + list.sync(children) + self.children = list + } else { + children = nil + } + } + + private func syncActions() { + if let actions = navigationObject?.actions { + let list = self.actions ?? ListInteractor() + list.sync(actions) + self.actions = list + } else { + actions = nil + } + } + + @IBAction func action(_ sender: Any?) { + Router.shared?.navigate(to: navigationObject?.link, completion: nil) + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/ActionPresenter.swift b/PlatformParticles/PlatformParticles/_Object Presenter/ActionPresenter.swift new file mode 100644 index 000000000..82bf7238d --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/ActionPresenter.swift @@ -0,0 +1,59 @@ +// +// ActionPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 2/10/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits +import Utilities + +public class ActionPresenter: ObjectPresenter { + open override var model: ModelObjectProtocol? { + didSet { + let dictionaryEntity = action as? DictionaryEntity + changeObservation(from: oldValue, to: dictionaryEntity, keyPath: #keyPath(DictionaryEntity.data)) { [weak self] _, _, _, animated in + self?.update() + } + } + } + + public var action: ActionProtocol? { + return model as? ActionProtocol + } + + @IBOutlet var titleLabel: LabelProtocol? + @IBOutlet var subtitleLabel: LabelProtocol? + @IBOutlet var imageView: ImageViewProtocol? + @IBOutlet var detailLabel: LabelProtocol? + @IBOutlet var detailButton: ButtonProtocol? { + didSet { + oldValue?.removeTarget() + detailButton?.addTarget(self, action: #selector(detail(_:))) + } + } + + @IBAction func detail(_ sender: Any?) { + } + + private func update() { + titleLabel?.text = action?.title?.localized + subtitleLabel?.text = action?.subtitle?.localized + if let image = action?.image { + imageView?.image = UIImage(named: image) + } else { + imageView?.image = nil + } + detailButton?.buttonTitle = action?.detail?.localized + detailLabel?.text = action?.detail?.localized + if let _ = action?.detailRouting { + detailLabel?.visible = false + detailButton?.visible = true + } else { + detailButton?.visible = false + detailLabel?.visible = true + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/ColoredClusterPresenter.swift b/PlatformParticles/PlatformParticles/_Object Presenter/ColoredClusterPresenter.swift new file mode 100644 index 000000000..adb44ba10 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/ColoredClusterPresenter.swift @@ -0,0 +1,147 @@ +// +// ColoredClusterPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 2/4/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Charts +import ParticlesKit +import UIToolkits + +public struct ColorCount { + var color: UIColor? + var count: Int = 0 +} + +open class ColoredClusterPresenter: ObjectPresenter { + @IBOutlet public var view: UIView? + @IBOutlet public var pie: PieChartView? { + didSet { + if pie !== oldValue { + pie?.holeRadiusPercent = 0.70 + pie?.legend.enabled = false + pie?.visible = false + } + } + } + + @IBOutlet var countLabel: UILabel? + @IBOutlet public var diameterConstraint: NSLayoutConstraint? + @IBInspectable var autoResizing: Bool = false + + override open var model: ModelObjectProtocol? { + didSet { + if model !== oldValue { + cluster = model as? ClusteredModelObjectProtocol + } + } + } + + open var cluster: ClusteredModelObjectProtocol? { + didSet { + if cluster !== oldValue { + updateCluster() + } + } + } + + open var colors: [String: Int]? { + return nil + } + + open var colorCounts: [ColorCount]? { + if let colors = colors { + var colorCounts = [ColorCount]() + + for (key, value) in colors { + if let count = parser.asInt(value) { + var colorCount = ColorCount() + colorCount.color = ColorPalette.shared.color(text: key) + colorCount.count = count + colorCounts.append(colorCount) + } + } + return colorCounts.sorted { (colorCount1, colorCount2) -> Bool in + if let color1 = colorCount1.color, let color2 = colorCount2.color { + var gray1: CGFloat = 0 + var gray2: CGFloat = 0 + var alpha1: CGFloat = 0 + var alpha2: CGFloat = 0 + color1.getWhite(&gray1, alpha: &alpha1) + color2.getWhite(&gray2, alpha: &alpha2) + if gray1 == gray2 { + return alpha1 > alpha2 + } else { + return gray1 < gray2 + } + } + return false + } + } + return nil + } + + public var count: Int { + var count: Int = 0 + if let colorCounts = colorCounts { + for colorCount in colorCounts { + count += colorCount.count + } + } + return count + } + + public var chart: PieChartData? { + didSet { + if chart !== oldValue { + if let chart = chart { + UIView.animate(pie, type: .fade, direction: .none, duration: UIView.defaultAnimationDuration, animations: { + self.pie?.data = chart + self.pie?.visible = true + }, completion: nil) + } else { + pie?.visible = false + } + } + } + } + + open func updateCluster() { + let count = self.count + if count > 0, let colorCounts = colorCounts { + countLabel?.text = "\(count)" + var pieEntries = [PieChartDataEntry]() + + var segments = [UIColor]() + for colorCount in colorCounts { + if let color = colorCount.color { + pieEntries.append(PieChartDataEntry(value: Double(colorCount.count) / Double(count))) + segments.append(color) + } + } + let set = PieChartDataSet(entries: pieEntries, label: nil) + set.colors = segments + set.drawIconsEnabled = false + set.drawValuesEnabled = false + set.sliceSpace = 1 + set.selectionShift = 0 + + chart = PieChartData(dataSet: set) + + if autoResizing { + let diameter = min(40, log2(CGFloat(count)) * 2 + 32.0) + diameterConstraint?.constant = diameter + view?.corner = diameter / 2.0 + } + } else { + countLabel?.text = nil + chart = nil + if autoResizing { + diameterConstraint?.constant = 40 + view?.corner = 20 + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/CompositeObjectPresenter.swift b/PlatformParticles/PlatformParticles/_Object Presenter/CompositeObjectPresenter.swift new file mode 100644 index 000000000..6c709c0d1 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/CompositeObjectPresenter.swift @@ -0,0 +1,33 @@ +// +// CompositeObjectPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 10/23/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits + +@objc open class CompositeObjectPresenter: ObjectPresenter { + @IBOutlet var childPresenters: [ObjectPresenter]? { + didSet { + changeComposition() + } + } + + @IBOutlet var view: UIView? + + override open func didSetModel(oldValue: ModelObjectProtocol?) { + super.didSetModel(oldValue: oldValue) + changeComposition() + } + + public func changeComposition() { + if let childPresenters = childPresenters { + for childPresenter in childPresenters { + childPresenter.model = model + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/LikedSmartObjectPresenter.swift b/PlatformParticles/PlatformParticles/_Object Presenter/LikedSmartObjectPresenter.swift new file mode 100644 index 000000000..df5aa0d19 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/LikedSmartObjectPresenter.swift @@ -0,0 +1,93 @@ +// +// LikedSmartObjectPresenter.swift +// PresenterLib +// +// Created by Qiang Huang on 11/6/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import UIToolkits + +@objc open class LikedSmartObjectPresenter: SmartObjectPresenter { + @IBInspectable public var likePath: String? + @IBInspectable public var dislikePath: String? + + @IBOutlet public var likeButton: ButtonProtocol? { + didSet { + if likeButton !== oldValue { + oldValue?.removeTarget() + likeButton?.addTarget(self, action: #selector(like(_:))) + } + } + } + + @IBOutlet public var dislikeButton: ButtonProtocol? { + didSet { + if dislikeButton !== oldValue { + oldValue?.removeTarget() + dislikeButton?.addTarget(self, action: #selector(dislike(_:))) + } + } + } + + open var likedManager: LikedObjectsProtocol? { + didSet { + changeObservation(from: oldValue, to: likedManager, keyPath: #keyPath(LikedObjectsProtocol.liked)) { [weak self] _, _, _, animated in + self?.updateLiked(animated: animated) + } + + changeObservation(from: oldValue, to: likedManager, keyPath: #keyPath(LikedObjectsProtocol.disliked)) { [weak self] _, _, _, animated in + self?.updateDisliked(animated: animated) + } + } + } + + override open func update(layout: Bool) { + super.update(layout: layout) + + updateLiked(animated: false) + updateDisliked(animated: false) + } + + open func updateLiked(animated: Bool) { + if let likeButton = likeButton, let key = model?.key { + if let liked = likedManager?.liked(key: key) { + let image = UIImage.named(liked ? "action_liked" : "action_like", bundles: Bundle.particles) + (likeButton as? UIButton)?.setImage(image, for: .normal) + (likeButton as? UIButton)?.isHidden = false + } else { + (likeButton as? UIButton)?.setImage(nil, for: .normal) + (likeButton as? UIButton)?.isHidden = true + } + } + } + + open func updateDisliked(animated: Bool) { + if let dislikeButton = dislikeButton, let key = model?.key { + if let disliked = likedManager?.disliked(key: key) { + let image = UIImage.named(disliked ? "action_disliked" : "action_dislike", bundles: Bundle.particles) + (dislikeButton as? UIButton)?.setImage(image, for: .normal) + (dislikeButton as? UIButton)?.isHidden = false + } else { + (dislikeButton as? UIButton)?.setImage(nil, for: .normal) + (dislikeButton as? UIButton)?.isHidden = true + } + } + } + + @IBAction @objc func like(_ sender: Any?) { + if let key = model?.key, let keyValue = key, let path = likePath { + Router.shared?.navigate(to: RoutingRequest(path: path, params: ["key": keyValue]), animated: false, completion: nil) +// likedManager?.toggleLike(key: key) + } + } + + @IBAction @objc func dislike(_ sender: Any?) { + if let key = model?.key, let keyValue = key, let path = dislikePath { + Router.shared?.navigate(to: RoutingRequest(path: path, params: ["key": keyValue]), animated: false, completion: nil) +// likedManager?.toggleDislike(key: key) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/ListInteractorPresenterView.swift b/PlatformParticles/PlatformParticles/_Object Presenter/ListInteractorPresenterView.swift new file mode 100644 index 000000000..88867d541 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/ListInteractorPresenterView.swift @@ -0,0 +1,36 @@ +// +// ListInteractorPresenterView.swift +// PlatformParticles +// +// Created by Qiang Huang on 1/1/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIKit + +@objc public class ListInteractorPresenterView: UIView, ObjectPresenterProtocol { + @IBOutlet public var presenter: ListPresenter? { + didSet { + if presenter !== oldValue { + presenter?.visible = true + } + } + } + + public var model: ModelObjectProtocol? { + didSet { + if model !== oldValue { + presenter?.interactor = interactor + } + } + } + + public var interactor: ListInteractor? { + return model as? ListInteractor + } + + @objc open var selectable: Bool { + return false + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/ListObjectViewPresenter.swift b/PlatformParticles/PlatformParticles/_Object Presenter/ListObjectViewPresenter.swift new file mode 100644 index 000000000..61b076381 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/ListObjectViewPresenter.swift @@ -0,0 +1,34 @@ +// +// ListObjectPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 9/3/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import ParticlesKit +import UIKit + +@objc public class ListObjectViewPresenter: ObjectViewPresenter { + @IBOutlet var listPresenter: ListPresenter? { + didSet { + didSetListPresenter(oldValue: oldValue) + } + } + + @objc public dynamic var list: ListInteractor? { + return model as? ListInteractor + } + + override public func didSetModel(oldValue: ModelObjectProtocol?) { + super.didSetModel(oldValue: oldValue) + listPresenter?.interactor = list + } + + func didSetListPresenter(oldValue: ListPresenter?) { + if listPresenter !== oldValue { + listPresenter?.visible = true + listPresenter?.interactor = list + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/ObjectLikeBarItemPresenter.swift b/PlatformParticles/PlatformParticles/_Object Presenter/ObjectLikeBarItemPresenter.swift new file mode 100644 index 000000000..c8c9d12ff --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/ObjectLikeBarItemPresenter.swift @@ -0,0 +1,85 @@ +// +// ObjectLikeBarItemPresenter.swift +// PresenterLib +// +// Created by Qiang Huang on 11/2/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits + +@objc open class ObjectLikeBarItemPresenter: ObjectPresenter { + @IBOutlet var likeButton: ButtonProtocol? { + didSet { + if likeButton !== oldValue { + oldValue?.removeTarget() + likeButton?.addTarget(self, action: #selector(like(_:))) + updateLiked() + } + } + } + + @IBOutlet var dislikeButton: ButtonProtocol? { + didSet { + if dislikeButton !== oldValue { + oldValue?.removeTarget() + dislikeButton?.addTarget(self, action: #selector(dislike(_:))) + updateDisliked() + } + } + } + + public var likedManager: LikedObjectsProtocol? { + didSet { + changeObservation(from: oldValue, to: likedManager, keyPath: #keyPath(LikedObjectsProtocol.liked)) { [weak self] _, _, _, _ in + self?.updateLiked() + } + + changeObservation(from: oldValue, to: likedManager, keyPath: #keyPath(LikedObjectsProtocol.disliked)) { [weak self] _, _, _, _ in + self?.updateDisliked() + } + } + } + + override open var model: ModelObjectProtocol? { + didSet { + if model !== oldValue { + self.updateLiked() + self.updateDisliked() + } + } + } + + open func updateLiked() { + if let key = model?.key { + if likedManager?.liked(key: key) ?? false { + likeButton?.buttonImage = UIImage.named("action_liked", bundles: Bundle.particles) + } else { + likeButton?.buttonImage = UIImage.named("action_like", bundles: Bundle.particles) + } + } + } + + open func updateDisliked() { + if let key = model?.key { + if likedManager?.disliked(key: key) ?? false { + dislikeButton?.buttonImage = UIImage.named("action_disliked", bundles: Bundle.particles) + } else { + dislikeButton?.buttonImage = UIImage.named("action_dislike", bundles: Bundle.particles) + } + } + } + + @IBAction func like(_ sender: Any?) { + if let key = model?.key, let likedManager = self.likedManager { + likedManager.toggleLike(key: key) + } + } + + @IBAction func dislike(_ sender: Any?) { + if let key = model?.key, let likedManager = self.likedManager { + likedManager.toggleDislike(key: key) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/ObjectPresenterCollectionViewCell.swift b/PlatformParticles/PlatformParticles/_Object Presenter/ObjectPresenterCollectionViewCell.swift new file mode 100644 index 000000000..f04aea66f --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/ObjectPresenterCollectionViewCell.swift @@ -0,0 +1,72 @@ +// +// ObjectPresenterCollectionViewCell.swift +// PresenterLib +// +// Created by Qiang Huang on 10/10/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import UIKit + +@objc public class ObjectPresenterCollectionViewCell: UICollectionViewCell, ObjectPresenterProtocol { + @IBOutlet public var presenterView: UIView? + + public var xib: String? { + didSet { + if xib != oldValue { + uninstallView(xib: oldValue, view: presenterView) + installView(xib: xib, into: contentView, parentViewController: nil) { [weak self] view in + self?.presenterView = view + } + + } + } + } + + override public var isSelected: Bool { + didSet { + if isSelected != oldValue { + (presenterView as? SelectableProtocol)?.isSelected = isSelected + } + } + } + + @objc public var isCellHighlighted: Bool = false { + didSet { + if isCellHighlighted != oldValue { + (presenterView as? HighlightableProtocol)?.isHighlighted = isCellHighlighted + } + } + } + + public var model: ModelObjectProtocol? { + get { return (presenterView as? ObjectPresenterView)?.model } + set { (presenterView as? ObjectPresenterView)?.model = newValue } + } + + @objc open var selectable: Bool { + return (presenterView as? ObjectPresenterView)?.selectable ?? false + } + + override public func awakeFromNib() { + super.awakeFromNib() + + if #available(iOS 12.0, *) { + contentView.translatesAutoresizingMaskIntoConstraints = false + + // Code below is needed to make the self-sizing cell work when building for iOS 12 from Xcode 10.0: + let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor) + let rightConstraint = contentView.rightAnchor.constraint(equalTo: rightAnchor) + let topConstraint = contentView.topAnchor.constraint(equalTo: topAnchor) + let bottomConstraint = contentView.bottomAnchor.constraint(equalTo: bottomAnchor) + NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint]) + } + } + + override public func prepareForReuse() { + isSelected = false + isHighlighted = false + model = nil + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/ObjectPresenterContainerView.swift b/PlatformParticles/PlatformParticles/_Object Presenter/ObjectPresenterContainerView.swift new file mode 100644 index 000000000..6667faef0 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/ObjectPresenterContainerView.swift @@ -0,0 +1,58 @@ +// +// ObjectPresenterContainerView.swift +// PresenterLib +// +// Created by Qiang Huang on 10/10/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import UIKit + +public class ObjectPresenterContainerView: UIView, ObjectPresenterProtocol, SelectableProtocol { + public var xibCache: XibPresenterCache = XibPresenterCache() + + @IBInspectable public var xibMap: String? { + didSet { + xibCache.xibMap = xibMap + } + } + + @IBOutlet public var presenterView: UIView? + + public var xib: String? { + didSet { + if xib != oldValue { + uninstallView(xib: oldValue, view: presenterView) + installView(xib: xib, into: self, parentViewController: nil) { [weak self] view in + if let self = self { + (self.presenterView as? ObjectPresenterView)?.model = self.model + } + } + } + } + } + + public var model: ModelObjectProtocol? { + didSet { + xib = xib(object: model) + (presenterView as? ObjectPresenterView)?.model = model + } + } + + @objc open var selectable: Bool { + return (presenterView as? ObjectPresenterView)?.selectable ?? false + } + + open var isSelected: Bool = false { + didSet { + if isSelected != oldValue { + (presenterView as? SelectableProtocol)?.isSelected = isSelected + } + } + } + + public func xib(object: ModelObjectProtocol?) -> String? { + return xibCache.xib(object: object) + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/ObjectPresenterTableViewCell.swift b/PlatformParticles/PlatformParticles/_Object Presenter/ObjectPresenterTableViewCell.swift new file mode 100644 index 000000000..9acb02681 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/ObjectPresenterTableViewCell.swift @@ -0,0 +1,106 @@ +// +// ObjectPresenterTableViewCell.swift +// PresenterLib +// +// Created by John Huang on 10/10/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIKit + +@objc open class ObjectPresenterTableViewCell: UITableViewCell, ObjectPresenterProtocol { + @IBOutlet public var presenterView: UIView? + + public func setXib(_ xib: String?, parentViewController: UIViewController?) { + if xib != self.xib { + uninstallView(xib: self.xib, view: presenterView) + self.xib = xib + installPresenterView(xib: xib, parentViewController: parentViewController) + } + } + + private var xib: String? + /* + public var xib: String? { + didSet { + if xib != oldValue { + uninstallView(xib: xib, view: presenterView) + installPresenterView(xib: xib) + } + } + } + */ + public var model: ModelObjectProtocol? { + get { return (presenterView as? ObjectPresenterView)?.model } + set { + if let presenter = presenterView as? ObjectPresenterProtocol { + presenter.model = newValue + if let selectable = presenter.selectable { + selectionStyle = selectable ? .default : .none + } else { + selectionStyle = .none + } + } else { + selectionStyle = .none + } + } + } + + @objc open var selectable: Bool { + return (presenterView as? ObjectPresenterView)?.selectable ?? false + } + + open var showDisclosure: Bool? { + if let presetingView = presenterView as? ObjectPresenterView { + return presetingView.showDisclosure + } + return nil + } + + open override var isSelected: Bool { + didSet { + if isSelected != oldValue { + (presenterView as? SelectableProtocol)?.isSelected = isSelected + } + } + } + + @objc open dynamic var isFirst: Bool = false { + didSet { + (presenterView as? ObjectPresenterView)?.isFirst = isFirst + } + } + + @objc open dynamic var isLast: Bool = false { + didSet { + (presenterView as? ObjectPresenterView)?.isLast = isLast + } + } + + open override func prepareForReuse() { + model = nil + isSelected = false + isHighlighted = false + isFirst = false + isLast = false + } + + open func installPresenterView(xib: String?, parentViewController: UIViewController?) { + installView(xib: xib, into: contentView, parentViewController: parentViewController) { [weak self] view in + self?.presenterView = view + } + } + + open override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + (presenterView as? SelectableProtocol)?.isSelected = isSelected + } + + open override func setHighlighted(_ highlighted: Bool, animated: Bool) { + super.setHighlighted(highlighted, animated: animated) + + (presenterView as? HighlightableProtocol)?.isHighlighted = highlighted + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/ObjectPresenterView.swift b/PlatformParticles/PlatformParticles/_Object Presenter/ObjectPresenterView.swift new file mode 100644 index 000000000..73d85cc29 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/ObjectPresenterView.swift @@ -0,0 +1,89 @@ +// +// ObjectPresenterView.swift +// PresenterLib +// +// Created by John Huang on 10/10/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import UIKit +import UIToolkits + +open class ObjectPresenterView: UIView, ObjectPresenterProtocol, SelectableProtocol, HighlightableProtocol { + @IBOutlet public var presenter: ObjectPresenter? { + didSet { + #if DEBUG + accessibilityIdentifier = String(describing: presenter) + #endif + } + } + + public var model: ModelObjectProtocol? { + get { return presenter?.model } + set { presenter?.model = newValue } + } + + open var selectable: Bool { + return presenter?.selectable ?? false + } + + open var showDisclosure: Bool? { + if let tableCellPresenter = presenter as? ObjectTableCellPresenterProtocol { + return tableCellPresenter.showDisclosure + } + return nil + } + + public var isSelected: Bool = false { + didSet { + if let selectable = (presenter as? SelectableProtocol) { + selectable.isSelected = isSelected + } + } + } + + public var isHighlighted: Bool = false { + didSet { + if let highlightable = (presenter as? HighlightableProtocol) { + highlightable.isHighlighted = isHighlighted + } + } + } + + public var isFirst: Bool = false { + didSet { + presenter?.isFirst = isFirst + } + } + + public var isLast: Bool = false { + didSet { + presenter?.isLast = isLast + } + } +} + +extension ObjectPresenter { + public func updateLayout(view: UIView?) { + if let view = view, model != nil { + if let collectionView: UICollectionView = view.parent() { + view.layoutIfNeeded() + DispatchQueue.main.async { + collectionView.collectionViewLayout.invalidateLayout() + } + } else if let tableView: UITableView = view.parent() { + if let uxTableView = tableView as? UXTableView { + uxTableView.updateLayout() + } else { + DispatchQueue.main.async { + tableView.beginUpdates() + tableView.endUpdates() + } + } + } else { + view.layoutIfNeeded() + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/ObjectViewPresenter.swift b/PlatformParticles/PlatformParticles/_Object Presenter/ObjectViewPresenter.swift new file mode 100644 index 000000000..2560f3156 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/ObjectViewPresenter.swift @@ -0,0 +1,50 @@ +// +// ObjectViewPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 4/19/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities +import Combine + +open class ObjectViewPresenter: ObjectPresenter, HighlightableProtocol { + + @IBOutlet open var view: UIView? + @IBInspectable public var automaticHighlight: Bool = false + + public var isHighlighted: Bool = false { + didSet { + didSetIsHighlighted(oldValue: oldValue) + } + } + + private var layoutDebouncer: Debouncer = Debouncer() + + public func addTap(view: UIView?, action: Selector) { + if let view = view { + let tap = UITapGestureRecognizer(target: self, action: action) + view.addGestureRecognizer(tap) + } + } + + open func updateLayout(animated: Bool) { + if animated { + UIView.animate(withDuration: UIView.defaultAnimationDuration) { [weak self] in + self?.view?.layoutIfNeeded() + } + } else { + view?.layoutIfNeeded() + } + } + + open func didSetIsHighlighted(oldValue: Bool) { + if isHighlighted != oldValue { + if automaticHighlight { + view?.alpha = isHighlighted ? 0.7 : 1.0 + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/ParallaxObjectPresenterTableViewCell.swift b/PlatformParticles/PlatformParticles/_Object Presenter/ParallaxObjectPresenterTableViewCell.swift new file mode 100644 index 000000000..a7791da83 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/ParallaxObjectPresenterTableViewCell.swift @@ -0,0 +1,34 @@ +// +// ParallaxObjectPresenterTableViewCell.swift +// PlatformParticles +// +// Created by Qiang Huang on 12/24/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIToolkits + +@objc open class ParallaxObjectPresenterTableViewCell: ObjectPresenterTableViewCell { + @IBOutlet var canvasView: UIView? + @IBOutlet var canvasCenter: NSLayoutConstraint? + + open override func installPresenterView(xib: String?, parentViewController: UIViewController?) { + installView(xib: xib, into: canvasView ?? contentView, parentViewController: parentViewController) { [weak self] view in + self?.presenterView = view + } + } + + open func parallax(animated: Bool) { + if let canvasView = canvasView, let canvasCenter = canvasCenter, let tableview: UITableView = self.parent(), let superview = tableview.superview { + let cellFrameInTable = tableview.convert(frame, to: superview) + let cellCenter = cellFrameInTable.origin.y + cellFrameInTable.size.height / 2 + let ratio = cellCenter / (tableview.frame.size.height / 2) - 1 + canvasCenter.constant = (frame.size.height - canvasView.frame.size.height) / 2 * ratio + if animated { + UIView.animate(withDuration: UIView.defaultAnimationDuration) { + self.layoutIfNeeded() + } + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/SmartObjectPresenter.swift b/PlatformParticles/PlatformParticles/_Object Presenter/SmartObjectPresenter.swift new file mode 100644 index 000000000..0683cdded --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/SmartObjectPresenter.swift @@ -0,0 +1,73 @@ +// +// SmartObjectPresenter.swift +// PresenterLib +// +// Created by Qiang Huang on 10/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits + +@objc open class SmartObjectPresenter: ObjectViewPresenter { + @IBOutlet var childPresenters: [ObjectPresenter]? + var lookup: [String: UIView] = [String: UIView]() + + override open var model: ModelObjectProtocol? { + didSet { + if model !== oldValue { + for (tag, _) in lookup { + kvoController.unobserve(oldValue, keyPath: tag) + } + lookup.removeAll() + update(layout: oldValue != nil) + } + } + } + + open func update(layout: Bool) { + if let view = view { + update(view: view) + if layout { + updateLayout(view: view) + } + } + } + + open func update(view: UIView) { + if let binding = view.binding { + update(binding: binding, view: view) + } else { + for child in view.subviews { + update(view: child) + } + } + } + + private func update(binding: String, view: UIView) { + if lookup[binding] == nil { + // this is to deal with many views with the same tag. Only one is updated + lookup[binding] = view + kvoController.observe(model, keyPath: binding, options: [.initial]) { [weak self] _, _, _ in + if let self = self, let model = self.model as? (NSObject & ModelObjectProtocol) { + if let label = view as? LabelProtocol { + label.text = self.parser.asString(model.value(forKey: binding))?.localized + } else if let imageView = view as? CachedImageView { + imageView.imageUrl = self.parser.asURL(model.value(forKey: binding)) + } else if let imageView = view as? ImageViewProtocol { + if let imageName = self.parser.asString(model.value(forKey: binding)) { + imageView.image = UIImage.named(imageName, bundles: Bundle.particles) + } else { + imageView.image = nil + } + } else { + let value = model.value(forKey: binding) + if let boolValue = value as? Bool { + view.isHidden = !boolValue + } + } + } + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/ThinlinePresenter.swift b/PlatformParticles/PlatformParticles/_Object Presenter/ThinlinePresenter.swift new file mode 100644 index 000000000..f4e6ef32c --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/ThinlinePresenter.swift @@ -0,0 +1,22 @@ +// +// ThinlinePresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 11/6/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits +import Utilities + +public class ThinlinePresenter: ObjectPresenter { + @IBOutlet var height: NSLayoutConstraint? { + didSet { + if height !== oldValue { + let scale = UIScreen.main.scale + height?.constant = 1.0 / scale + } + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/TimeIntervalObjectViewPresenter.swift b/PlatformParticles/PlatformParticles/_Object Presenter/TimeIntervalObjectViewPresenter.swift new file mode 100644 index 000000000..284a2e175 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/TimeIntervalObjectViewPresenter.swift @@ -0,0 +1,34 @@ +// +// TimeIntervalObjectViewPresenter.swift +// PlatformParticles +// +// Created by John Huang on 2/12/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import Foundation +import ParticlesKit +import Utilities + +@objc open class TimeIntervalObjectViewPresenter: ObjectViewPresenter { + public var clock: Clock? { + didSet { + didSetClock(oldValue: oldValue) + } + } + + open override func didSetModel(oldValue: ModelObjectProtocol?) { + super.didSetModel(oldValue: oldValue) + clock = Clock.shared + } + + open func didSetClock(oldValue: Clock?) { + changeObservation(from: oldValue, to: clock, keyPath: #keyPath(Clock.time)) { [weak self] _, _, _, animated in + self?.displayTime(animated: animated) + } + } + + open func displayTime(animated: Bool) { + } +} + diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/UIView+Binding.swift b/PlatformParticles/PlatformParticles/_Object Presenter/UIView+Binding.swift new file mode 100644 index 000000000..22ca0e145 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/UIView+Binding.swift @@ -0,0 +1,25 @@ +// +// UIView+Binding.swift +// PlatformParticles +// +// Created by Qiang Huang on 4/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +extension UIView { + private struct AssociatedKey { + static var bindingKey = "view.text.binding" + } + + @IBInspectable public var binding: String? { + get { + return associatedObject(base: self, key: &AssociatedKey.bindingKey) + } + set { + retainObject(base: self, key: &AssociatedKey.bindingKey, value: newValue) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/UIView+Xib.swift b/PlatformParticles/PlatformParticles/_Object Presenter/UIView+Xib.swift new file mode 100644 index 000000000..1555ec0e8 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/UIView+Xib.swift @@ -0,0 +1,80 @@ +// +// UIView+Xib.swift +// PresenterLib +// +// Created by Qiang Huang on 10/10/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import UIKit +import UIToolkits +import Utilities + +extension UIView { + public func installView(xib: String?, completion: @escaping ((UIView?) -> Void)) { + installView(xib: xib, into: self, parentViewController: self.viewController(), completion: completion) + } + + @objc open func installView(xib: String?, into contentView: UIView?, parentViewController: UIViewController?, completion: @escaping ((UIView?) -> Void)) { + #if DEBUG + accessibilityIdentifier = "xib: \(xib ?? "")" + #endif + + if let contentView = contentView, let xib = xib { + if let loadedView: UIView = XibLoader.load(from: xib) { + install(view: loadedView, into: contentView) + completion(loadedView) + } else { + ClassLoader.load(from: xib) { [weak self] viewController in + if let self = self, let loadedViewController: UIViewController = viewController { + + guard let parentViewController = parentViewController else { + assertionFailure("parentViewController is required when loading from ClassLoader") + completion(nil) + return + } + + self.uninstallView(xib: xib, view: nil) + + parentViewController.addChild(loadedViewController) + self.viewControllerXibMap?[xib] = loadedViewController + self.install(view: loadedViewController.view, into: contentView) + loadedViewController.didMove(toParent: parentViewController) + + completion(loadedViewController.view) + } else { + completion(nil) + } + } + } + } else { + completion(nil) + } + } + + public func uninstallView(xib: String?, view: UIView?) { + if let xib = xib { + if let existingViewController = viewControllerXibMap?[xib] { + existingViewController.willMove(toParent: nil) + existingViewController.removeFromParent() + viewControllerXibMap?.removeValue(forKey: xib) + } + } + view?.removeFromSuperview() + } + + private struct AssociatedKey { + static var bindingKey = "view.xib.viewControllerXibMap" + } + + private var viewControllerXibMap: [String: UIViewController]? { + get { + return associatedObject(base: self, key: &AssociatedKey.bindingKey) + } + set { + retainObject(base: self, key: &AssociatedKey.bindingKey, value: newValue) + } + } +} + diff --git a/PlatformParticles/PlatformParticles/_Object Presenter/XibActionPresenter.swift b/PlatformParticles/PlatformParticles/_Object Presenter/XibActionPresenter.swift new file mode 100644 index 000000000..bd92ac24f --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Object Presenter/XibActionPresenter.swift @@ -0,0 +1,81 @@ +// +// XibActionPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 2/10/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import UIToolkits + +@objc open class XibActionPresenter: ObjectPresenter { + @IBOutlet var titleLabel: LabelProtocol? + @IBOutlet var textLabel: LabelProtocol? + @IBOutlet var imageView: ImageViewProtocol? + @IBOutlet var colorView: UIView? + + public var action: XibAction? { + return model as? (XibAction) + } + + override public var model: ModelObjectProtocol? { + didSet { + changeObservation(from: oldValue, to: action, keyPath: #keyPath(XibAction.title)) { [weak self] _, _, _, _ in + self?.updateTitle() + } + changeObservation(from: oldValue, to: action, keyPath: #keyPath(XibAction.text)) { [weak self] _, _, _, _ in + self?.updateText() + } + changeObservation(from: oldValue, to: action, keyPath: #keyPath(XibAction.image)) { [weak self] _, _, _, _ in + self?.updateImage() + } + changeObservation(from: oldValue, to: action, keyPath: #keyPath(XibAction.color)) { [weak self] _, _, _, _ in + self?.updateColor() + } + } + } + + @IBOutlet var linkButton: ButtonProtocol? { + didSet { + (linkButton as? UIButton)?.centerImageAndButton(0, imageOnTop: true) + oldValue?.removeTarget() + linkButton?.addTarget(self, action: #selector(link(_:))) + } + } + + override public var selectable: Bool { + if linkButton != nil { + return false + } else { + return action?.request != nil + } + } + + @IBAction func link(_ sender: Any?) { + if let request = action?.request { + Router.shared?.navigate(to: request, animated: true, completion: nil) + } + } + + private func updateTitle() { + titleLabel?.text = action?.title + } + + private func updateText() { + textLabel?.text = action?.text + } + + private func updateImage() { + if let imageName = action?.image { + imageView?.image = UIImage.named(imageName, bundles: Bundle.particles) + } else { + imageView?.image = nil + } + } + + open func updateColor() { + colorView?.backgroundColor = ColorPalette.shared.color(system: action?.color) + } +} diff --git a/PlatformParticles/PlatformParticles/_Permissions/PrivacyPermissionPresenter.swift b/PlatformParticles/PlatformParticles/_Permissions/PrivacyPermissionPresenter.swift new file mode 100644 index 000000000..46d4a0e5b --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Permissions/PrivacyPermissionPresenter.swift @@ -0,0 +1,77 @@ +// +// PrivacyPermissionPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 7/17/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIToolkits +import Utilities + +open class PrivacyPermissionPresenter: NSObject { + @IBOutlet var view: UIView? { + didSet { + if view !== oldValue { + view?.bringToFront() + updateAuthorization() + } + } + } + + @IBOutlet var title: UILabel? { + didSet { + if title !== oldValue { + updateAuthorization() + } + } + } + + @IBOutlet var detail: UILabel? { + didSet { + if detail !== oldValue { + updateAuthorization() + } + } + } + + @IBOutlet var button: UIButton? { + didSet { + if button !== oldValue { + oldValue?.removeTarget() + button?.addTarget(self, action: #selector(authorize(_:))) + updateAuthorization() + } + } + } + + @IBOutlet public var authorization: PrivacyPermission? { + didSet { + changeObservation(from: oldValue, to: authorization, keyPath: #keyPath(PrivacyPermission.authorization)) { [weak self] _, _, _, _ in + self?.updateAuthorization() + } + } + } + + open func updateAuthorization() { + switch authorization?.authorization { + case .authorized?: + view?.isHidden = true + + default: + title?.text = authorization?.requestTitle + detail?.text = authorization?.requestMessage + button?.buttonTitle = (authorization?.authorization == .notDetermined) ? "Permit" : "Go to Settings" + view?.isHidden = false + } + } + + @IBAction func authorize(_ sender: Any?) { + UIViewController.topmost()?.dismiss(sender) + if authorization?.authorization == .notDetermined { + authorization?.promptToAuthorize() + } else { + authorization?.promptToSettings() + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Progress/ProgressPresenter.swift b/PlatformParticles/PlatformParticles/_Progress/ProgressPresenter.swift new file mode 100644 index 000000000..3db48fe1f --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Progress/ProgressPresenter.swift @@ -0,0 +1,66 @@ +// +// ProgressPresenter.swift +// PlatformParticles +// +// Created by Qiang Huang on 8/14/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +@objc open class ProgressPresenter: NSObject { + @IBOutlet public var progressBar: UIProgressView? + @IBOutlet public var titleLabel: UILabel? + @IBOutlet public var detailLabel: UILabel? + @IBOutlet public var imageView: UIImageView? + @IBOutlet public var dismissButton: UIButton? { + didSet { + if dismissButton !== oldValue { + oldValue?.removeTarget() + dismissButton?.addTarget(self, action: #selector(dismiss(_:))) + } + } + } + + @objc open dynamic var progress: ProgressProtocol? { + didSet { + changeObservation(from: oldValue, to: progress, keyPath: #keyPath(ProgressProtocol.started)) { [weak self] _, _, _, _ in + self?.updateStarted() + } + changeObservation(from: oldValue, to: progress, keyPath: #keyPath(ProgressProtocol.progress)) { [weak self] _, _, _, _ in + self?.updateProgress() + } + changeObservation(from: oldValue, to: progress, keyPath: #keyPath(ProgressProtocol.error)) { [weak self] _, _, _, _ in + self?.updateError() + } + changeObservation(from: oldValue, to: progress, keyPath: #keyPath(ProgressProtocol.text)) { [weak self] _, _, _, _ in + self?.updateText() + } + } + } + + @IBAction open func dismiss(_ sender: Any?) { + } + + open func updateStarted() { + let started = progress?.started ?? false + progressBar?.visible = started + titleLabel?.visible = !started + + updateImage() + updateText() + } + + open func updateProgress() { + progressBar?.setProgress(progress?.progress ?? 0, animated: true) + } + + open func updateError() { + } + + open func updateImage() { + } + + open func updateText() { + } +} diff --git a/PlatformParticles/PlatformParticles/_Shared/ParticlesPlatformAppInjection.swift b/PlatformParticles/PlatformParticles/_Shared/ParticlesPlatformAppInjection.swift new file mode 100644 index 000000000..7fa9fd033 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Shared/ParticlesPlatformAppInjection.swift @@ -0,0 +1,25 @@ +// +// ParticlesPlatformAppInjection.swift +// PlatformParticles +// +// Created by Qiang Huang on 1/6/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import PlatformRouting +import UIToolkits +import Utilities +#if _iOS + import UIAppToolkits +#endif + +open class ParticlesPlatformAppInjection: ParticlesPlatformInjection { + open override func injectUI() { + super.injectUI() + + #if _iOS + ViewControllerStack.shared = UIKitAppViewControllerStack() + #endif + } +} diff --git a/PlatformParticles/PlatformParticles/_Shared/ParticlesPlatformExtensionInjection.swift b/PlatformParticles/PlatformParticles/_Shared/ParticlesPlatformExtensionInjection.swift new file mode 100644 index 000000000..3acf589d2 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Shared/ParticlesPlatformExtensionInjection.swift @@ -0,0 +1,9 @@ +// +// ParticlesPlatformExtensionInjection.swift +// PlatformParticles +// +// Created by Qiang Huang on 1/6/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation diff --git a/PlatformParticles/PlatformParticles/_Shared/ParticlesPlatformInjection.swift b/PlatformParticles/PlatformParticles/_Shared/ParticlesPlatformInjection.swift new file mode 100644 index 000000000..bb0a8e4e7 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Shared/ParticlesPlatformInjection.swift @@ -0,0 +1,32 @@ +// +// ParticlesPlatformInjection.swift +// PlatformParticles +// +// Created by Qiang Huang on 12/26/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import PlatformRouting +import SDWebImage +import SDWebImageSVGCoder +import Utilities +import UIToolkits + +open class ParticlesPlatformInjection: ParticlesInjection { + open override func injectAppStart(completion: @escaping () -> Void) { + super.injectAppStart {[weak self] in + self?.injectUI() + completion() + } + } + + open func injectUI() { + Console.shared.log("injectUI") + + HapticFeedback.shared = MotionHapticFeedback() + SDImageCodersManager.shared.addCoder(SDImageSVGCoder.shared) + RoutingTabBarController.parserOverwrite = Parser.standard + PrompterFactory.shared = UIKitPrompterFactory() + } +} diff --git a/PlatformParticles/PlatformParticles/_Tracking/TransformerTracker.swift b/PlatformParticles/PlatformParticles/_Tracking/TransformerTracker.swift new file mode 100644 index 000000000..3ba4ac000 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Tracking/TransformerTracker.swift @@ -0,0 +1,51 @@ +// +// TransformerTracker.swift +// PlatformParticles +// +// Created by Qiang Huang on 1/21/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +open class TransformerTracker: NSObject & TrackingProtocol { + public var excluded: Bool = false + + private var entity: DictionaryEntity? + + override public init() { + super.init() + + if let destinations = (JsonLoader.load(bundle: Bundle.main, fileName: "events.json") ?? JsonLoader.load(bundles: Bundle.particles, fileName: "events.json")) as? [String: Any] { + entity = DictionaryEntity() + entity?.parse(dictionary: destinations) + } + } + + + open func setUserId(_ userId: String?) { + assertionFailure("TransformerTracker does not support setUserId, should override") + } + + open func setValue(_ value: Any?, forUserProperty userProperty: String) { + assertionFailure("TransformerTracker does not support setUserProperty, should override") + } + + open func leave(_ path: String?) { + } + + open func transform(path: String?) -> String? { + if let pathElements = path?.components(separatedBy: "/").compactMap({ element in + element.trim() + }) { + let path = pathElements.joined(separator: "_") + return (entity?.data?[path] as? String) ?? path + } else { + return nil + } + } + + open func log(event: String, data: [String: Any]?, revenue: NSNumber?) { + } +} diff --git a/PlatformParticles/PlatformParticles/_ViewController/DataPresenterViewController.swift b/PlatformParticles/PlatformParticles/_ViewController/DataPresenterViewController.swift new file mode 100644 index 000000000..7be5df024 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_ViewController/DataPresenterViewController.swift @@ -0,0 +1,23 @@ +// +// DataPresenterViewController.swift +// PresenterLib +// +// Created by Qiang Huang on 11/21/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit + +open class DataPresenterViewController: ListPresenterViewController { + @IBOutlet open var interactor: InteractorProtocol? { + didSet { + changeObservation(from: oldValue, to: interactor, keyPath: #keyPath(InteractorProtocol.entity), block: { [weak self] _, _, _, _ in + if let self = self { + self.entity = self.interactor?.entity + } + }) + } + } + + open var entity: ModelObjectProtocol? +} diff --git a/PlatformParticles/PlatformParticles/_ViewController/GridPresenterViewController.swift b/PlatformParticles/PlatformParticles/_ViewController/GridPresenterViewController.swift new file mode 100644 index 000000000..4766fb011 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_ViewController/GridPresenterViewController.swift @@ -0,0 +1,36 @@ +// +// GridPresenterViewController.swift +// PlatformParticles +// +// Created by Qiang Huang on 2/15/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit + +open class GridPresenterViewController: TrackingViewController { + @IBOutlet open var presenterManager: GridPresenterManager? + + open override func awakeFromNib() { + super.awakeFromNib() + if presenterManager?.view == nil { + presenterManager?.view = view + } + } + + open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { [weak self] in + self?.presenterManager?.updateLayout() + } + } + + open override func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + super.navigate(to: request, animated: animated) { [weak self] object, completed in + if completed { + self?.presenterManager?.show(view: request?.params?["view"] as? String) + } + completion?(object, completed) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_ViewController/ListPresenterViewController.swift b/PlatformParticles/PlatformParticles/_ViewController/ListPresenterViewController.swift new file mode 100644 index 000000000..2640b8a43 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_ViewController/ListPresenterViewController.swift @@ -0,0 +1,66 @@ +// +// ListPresenterViewController.swift +// PresenterLib +// +// Created by Qiang Huang on 10/12/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit + +open class ListPresenterViewController: TrackingViewController { + @IBOutlet open var presenterManager: ListPresenterManager? { + didSet { + changeObservation(from: oldValue, to: presenterManager, keyPath: #keyPath(ListPresenterManager.current)) { [weak self] _, _, _, _ in + if let self = self { + self.current = self.presenterManager?.current + } + } + } + } + + open var current: ListPresenter? + + open override func awakeFromNib() { + super.awakeFromNib() + if presenterManager?.view == nil { + presenterManager?.view = view + } + } + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if presenterManager?.flat ?? false { + if let presenters = presenterManager?.presenters { + for presenter in presenters { + if presenter.current == nil { + setup(presenter: presenter) + } + } + } + } else { + if let current = current, current.current == nil { + setup(presenter: current) + } + } + } + + open func setup(presenter: ListPresenter) { + } + + open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { [weak self] in + self?.presenterManager?.updateLayout() + } + } + + open override func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + super.navigate(to: request, animated: animated) { [weak self] object, completed in + if completed { + self?.presenterManager?.show(view: request?.params?["view"] as? String) + } + completion?(object, completed) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_ViewController/PermissionPrimerViewController.swift b/PlatformParticles/PlatformParticles/_ViewController/PermissionPrimerViewController.swift new file mode 100644 index 000000000..252959deb --- /dev/null +++ b/PlatformParticles/PlatformParticles/_ViewController/PermissionPrimerViewController.swift @@ -0,0 +1,29 @@ +// +// PrivacyAuthorizationViewController.swift +// PlatformParticles +// +// Created by Qiang Huang on 9/1/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import UIToolkits +import Utilities + +open class PermissionPrimerViewController: TrackingViewController { + @IBInspectable var path: String? + @IBInspectable var hideNavBar: Bool = false + @IBOutlet var presenter: PrivacyPermissionPresenter? + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if hideNavBar { + navigationController?.navigationBar.transparent = true + } + } + + open override func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { + return request?.path == path + } +} diff --git a/PlatformParticles/PlatformParticles/_ViewController/PrivacyPermissionViewController.swift b/PlatformParticles/PlatformParticles/_ViewController/PrivacyPermissionViewController.swift new file mode 100644 index 000000000..6d041abbc --- /dev/null +++ b/PlatformParticles/PlatformParticles/_ViewController/PrivacyPermissionViewController.swift @@ -0,0 +1,43 @@ +// +// PrivacyAuthorizationViewController.swift +// PlatformParticles +// +// Created by Qiang Huang on 9/1/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import UIToolkits +import Utilities + +open class PrivacyPermissionViewController: TrackingViewController { + @IBInspectable var path: String? + @IBInspectable var hideNavBar: Bool = false + + @IBOutlet var settingsButton: ButtonProtocol? { + didSet { + if settingsButton !== oldValue { + oldValue?.removeTarget() + settingsButton?.addTarget(self, action: #selector(settings(_:))) + } + } + } + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if hideNavBar { + navigationController?.navigationBar.transparent = true + } + } + + open override func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { + return request?.path == path + } + + @IBAction func settings(_ sender: Any?) { + if let url = URL(string: UIApplication.openSettingsURLString) { + URLHandler.shared?.open(url, completionHandler: nil) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_ViewController/SearchViewController.swift b/PlatformParticles/PlatformParticles/_ViewController/SearchViewController.swift new file mode 100644 index 000000000..0e3684672 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_ViewController/SearchViewController.swift @@ -0,0 +1,162 @@ +// +// SearchViewController.swift +// PresenterLib +// +// Created by Qiang Huang on 10/27/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import UIKit +import UIToolkits +import Utilities + +open class SearchViewController: ListPresenterViewController { + public var savedSearchManager: SavedSearchesProtocol? { + didSet { + changeObservation(from: oldValue, to: savedSearchManager, keyPath: #keyPath(SavedSearchesProtocol.savedSearches)) { [weak self] _, _, _, _ in + self?.configureSearchBar() + } + } + } + + @IBOutlet public var viewButton: ButtonProtocol? + @IBOutlet public var filtersButton: ButtonProtocol? { + didSet { + if filtersButton !== oldValue { + oldValue?.removeTarget() + filtersButton?.addTarget(self, action: #selector(filters(_:))) + } + } + } + + @IBOutlet public var likesButton: ButtonProtocol? { + didSet { + if likesButton !== oldValue { + oldValue?.removeTarget() + likesButton?.addTarget(self, action: #selector(likes(_:))) + } + } + } + + open var leftButtons: [UIBarButtonItem]? { + return nil + } + + open var rightButtons: [UIBarButtonItem]? { + var buttons = [UIBarButtonItem]() + if isSearching == true { + if let searchDoneButton = searchDoneButton as? UIBarButtonItem { + buttons.append(searchDoneButton) + } + if let searchSaveButton = searchSaveButton as? UIBarButtonItem { + buttons.append(searchSaveButton) + } + } else { + if let viewButton = viewButton as? UIBarButtonItem { + buttons.append(viewButton) + } + if let likesButton = likesButton as? UIBarButtonItem { + buttons.append(likesButton) + } + if let filtersButton = filtersButton as? UIBarButtonItem { + buttons.append(filtersButton) + } + } + return buttons + } + + var leftBarButtonItems: [UIBarButtonItem]? { + didSet { + navigationItem.leftBarButtonItems = leftBarButtonItems + } + } + + var rightBarButtonItems: [UIBarButtonItem]? { + didSet { + navigationItem.rightBarButtonItems = rightBarButtonItems + } + } + + open var titleView: UIView? { + return searchView + } + + public var onlyShowLiked: Bool { + get { + return (presenterManager?.listInteractor as? FilteredListInteractorProtocol)?.onlyShowLiked ?? false + } + set { + (presenterManager?.listInteractor as? FilteredListInteractorProtocol)?.onlyShowLiked = newValue + self.updateLikedButton() + } + } + + open var filtersRoute: String? { return nil } + + override open func viewDidLoad() { + super.viewDidLoad() + isSearching = false + } + + override open func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + updateLikedButton() + updateFiltersButton() + searchingChanged(animated: false) + } + + @IBAction func filters(_ sender: Any?) { + if let filtersRoute = filtersRoute { + Router.shared?.navigate(to: RoutingRequest(path: filtersRoute), animated: true, completion: nil) + } + } + + open override func searchingChanged(animated: Bool) { + super.searchingChanged(animated: animated) + leftBarButtonItems = leftButtons + rightBarButtonItems = rightButtons + navigationItem.titleView = titleView + } + + @IBAction func likes(_ sender: Any?) { + onlyShowLiked = !onlyShowLiked + } + + @IBAction override open func saveSearch(_ sender: Any?) { + PrompterFactory.shared?.textPrompter().prompt(title: "Save Search", message: "Enter a name", text: nil, placeholder: nil, completion: { [weak self] text, ok in + if let self = self, ok { + self.searchBar?.resignFirstResponder() + if let text = text, let filteredInteractor = self.presenterManager?.listInteractor as? FilteredListInteractorProtocol { + self.savedSearchManager?.add(name: text, search: filteredInteractor.filterText, filters: filteredInteractor.filters?.data) + } + } + }) + } + + override open func configureSearchBar() { + super.configureSearchBar() + #if _iOS + searchBar?.showsBookmarkButton = (savedSearchManager?.savedSearches?.count != 0) + #endif + } + + open func updateLikedButton() { + let imageName = onlyShowLiked ? "action_liked" : "action_like" + likesButton?.buttonImage = UIImage.named(imageName, bundles: Bundle.particles) + } + + open func updateFiltersButton() { + let data = (presenterManager?.listInteractor as? FilteredListInteractorProtocol)?.filters?.data + let filtersCount = data?.count ?? 0 + let imageName = filtersCount > 0 ? "view_filters_on" : "view_filters" + filtersButton?.buttonImage = UIImage.named(imageName, bundles: Bundle.particles) +// filtersButton?.pp_addBadge(withNumber: filtersCount) + } + + override open func searchTextChanged() { + super.searchTextChanged() + (presenterManager?.listInteractor as? FilteredListInteractorProtocol)?.filterText = searchText?.trim() + } +} diff --git a/PlatformParticles/PlatformParticles/_ViewController/TrackingViewController.swift b/PlatformParticles/PlatformParticles/_ViewController/TrackingViewController.swift new file mode 100644 index 000000000..2fddb2a58 --- /dev/null +++ b/PlatformParticles/PlatformParticles/_ViewController/TrackingViewController.swift @@ -0,0 +1,35 @@ +// +// TrackingViewController.swift +// PlatformParticles +// +// Created by Qiang Huang on 12/20/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import PlatformRouting +import UIToolkits +import Utilities + +open class TrackingViewController: NavigableViewController { + + override open func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if #available(iOS 13.0, *) { + navigationController?.navigationBar.setNeedsLayout() + } + } + + override open func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + Tracking.shared?.leave(history?.path) + } + + override open func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if let self = self as? TrackingViewProtocol { + self.logScreenView() + } + } +} diff --git a/PlatformParticles/PlatformParticles/_ViewController/UpgradeViewController.swift b/PlatformParticles/PlatformParticles/_ViewController/UpgradeViewController.swift new file mode 100644 index 000000000..6d4ff1dbd --- /dev/null +++ b/PlatformParticles/PlatformParticles/_ViewController/UpgradeViewController.swift @@ -0,0 +1,78 @@ +// +// UpgradeViewController.swift +// PlatformParticles +// +// Created by Qiang Huang on 8/24/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import RoutingKit +import UIToolkits +import Utilities + +open class UpgradeViewController: TrackingViewController { + @IBInspectable var url: String? + @IBInspectable var text: String? { + didSet { + if text != oldValue { + updateButton() + } + } + } + + @IBInspectable var msg: String? { + didSet { + if msg != oldValue { + updateMessage() + } + } + } + + @IBOutlet var messageLabel: LabelProtocol? { + didSet { + if messageLabel !== oldValue { + updateMessage() + } + } + } + + @IBOutlet var upgradeButton: ButtonProtocol? { + didSet { + if upgradeButton !== oldValue { + updateButton() + oldValue?.removeTarget() + upgradeButton?.addTarget(self, action: #selector(upgrade(_:))) + } + } + } + + open override func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { + if request?.path == "/upgrade" { + if let text = parser.asString(request?.params?["texts"]) { + self.text = text + } + if let url = parser.asString(request?.params?["url"]) { + self.url = url + } + return true + } + return false + } + + private func updateButton() { + if upgradeButton?.buttonImage == nil { + upgradeButton?.buttonTitle = text + } + } + + private func updateMessage() { + messageLabel?.text = msg + } + + @IBAction func upgrade(_ sender: Any?) { + if let url = url, let upgradeUrl = URL(string: url) { + URLHandler.shared?.open(upgradeUrl, completionHandler: nil) + } + } +} diff --git a/PlatformParticles/PlatformParticles/_Xib/XibPresenterProtocol.swift b/PlatformParticles/PlatformParticles/_Xib/XibPresenterProtocol.swift new file mode 100644 index 000000000..dcb31c66a --- /dev/null +++ b/PlatformParticles/PlatformParticles/_Xib/XibPresenterProtocol.swift @@ -0,0 +1,26 @@ +// +// XibPresenterProtocol.swift +// ParticlesKit +// +// Created by Qiang Huang on 1/16/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ParticlesKit +import Utilities + +public protocol XibPresenterProtocol: NSObjectProtocol { + var xibCache: XibPresenterCache { get set } + func xib(object: ModelObjectProtocol?) -> String? + func defaultSize(xib: String?) -> CGSize? +} + +public extension XibPresenterProtocol { + func xib(object: ModelObjectProtocol?) -> String? { + return xibCache.xib(object: object) + } + + func defaultSize(xib: String?) -> CGSize? { + return xibCache.defaultSize(xib: xib) + } +} diff --git a/PlatformParticles/PlatformParticlesAppleTV/Info.plist b/PlatformParticles/PlatformParticlesAppleTV/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/PlatformParticles/PlatformParticlesAppleTV/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/PlatformParticles/PlatformParticlesAppleTV/PlatformParticles.h b/PlatformParticles/PlatformParticlesAppleTV/PlatformParticles.h new file mode 100644 index 000000000..f8feef3e0 --- /dev/null +++ b/PlatformParticles/PlatformParticlesAppleTV/PlatformParticles.h @@ -0,0 +1,19 @@ +// +// PlatformParticlesAppleTV.h +// PlatformParticlesAppleTV +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for PlatformParticlesAppleTV. +FOUNDATION_EXPORT double PlatformParticlesAppleTVVersionNumber; + +//! Project version string for PlatformParticlesAppleTV. +FOUNDATION_EXPORT const unsigned char PlatformParticlesAppleTVVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/PlatformParticles/PlatformParticlesAppleTVTests/Info.plist b/PlatformParticles/PlatformParticlesAppleTVTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/PlatformParticles/PlatformParticlesAppleTVTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/PlatformParticles/PlatformParticlesAppleTVTests/PlatformParticlesAppleTVTests.swift b/PlatformParticles/PlatformParticlesAppleTVTests/PlatformParticlesAppleTVTests.swift new file mode 100644 index 000000000..a24ec83e5 --- /dev/null +++ b/PlatformParticles/PlatformParticlesAppleTVTests/PlatformParticlesAppleTVTests.swift @@ -0,0 +1,32 @@ +// +// PlatformParticlesAppleTVTests.swift +// PlatformParticlesAppleTVTests +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +@testable import PlatformParticles +import XCTest + +class PlatformParticlesAppleTVTests: XCTestCase { + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. + } + } +} diff --git a/PlatformParticles/PlatformParticlesAppleWatch/ExtensionDelegate/DefaultExtensionDelegate.swift b/PlatformParticles/PlatformParticlesAppleWatch/ExtensionDelegate/DefaultExtensionDelegate.swift new file mode 100644 index 000000000..d3dc1dcf6 --- /dev/null +++ b/PlatformParticles/PlatformParticlesAppleWatch/ExtensionDelegate/DefaultExtensionDelegate.swift @@ -0,0 +1,24 @@ +// +// DefaultExtensionDelegate.swift +// PlatformParticlesAppleWatch +// +// Created by Qiang Huang on 12/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import PlatformRouting +import RoutingKit +import Utilities + +open class DefaultExtensionDelegate: RoutingExtensionDelegate { + open override func applicationDidFinishLaunching() { + inject() + + super.applicationDidFinishLaunching() + } + + open func inject() { + Injection.shared = ParticlesInjection() + } +} diff --git a/PlatformParticles/PlatformParticlesAppleWatch/Info.plist b/PlatformParticles/PlatformParticlesAppleWatch/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/PlatformParticles/PlatformParticlesAppleWatch/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/PlatformParticles/PlatformParticlesAppleWatch/List Presenter/InterfaceController/ListPresenterInterfaceController.swift b/PlatformParticles/PlatformParticlesAppleWatch/List Presenter/InterfaceController/ListPresenterInterfaceController.swift new file mode 100644 index 000000000..9303ba855 --- /dev/null +++ b/PlatformParticles/PlatformParticlesAppleWatch/List Presenter/InterfaceController/ListPresenterInterfaceController.swift @@ -0,0 +1,34 @@ +// +// ListPresenterInterfaceController.swift +// PlatformParticlesAppleWatch +// +// Created by Qiang Huang on 12/9/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import PlatformRouting + +open class ListPresenterInterfaceController: RoutingInterfaceController { + open var interactor: ListInteractor? { + didSet { + if interactor !== oldValue { + presenter?.interactor = interactor + } + } + } + + open var presenter: TableViewListPresenter? { + didSet { + if presenter !== oldValue { + presenter?.interactor = interactor + } + } + } + + open override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) { + if table === presenter?.tableView { + presenter?.select(index: rowIndex, completion: nil) + } + } +} diff --git a/PlatformParticles/PlatformParticlesAppleWatch/List Presenter/Table/TableViewListPresenter.swift b/PlatformParticles/PlatformParticlesAppleWatch/List Presenter/Table/TableViewListPresenter.swift new file mode 100644 index 000000000..355866ab9 --- /dev/null +++ b/PlatformParticles/PlatformParticlesAppleWatch/List Presenter/Table/TableViewListPresenter.swift @@ -0,0 +1,444 @@ +// +// TableViewListPresenter.swift +// PlatformParticlesAppleWatch +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Differ +import ParticlesKit +import UIKit +import UIToolkits +import Utilities + +open class TableViewListPresenter: XibListPresenter, UITableViewDataSource, UITableViewDelegate { + @IBInspectable var parallax: Bool = false { + didSet { + tableViewXibRegister.parallax = parallax + } + } + + override open var selectionHandler: SelectionHandlerProtocol? { + didSet { + if selectionHandler !== oldValue { + for section in sections { + section.tableView = tableView + section.selectionHandler = selectionHandler + } + } + } + } + + @IBInspectable var pullDownToRefresh: Bool = false { + didSet { + if pullDownToRefresh != oldValue { + refreshControl = pullDownToRefresh ? UIRefreshControl() : nil + } + } + } + + open var refreshControl: UIRefreshControl? { + didSet { + if refreshControl !== oldValue { + if let refreshControl = refreshControl { + refreshControl.addTarget(self, action: #selector(pullDownToRefresh(_:)), for: .valueChanged) + } + tableView?.refreshControl = refreshControl + } + } + } + + @IBInspectable var disclosure: Bool = false { + didSet { + if let cells = tableView?.visibleCells { + for cell in cells { + cell.accessoryType = accessory(for: cell) + } + } + } + } + + @IBInspectable public var cellBackgroundColor: UIColor? + @IBInspectable var sectionHeaderXib: String? + @IBInspectable var sectionFooterXib: String? + @IBInspectable var headerXib: String? + @IBInspectable var footerXib: String? + @IBInspectable var intMode: Int { + get { return mode.rawValue } + set { mode = PresenterMode(rawValue: newValue) ?? .linear } + } + + @IBInspectable var animatedChange: Bool = false { + didSet { + if animatedChange != oldValue { + for section in sections { + section.animatedChange = animatedChange + } + } + } + } + + @IBInspectable var scrollToSelection: Bool = false { + didSet { + if scrollToSelection != oldValue { + for section in sections { + section.scrollToSelection = scrollToSelection && (visible ?? false) + } + } + } + } + + override open var visible: Bool? { + didSet { + if visible != oldValue { + for section in sections { + section.scrollToSelection = scrollToSelection && (visible ?? false) + } + } + } + } + + @IBOutlet open var tableView: UITableView? { + didSet { + if tableView !== oldValue { + oldValue?.dataSource = nil + oldValue?.delegate = nil + tableView?.dataSource = self + tableView?.delegate = self + tableViewXibRegister.tableView = tableView + for section in sections { + section.tableView = tableView + section.selectionHandler = selectionHandler + } + if interactor != nil { + refresh(animated: false) { [weak self] in + self?.updateCompleted(firstContent: true) + } + } + if let headerView: UIView = XibLoader.load(from: headerXib) { + headerView.autoresizingMask = .flexibleWidth + headerView.translatesAutoresizingMaskIntoConstraints = true + tableView?.tableHeaderView = headerView + } + if let footerView: UIView = XibLoader.load(from: footerXib) { + footerView.autoresizingMask = .flexibleWidth + footerView.translatesAutoresizingMaskIntoConstraints = true + tableView?.tableFooterView = footerView + } + tableView?.refreshControl = refreshControl + } + } + } + + private var debouncer: Debouncer = Debouncer() + + public var mode: PresenterMode = .linear { + didSet { + if mode != oldValue { + if let handler = updateDebouncer.debounce() { + handler.run({ [weak self] in + if let self = self { + self.current = self.pending + self.refresh(animated: true) { [weak self] in + self?.updateCompleted(firstContent: false) + } + } + }, delay: nil) + } + } + } + } + + private var tableViewXibRegister: TableViewXibRegister = TableViewXibRegister() + private var tableViewHeaderXibRegister: TableViewHeaderXibRegister = TableViewHeaderXibRegister() + open var sections: [TableViewSectionListPresenter] = [] + + override open var title: String? { + return "List" + } + + override open var icon: UIImage? { + return UIImage.named("view_list", bundles: Bundle.particles) + } + + open override func didSetInteractor(oldValue: ListInteractor?) { + super.didSetInteractor(oldValue: oldValue) + if interactor != oldValue { + switch mode { + case .linear: + sections.removeAll() + if let interactor = interactor { + sections.append(section(for: interactor, index: 0)) + } + refresh(animated: true) { [weak self] in + self?.updateCompleted(firstContent: true) + } + + case .sections: + break + } + } + } + + open override func didSetCurrent(oldValue: [ModelObjectProtocol]?) { + super.didSetCurrent(oldValue: oldValue) + if mode == .sections, let list = current as? [ListInteractor] { + var index = 0 + self.sections = list.map({ (child: ListInteractor) -> TableViewSectionListPresenter in + let tableSection = section(for: child, index: index) + index += 1 + return tableSection + }) + } + } + + internal func section(for interactor: ListInteractor, index: Int) -> TableViewSectionListPresenter { + let section = self.section(with: interactor) ?? TableViewSectionListPresenter() + if section.xibRegister !== tableViewXibRegister { + section.xibRegister = tableViewXibRegister + } + if section.headerXibRegister !== tableViewHeaderXibRegister { + section.headerXibRegister = tableViewHeaderXibRegister + } + if section.xibMap != xibMap { + section.xibMap = xibMap + } + if section.sequence != index { + section.sequence = index + } + if section.interactor !== interactor { + section.interactor = interactor + } + if section.selectionHandler !== selectionHandler { + section.selectionHandler = selectionHandler + } + if section.sectionHeaderXib != sectionHeaderXib { + section.sectionHeaderXib = sectionHeaderXib + } + if section.sectionFooterXib != sectionFooterXib { + section.sectionFooterXib = sectionFooterXib + } + section.animatedChange = animatedChange + section.scrollToSelection = scrollToSelection + if section.tableView !== tableView { + section.tableView = tableView + } + return section + } + + internal func section(with interactor: ListInteractor) -> TableViewSectionListPresenter? { + return sections.first(where: { (presenter) -> Bool in + presenter.interactor === interactor + }) + } + + override open func refresh(animated: Bool, completion: (() -> Void)?) { + if animated { + refreshControl?.endRefreshing() + UIView.animate(tableView, type: .fade, direction: .none, duration: UIView.defaultAnimationDuration, animations: { [weak self] in + if let self = self { + self.tableView?.reloadData() + completion?() + } + }, completion: nil) + } else { + tableView?.reloadData() + completion?() + } + } + + open func numberOfSections(in tableView: UITableView) -> Int { + if interactor == nil { + return 0 + } else { + return mode == .sections ? sections.count : 1 + } + } + + open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if mode == .sections { + let sectionPresenter = sections[section] + return sectionPresenter.count ?? 0 + } else { + return sections.first?.count ?? 0 + } + } + + open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if let section = (mode == .sections) ? sections[indexPath.section] : sections.first { + let cell = section.cell(indexPath: indexPath) ?? UITableViewCell() + if tableView.estimatedRowHeight <= 0 { + tableView.estimatedRowHeight = cell.frame.size.height + } + cell.accessoryType = accessory(for: cell) + if let cellBackgroundColor = cellBackgroundColor { + cell.backgroundColor = cellBackgroundColor + } + if ((cell as? ObjectPresenterTableViewCell)?.presenterView as? ObjectPresenterProtocol)?.selectable ?? false { + cell.selectionStyle = tableView.allowsMultipleSelection ? .none : .default + } else { + cell.selectionStyle = .none + } + return cell + } else { + return UITableViewCell() + } + } + + open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let cell = tableView.cellForRow(at: indexPath) { + UserInteraction.shared.sender = cell + } + if let section = (mode == .sections) ? sections[indexPath.section] : sections.first { + section.select(to: indexPath) + } + } + + public func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { + if let section = (mode == .sections && indexPath.section < sections.count) ? sections[indexPath.section] : sections.first { + section.deselect(indexPath: indexPath) + } + } + + open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + if let section = (mode == .sections && indexPath.section < sections.count) ? sections[indexPath.section] : sections.first { + if let size = section.defaultSize(at: indexPath.row) { + return size.height + } + } + return 44 + } + + + open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + if let tableSection = (mode == .sections) ? sections[section] : sections.first { + return tableSection.headerViewSize() ?? defaultSize(xib: tableSection.sectionHeaderXib)?.height ?? 0 + } + return 0 + } + + open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if let tableSection = (mode == .sections) ? sections[section] : sections.first { + return tableSection.headerView() ?? accessoryView(xib: tableSection.sectionHeaderXib, section: section) + } + return nil + } + + open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + if let tableSection = (mode == .sections) ? sections[section] : sections.first { + if let footerView = tableSection.footerView() { + return footerView.frame.size.height + } else if let size = defaultSize(xib: tableSection.sectionFooterXib) { + return size.height + } + } + return 0 + } + + open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + if let tableSection = (mode == .sections) ? sections[section] : sections.first { + return tableSection.footerView() ?? accessoryView(xib: tableSection.sectionFooterXib, section: section) + } + return nil + } + + open func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { + return true + } + + open func accessoryView(xib: String?, section: Int) -> UIView? { + if let view: UIView = XibLoader.load(from: xib) { + if let presenterView: ObjectPresenterView = view as? ObjectPresenterView { + if mode == .linear { + presenterView.model = interactor?.parent + } else { + presenterView.model = object(at: section) + } + } + return view + } + return nil + } + + open func accessory(for cell: UITableViewCell) -> UITableViewCell.AccessoryType { + return showDisclosure(cell: cell) ? .disclosureIndicator : .none + } + + open func showDisclosure(cell: UITableViewCell) -> Bool { + if let presentingCell = cell as? ObjectPresenterTableViewCell { + return presentingCell.showDisclosure ?? disclosure + } + return disclosure + } + + override open func update() { + if tableView != nil { + let firstContent = (current == nil) + if mode == .sections { + current = pending + refresh(animated: true) { [weak self] in + self?.updateCompleted(firstContent: firstContent) + } + } else { + update(move: true) + updateCompleted(firstContent: firstContent) + } + } else { + current = pending + } + } + + override open func update(diff: Diff, patches: [Patch], current: [ModelObjectProtocol]?) { + tableView?.performBatchUpdates({ + for change in patches { + switch change { + case let .deletion(index): + tableView?.deleteSections(IndexSet(integer: index), with: .fade) + + case let .insertion(index: index, element: _): + tableView?.insertSections(IndexSet(integer: index), with: .fade) + } + } + }, completion: nil) + } + + override open func updateLayout() { + if let handler = debouncer.debounce() { + handler.run({ [weak self] in + if let self = self { + self.tableView?.beginUpdates() + self.tableView?.endUpdates() + Console.shared.log("endUpdate/updateLayout") + } + }, delay: 0.5) + } + } + + @objc open func pullDownToRefresh(_ sender: Any?) { + } + + override open func changed(selected: [ModelObjectProtocol]?) { + if let tableSection = (mode == .sections) ? nil : sections.first { + tableSection.changed(selected: selected) + } + } +} + +extension TableViewListPresenter { + public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + if parallax { + (cell as? ParallaxObjectPresenterTableViewCell)?.parallax(animated: true) + } + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + if parallax { + if let visibleCells = tableView?.visibleCells { + for cell in visibleCells { + (cell as? ParallaxObjectPresenterTableViewCell)?.parallax(animated: true) + } + } + } + } +} diff --git a/PlatformParticles/PlatformParticlesAppleWatch/List Presenter/XibListPresenter.swift b/PlatformParticles/PlatformParticlesAppleWatch/List Presenter/XibListPresenter.swift new file mode 100644 index 000000000..c45ad4e9e --- /dev/null +++ b/PlatformParticles/PlatformParticlesAppleWatch/List Presenter/XibListPresenter.swift @@ -0,0 +1,53 @@ +// +// XibListPresenter.swift +// PresenterLib +// +// Created by Qiang Huang on 10/9/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits +import WatchKit + +open class XibListPresenter: ListPresenter { + @IBInspectable var xibMap: String? { + didSet { + if xibMap != oldValue { + if let xibMap = xibMap { + xibMapFile = XibJsonFile(fileName: xibMap, bundle: Bundle.ui()) + } + } + } + } + + private static var sharedXibMapFile: XibJsonFile = { + XibJsonFile(fileName: "xib.json", bundle: Bundle.ui()) + }() + + private var xibMapFile: XibJsonFile? + + public func xibFile(object: ModelObjectProtocol?) -> String? { + var xib: String? + if let xibProvider = object as? XibProviderProtocol { + xib = xibProvider.xib + } + if xib == nil { + if let xibFile = xibMapFile { + xib = xibFile.xibFile(object: object) + } + } + if xib == nil { + let xibFile = XibListPresenter.sharedXibMapFile + xib = xibFile.xibFile(object: object) + } + return xib + } + + public func xib(object: ModelObjectProtocol?) -> String? { + if let xibFile = self.xibFile(object: object) { + return xibFile + } + return nil + } +} diff --git a/PlatformParticles/PlatformParticlesAppleWatch/PlatformParticles.h b/PlatformParticles/PlatformParticlesAppleWatch/PlatformParticles.h new file mode 100644 index 000000000..b2eb429c6 --- /dev/null +++ b/PlatformParticles/PlatformParticlesAppleWatch/PlatformParticles.h @@ -0,0 +1,17 @@ +// +// PlatformParticlesAppleWatch.h +// PlatformParticlesAppleWatch +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for PlatformParticlesAppleWatch. +FOUNDATION_EXPORT double PlatformParticlesAppleWatchVersionNumber; + +//! Project version string for PlatformParticlesAppleWatch. +FOUNDATION_EXPORT const unsigned char PlatformParticlesAppleWatchVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/PlatformParticles/PlatformParticlesTests/Info.plist b/PlatformParticles/PlatformParticlesTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/PlatformParticles/PlatformParticlesTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/PlatformParticles/PlatformParticlesTests/PlatformParticlesTests.swift b/PlatformParticles/PlatformParticlesTests/PlatformParticlesTests.swift new file mode 100644 index 000000000..442a191a5 --- /dev/null +++ b/PlatformParticles/PlatformParticlesTests/PlatformParticlesTests.swift @@ -0,0 +1,34 @@ +// +// PlatformParticlesTests.swift +// PlatformParticlesTests +// +// Created by Qiang Huang on 11/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import XCTest +@testable import PlatformParticles + +class PlatformParticlesTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/PlatformRouting/PlatformRouting.xcodeproj/project.pbxproj b/PlatformRouting/PlatformRouting.xcodeproj/project.pbxproj new file mode 100644 index 000000000..dc2caa742 --- /dev/null +++ b/PlatformRouting/PlatformRouting.xcodeproj/project.pbxproj @@ -0,0 +1,1824 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 278A4EDC2B92C62A003898EB /* FadeTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278A4EDB2B92C62A003898EB /* FadeTransition.swift */; }; + 278A4EDE2B92C639003898EB /* TransitionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278A4EDD2B92C639003898EB /* TransitionDelegate.swift */; }; + 310CDBAD21C6FA44009665CF /* UIToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBC0D21BB7B7E00BEF926 /* UIToolkits.framework */; }; + 311BAFEB255083C1004331D8 /* Mail.xib in Resources */ = {isa = PBXBuildFile; fileRef = 311BAFDE255083C1004331D8 /* Mail.xib */; }; + 311BB01F2550845A004331D8 /* Share.xib in Resources */ = {isa = PBXBuildFile; fileRef = 311BB01E2550845A004331D8 /* Share.xib */; }; + 311D6FA92176F04900655040 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 311D6F702176EF7B00655040 /* Utilities.framework */; }; + 311D6FAA2176F04900655040 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 311D6F5E2176EF6E00655040 /* RoutingKit.framework */; }; + 311D6FAB2176F04900655040 /* UIToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 311D6F672176EF7500655040 /* UIToolkits.framework */; }; + 31308B6424FAEDCC003B5B9A /* routing_shared.json in Resources */ = {isa = PBXBuildFile; fileRef = 31308B6324FAEDCC003B5B9A /* routing_shared.json */; }; + 31308B6524FAEDCC003B5B9A /* routing_shared.json in Resources */ = {isa = PBXBuildFile; fileRef = 31308B6324FAEDCC003B5B9A /* routing_shared.json */; }; + 31331DC6255345E40050D74C /* SimpleLocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31331DC4255345E40050D74C /* SimpleLocalNotification.swift */; }; + 313EB76321BB4CA300BEF926 /* PlatformRouting.h in Headers */ = {isa = PBXBuildFile; fileRef = 313EB76121BB4CA300BEF926 /* PlatformRouting.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 313EB76721BB4CCB00BEF926 /* MappedAppleWatchRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EB75821BB4C6500BEF926 /* MappedAppleWatchRouter.swift */; }; + 313EB76821BB4CCB00BEF926 /* RoutingInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EB75421BB4C3700BEF926 /* RoutingInterfaceController.swift */; }; + 313EB76D21BB4D1000BEF926 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB73721BB4BE800BEF926 /* Utilities.framework */; }; + 313EB76E21BB4D1000BEF926 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB74721BB4BE800BEF926 /* RoutingKit.framework */; }; + 313EBBEE21BB7B7E00BEF926 /* PlatformRouting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBBE521BB7B7D00BEF926 /* PlatformRouting.framework */; }; + 313EBBF321BB7B7E00BEF926 /* PlatformRoutingAppleTVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EBBF221BB7B7E00BEF926 /* PlatformRoutingAppleTVTests.swift */; }; + 313EBBF521BB7B7E00BEF926 /* PlatformRouting.h in Headers */ = {isa = PBXBuildFile; fileRef = 313EBBE721BB7B7D00BEF926 /* PlatformRouting.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 313EBC3C21BB7BD500BEF926 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB73921BB4BE800BEF926 /* Utilities.framework */; }; + 313EBC3D21BB7BD500BEF926 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB74921BB4BE800BEF926 /* RoutingKit.framework */; }; + 313EBE0021BC3D9000BEF926 /* RoutingExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EBDFF21BC3D9000BEF926 /* RoutingExtensionDelegate.swift */; }; + 314B626D23DCCE8900139EB3 /* RoutingDrawerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B624F23DCCE8900139EB3 /* RoutingDrawerController.swift */; }; + 314B626E23DCCE8900139EB3 /* RoutingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625023DCCE8900139EB3 /* RoutingViewController.swift */; }; + 314B626F23DCCE8900139EB3 /* RoutingSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625123DCCE8900139EB3 /* RoutingSplitViewController.swift */; }; + 314B627123DCCE8900139EB3 /* RoutingTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625323DCCE8900139EB3 /* RoutingTabBarController.swift */; }; + 314B627223DCCE8900139EB3 /* UITabBarController+Routing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625523DCCE8900139EB3 /* UITabBarController+Routing.swift */; }; + 314B627323DCCE8900139EB3 /* UINavigationViewController+Routing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625623DCCE8900139EB3 /* UINavigationViewController+Routing.swift */; }; + 314B627423DCCE8900139EB3 /* UISplitViewController+Routing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625723DCCE8900139EB3 /* UISplitViewController+Routing.swift */; }; + 314B627523DCCE8900139EB3 /* RoutingMap+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625823DCCE8900139EB3 /* RoutingMap+iOS.swift */; }; + 314B627623DCCE8900139EB3 /* EmbeddingProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625A23DCCE8900139EB3 /* EmbeddingProtocols.swift */; }; + 314B627723DCCE8900139EB3 /* EmbeddingFloatingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625B23DCCE8900139EB3 /* EmbeddingFloatingManager.swift */; }; + 314B627823DCCE8900139EB3 /* RoutingEmbeddingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625C23DCCE8900139EB3 /* RoutingEmbeddingController.swift */; }; + 314B627923DCCE8900139EB3 /* UINavigationController+Embedded.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625D23DCCE8900139EB3 /* UINavigationController+Embedded.swift */; }; + 314B627B23DCCE8900139EB3 /* MailAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B626223DCCE8900139EB3 /* MailAction.swift */; }; + 314B627C23DCCE8900139EB3 /* RateAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B626323DCCE8900139EB3 /* RateAction.swift */; }; + 314B627D23DCCE8900139EB3 /* TelAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B626423DCCE8900139EB3 /* TelAction.swift */; }; + 314B627E23DCCE8900139EB3 /* MappedUIKitExtensionRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B626623DCCE8900139EB3 /* MappedUIKitExtensionRouter.swift */; }; + 314B628023DCCE8900139EB3 /* RoutingAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B626A23DCCE8900139EB3 /* RoutingAppDelegate.swift */; }; + 314B628123DCCE8900139EB3 /* MappedUIKitRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B626B23DCCE8900139EB3 /* MappedUIKitRouter.swift */; }; + 314B628223DCCE8900139EB3 /* MappedUIKitAppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B626C23DCCE8900139EB3 /* MappedUIKitAppRouter.swift */; }; + 314B6E8523DD05B500139EB3 /* RoutingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625023DCCE8900139EB3 /* RoutingViewController.swift */; }; + 314B6E8F23DD05EE00139EB3 /* UITabBarController+Routing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625523DCCE8900139EB3 /* UITabBarController+Routing.swift */; }; + 314B6E9023DD065500139EB3 /* RoutingAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B626A23DCCE8900139EB3 /* RoutingAppDelegate.swift */; }; + 314B6E9123DD06B500139EB3 /* MappedUIKitAppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B626C23DCCE8900139EB3 /* MappedUIKitAppRouter.swift */; }; + 314B6E9223DD06CB00139EB3 /* MappedUIKitRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B626B23DCCE8900139EB3 /* MappedUIKitRouter.swift */; }; + 314B6E9323DD070600139EB3 /* RoutingMap+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625823DCCE8900139EB3 /* RoutingMap+iOS.swift */; }; + 314B6E9423DD079900139EB3 /* RoutingTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B625323DCCE8900139EB3 /* RoutingTabBarController.swift */; }; + 314B6ED923DD0AA900139EB3 /* UIAppToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310C3E0021DAAE210081E56D /* UIAppToolkits.framework */; }; + 314C35AF24C7C1A000695F7E /* Drawer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 314C35AD24C7C1A000695F7E /* Drawer.storyboard */; }; + 314C35B024C7C1A000695F7E /* Embed.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 314C35AE24C7C1A000695F7E /* Embed.storyboard */; }; + 314C35B224C7C1AA00695F7E /* EnableDebug.xib in Resources */ = {isa = PBXBuildFile; fileRef = 314C35B124C7C1AA00695F7E /* EnableDebug.xib */; }; + 314C35B624C7C29600695F7E /* DebugEnableAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314C35B524C7C29600695F7E /* DebugEnableAction.swift */; }; + 314C35B724C7C29600695F7E /* DebugEnableAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314C35B524C7C29600695F7E /* DebugEnableAction.swift */; }; + 314C35B824C7C29600695F7E /* DebugEnableAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314C35B524C7C29600695F7E /* DebugEnableAction.swift */; }; + 314D9C172485AA2D00582A1A /* RoutingContainingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314D9C162485AA2D00582A1A /* RoutingContainingController.swift */; }; + 31CEE9BF2170FA2700DC61DA /* PlatformRouting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31CEE9B52170FA2700DC61DA /* PlatformRouting.framework */; }; + 31CEE9C42170FA2700DC61DA /* PlatformRoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CEE9C32170FA2700DC61DA /* PlatformRoutingTests.swift */; }; + 31CEE9C62170FA2700DC61DA /* PlatformRouting.h in Headers */ = {isa = PBXBuildFile; fileRef = 31CEE9B82170FA2700DC61DA /* PlatformRouting.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 31F762D124BA3F5C001EA293 /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31F762C624BA3F48001EA293 /* ParticlesKit.framework */; }; + 31F762D424BA3F6C001EA293 /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31F762C824BA3F48001EA293 /* ParticlesKit.framework */; }; + 31F762D724BA3F76001EA293 /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31F762CA24BA3F48001EA293 /* ParticlesKit.framework */; }; + 9EA919B6201D5FB65A741E9F /* Pods_iOS_PlatformRouting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 21D0A34C88FFB5FB1165548D /* Pods_iOS_PlatformRouting.framework */; }; + A32C1736A60BE927D3C195A4 /* Pods_iOS_PlatformRoutingTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A4A40F832E3D20D7A978DE2 /* Pods_iOS_PlatformRoutingTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 310C3DFF21DAAE210081E56D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310C3A0B21D9623E0081E56D; + remoteInfo = UIAppToolkits; + }; + 310C3E0121DAAE210081E56D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310C3A1321D9623F0081E56D; + remoteInfo = UIAppToolkitsTests; + }; + 310CDBAA21C6F9F6009665CF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313EBB0321BB79AD00BEF926; + remoteInfo = UIToolkitsAppleTV; + }; + 311D6F5D2176EF6E00655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F582176EF6E00655040 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B01216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 311D6F5F2176EF6E00655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F582176EF6E00655040 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B0A216BCA10008ABEE9; + remoteInfo = RoutingKitTests; + }; + 311D6F662176EF7500655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65ADD216BC9E1008ABEE9; + remoteInfo = UIToolkits; + }; + 311D6F682176EF7500655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AE6216BC9E1008ABEE9; + remoteInfo = UIToolkitsTests; + }; + 311D6F6F2176EF7B00655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F6A2176EF7B00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 311D6F712176EF7B00655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F6A2176EF7B00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 311D6F732176EF8600655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F582176EF6E00655040 /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65B00216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 311D6F752176EF8600655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F6A2176EF7B00655040 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 311D6F772176EF8600655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65ADC216BC9E1008ABEE9; + remoteInfo = UIToolkits; + }; + 313EB73621BB4BE800BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F6A2176EF7B00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 313EB73821BB4BE800BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F6A2176EF7B00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 313EB73C21BB4BE800BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F6A2176EF7B00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 313EB74621BB4BE800BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F582176EF6E00655040 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196825B21B7947600AE0F28; + remoteInfo = RoutingKitAppleWatch; + }; + 313EB74821BB4BE800BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F582176EF6E00655040 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22321BA26D700BEF926; + remoteInfo = RoutingKitAppleTV; + }; + 313EB74C21BB4BE800BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F582176EF6E00655040 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22B21BA26D800BEF926; + remoteInfo = RoutingKitAppleTVTests; + }; + 313EB76921BB4CFE00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F6A2176EF7B00655040 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 3196823621B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 313EB76B21BB4CFE00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F582176EF6E00655040 /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 3196825A21B7947600AE0F28; + remoteInfo = RoutingKitAppleWatch; + }; + 313EBBEF21BB7B7E00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31CEE9AC2170FA2700DC61DA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 313EBBE421BB7B7D00BEF926; + remoteInfo = PlatformRoutingAppleTV; + }; + 313EBC0A21BB7B7E00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB85D21BB730700BEF926; + remoteInfo = UIToolkitsAppleWatch; + }; + 313EBC0C21BB7B7E00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB0421BB79AD00BEF926; + remoteInfo = UIToolkitsAppleTV; + }; + 313EBC1021BB7B7E00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB0C21BB79AD00BEF926; + remoteInfo = UIToolkitsAppleTVTests; + }; + 313EBC3221BB7BA900BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F6A2176EF7B00655040 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313A536421B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 313EBC3421BB7BA900BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F582176EF6E00655040 /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313EB22221BA26D700BEF926; + remoteInfo = RoutingKitAppleTV; + }; + 314B6EDC23DD0AB200139EB3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 310C3A0A21D9623E0081E56D; + remoteInfo = UIAppToolkits; + }; + 31CEE9C02170FA2700DC61DA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31CEE9AC2170FA2700DC61DA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31CEE9B42170FA2700DC61DA; + remoteInfo = PlatformRouting; + }; + 31F762C524BA3F48001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F762BD24BA3F48001EA293 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FDA21B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 31F762C724BA3F48001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F762BD24BA3F48001EA293 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 319682B421B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 31F762C924BA3F48001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F762BD24BA3F48001EA293 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16121BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 31F762CB24BA3F48001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F762BD24BA3F48001EA293 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FE321B0E9C4001775BA; + remoteInfo = ParticlesKitTests; + }; + 31F762CD24BA3F48001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F762BD24BA3F48001EA293 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16921BA246A00BEF926; + remoteInfo = ParticlesKitAppleTVTests; + }; + 31F762CF24BA3F51001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F762BD24BA3F48001EA293 /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 311C0FD921B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 1A4A40F832E3D20D7A978DE2 /* Pods_iOS_PlatformRoutingTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_PlatformRoutingTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 21D0A34C88FFB5FB1165548D /* Pods_iOS_PlatformRouting.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_PlatformRouting.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 278A4EDB2B92C62A003898EB /* FadeTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadeTransition.swift; sourceTree = ""; }; + 278A4EDD2B92C639003898EB /* TransitionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionDelegate.swift; sourceTree = ""; }; + 306E17287D7FE48D8EEA3648 /* Pods-iOS-PlatformRouting.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformRouting.debug.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformRouting/Pods-iOS-PlatformRouting.debug.xcconfig"; sourceTree = ""; }; + 311BAFDE255083C1004331D8 /* Mail.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Mail.xib; sourceTree = ""; }; + 311BB01E2550845A004331D8 /* Share.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Share.xib; sourceTree = ""; }; + 311D6F582176EF6E00655040 /* RoutingKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RoutingKit.xcodeproj; path = ../RoutingKit/RoutingKit.xcodeproj; sourceTree = ""; }; + 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = UIToolkits.xcodeproj; path = ../UIToolkits/UIToolkits.xcodeproj; sourceTree = ""; }; + 311D6F6A2176EF7B00655040 /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 31308B6324FAEDCC003B5B9A /* routing_shared.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = routing_shared.json; sourceTree = ""; }; + 31331DC4255345E40050D74C /* SimpleLocalNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleLocalNotification.swift; sourceTree = ""; }; + 313EB75421BB4C3700BEF926 /* RoutingInterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingInterfaceController.swift; sourceTree = ""; }; + 313EB75821BB4C6500BEF926 /* MappedAppleWatchRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MappedAppleWatchRouter.swift; sourceTree = ""; }; + 313EB75F21BB4CA300BEF926 /* PlatformRouting.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlatformRouting.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EB76121BB4CA300BEF926 /* PlatformRouting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformRouting.h; sourceTree = ""; }; + 313EB76221BB4CA300BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EBBE521BB7B7D00BEF926 /* PlatformRouting.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlatformRouting.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EBBE721BB7B7D00BEF926 /* PlatformRouting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformRouting.h; sourceTree = ""; }; + 313EBBE821BB7B7D00BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EBBED21BB7B7D00BEF926 /* PlatformRoutingAppleTVTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PlatformRoutingAppleTVTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EBBF221BB7B7E00BEF926 /* PlatformRoutingAppleTVTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformRoutingAppleTVTests.swift; sourceTree = ""; }; + 313EBBF421BB7B7E00BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EBDFF21BC3D9000BEF926 /* RoutingExtensionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingExtensionDelegate.swift; sourceTree = ""; }; + 314B624F23DCCE8900139EB3 /* RoutingDrawerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutingDrawerController.swift; sourceTree = ""; }; + 314B625023DCCE8900139EB3 /* RoutingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutingViewController.swift; sourceTree = ""; }; + 314B625123DCCE8900139EB3 /* RoutingSplitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutingSplitViewController.swift; sourceTree = ""; }; + 314B625323DCCE8900139EB3 /* RoutingTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutingTabBarController.swift; sourceTree = ""; }; + 314B625523DCCE8900139EB3 /* UITabBarController+Routing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITabBarController+Routing.swift"; sourceTree = ""; }; + 314B625623DCCE8900139EB3 /* UINavigationViewController+Routing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UINavigationViewController+Routing.swift"; sourceTree = ""; }; + 314B625723DCCE8900139EB3 /* UISplitViewController+Routing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UISplitViewController+Routing.swift"; sourceTree = ""; }; + 314B625823DCCE8900139EB3 /* RoutingMap+iOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RoutingMap+iOS.swift"; sourceTree = ""; }; + 314B625A23DCCE8900139EB3 /* EmbeddingProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddingProtocols.swift; sourceTree = ""; }; + 314B625B23DCCE8900139EB3 /* EmbeddingFloatingManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddingFloatingManager.swift; sourceTree = ""; }; + 314B625C23DCCE8900139EB3 /* RoutingEmbeddingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutingEmbeddingController.swift; sourceTree = ""; }; + 314B625D23DCCE8900139EB3 /* UINavigationController+Embedded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Embedded.swift"; sourceTree = ""; }; + 314B626223DCCE8900139EB3 /* MailAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MailAction.swift; sourceTree = ""; }; + 314B626323DCCE8900139EB3 /* RateAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RateAction.swift; sourceTree = ""; }; + 314B626423DCCE8900139EB3 /* TelAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelAction.swift; sourceTree = ""; }; + 314B626623DCCE8900139EB3 /* MappedUIKitExtensionRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappedUIKitExtensionRouter.swift; sourceTree = ""; }; + 314B626A23DCCE8900139EB3 /* RoutingAppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutingAppDelegate.swift; sourceTree = ""; }; + 314B626B23DCCE8900139EB3 /* MappedUIKitRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappedUIKitRouter.swift; sourceTree = ""; }; + 314B626C23DCCE8900139EB3 /* MappedUIKitAppRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappedUIKitAppRouter.swift; sourceTree = ""; }; + 314C35AD24C7C1A000695F7E /* Drawer.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Drawer.storyboard; sourceTree = ""; }; + 314C35AE24C7C1A000695F7E /* Embed.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Embed.storyboard; sourceTree = ""; }; + 314C35B124C7C1AA00695F7E /* EnableDebug.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EnableDebug.xib; sourceTree = ""; }; + 314C35B524C7C29600695F7E /* DebugEnableAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugEnableAction.swift; sourceTree = ""; }; + 314D9C162485AA2D00582A1A /* RoutingContainingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingContainingController.swift; sourceTree = ""; }; + 31CEE9B52170FA2700DC61DA /* PlatformRouting.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlatformRouting.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31CEE9B82170FA2700DC61DA /* PlatformRouting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformRouting.h; sourceTree = ""; }; + 31CEE9B92170FA2700DC61DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31CEE9BE2170FA2700DC61DA /* PlatformRoutingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PlatformRoutingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 31CEE9C32170FA2700DC61DA /* PlatformRoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformRoutingTests.swift; sourceTree = ""; }; + 31CEE9C52170FA2700DC61DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31CEE9D02170FA3600DC61DA /* RoutingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RoutingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31F762BD24BA3F48001EA293 /* ParticlesKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParticlesKit.xcodeproj; path = ../ParticlesKit/ParticlesKit.xcodeproj; sourceTree = ""; }; + 86201953C1DEEFBB5C8207DA /* Pods-iOS-PlatformRouting.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformRouting.release.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformRouting/Pods-iOS-PlatformRouting.release.xcconfig"; sourceTree = ""; }; + C92D500101D7BB756DE1D820 /* Pods-iOS-PlatformRoutingTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformRoutingTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformRoutingTests/Pods-iOS-PlatformRoutingTests.debug.xcconfig"; sourceTree = ""; }; + F0F3C6868E6D2A68FE400D98 /* Pods-iOS-PlatformRoutingTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformRoutingTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformRoutingTests/Pods-iOS-PlatformRoutingTests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 313EB75C21BB4CA300BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB76D21BB4D1000BEF926 /* Utilities.framework in Frameworks */, + 31F762D424BA3F6C001EA293 /* ParticlesKit.framework in Frameworks */, + 313EB76E21BB4D1000BEF926 /* RoutingKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBBE221BB7B7D00BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 310CDBAD21C6FA44009665CF /* UIToolkits.framework in Frameworks */, + 31F762D724BA3F76001EA293 /* ParticlesKit.framework in Frameworks */, + 313EBC3C21BB7BD500BEF926 /* Utilities.framework in Frameworks */, + 313EBC3D21BB7BD500BEF926 /* RoutingKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBBEA21BB7B7D00BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBBEE21BB7B7E00BEF926 /* PlatformRouting.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CEE9B22170FA2700DC61DA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B6ED923DD0AA900139EB3 /* UIAppToolkits.framework in Frameworks */, + 311D6FA92176F04900655040 /* Utilities.framework in Frameworks */, + 311D6FAB2176F04900655040 /* UIToolkits.framework in Frameworks */, + 311D6FAA2176F04900655040 /* RoutingKit.framework in Frameworks */, + 31F762D124BA3F5C001EA293 /* ParticlesKit.framework in Frameworks */, + 9EA919B6201D5FB65A741E9F /* Pods_iOS_PlatformRouting.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CEE9BB2170FA2700DC61DA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31CEE9BF2170FA2700DC61DA /* PlatformRouting.framework in Frameworks */, + A32C1736A60BE927D3C195A4 /* Pods_iOS_PlatformRoutingTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02684F0328BD41660007CEFF /* Dependencies */ = { + isa = PBXGroup; + children = ( + 31F762BD24BA3F48001EA293 /* ParticlesKit.xcodeproj */, + 311D6F582176EF6E00655040 /* RoutingKit.xcodeproj */, + 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */, + 311D6F6A2176EF7B00655040 /* Utilities.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 278A4ECE2B92C4EA003898EB /* _Transitions */ = { + isa = PBXGroup; + children = ( + 278A4EDB2B92C62A003898EB /* FadeTransition.swift */, + 278A4EDD2B92C639003898EB /* TransitionDelegate.swift */, + ); + path = _Transitions; + sourceTree = ""; + }; + 311D6F592176EF6E00655040 /* Products */ = { + isa = PBXGroup; + children = ( + 311D6F5E2176EF6E00655040 /* RoutingKit.framework */, + 313EB74721BB4BE800BEF926 /* RoutingKit.framework */, + 313EB74921BB4BE800BEF926 /* RoutingKit.framework */, + 311D6F602176EF6E00655040 /* RoutingKitTests.xctest */, + 313EB74D21BB4BE800BEF926 /* RoutingKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 311D6F622176EF7500655040 /* Products */ = { + isa = PBXGroup; + children = ( + 311D6F672176EF7500655040 /* UIToolkits.framework */, + 313EBC0B21BB7B7E00BEF926 /* UIToolkits.framework */, + 313EBC0D21BB7B7E00BEF926 /* UIToolkits.framework */, + 310C3E0021DAAE210081E56D /* UIAppToolkits.framework */, + 311D6F692176EF7500655040 /* UIToolkitsTests.xctest */, + 313EBC1121BB7B7E00BEF926 /* UIToolkitsAppleTVTests.xctest */, + 310C3E0221DAAE210081E56D /* UIAppToolkitsTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 311D6F6B2176EF7B00655040 /* Products */ = { + isa = PBXGroup; + children = ( + 311D6F702176EF7B00655040 /* Utilities.framework */, + 313EB73721BB4BE800BEF926 /* Utilities.framework */, + 313EB73921BB4BE800BEF926 /* Utilities.framework */, + 311D6F722176EF7B00655040 /* UtilitiesTests.xctest */, + 313EB73D21BB4BE800BEF926 /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31308B5624FAEDB5003B5B9A /* _UIKit */ = { + isa = PBXGroup; + children = ( + 31308B6324FAEDCC003B5B9A /* routing_shared.json */, + ); + path = _UIKit; + sourceTree = ""; + }; + 31331DC3255345E40050D74C /* _LocalNotification */ = { + isa = PBXGroup; + children = ( + 31331DC4255345E40050D74C /* SimpleLocalNotification.swift */, + ); + path = _LocalNotification; + sourceTree = ""; + }; + 313EB75321BB4C0100BEF926 /* AppleWatch */ = { + isa = PBXGroup; + children = ( + 313EB75721BB4C4C00BEF926 /* Router */, + 313EB75621BB4C3C00BEF926 /* InterfaceController */, + ); + path = AppleWatch; + sourceTree = ""; + }; + 313EB75621BB4C3C00BEF926 /* InterfaceController */ = { + isa = PBXGroup; + children = ( + 313EB75421BB4C3700BEF926 /* RoutingInterfaceController.swift */, + ); + path = InterfaceController; + sourceTree = ""; + }; + 313EB75721BB4C4C00BEF926 /* Router */ = { + isa = PBXGroup; + children = ( + 313EB75821BB4C6500BEF926 /* MappedAppleWatchRouter.swift */, + ); + path = Router; + sourceTree = ""; + }; + 313EB76021BB4CA300BEF926 /* PlatformRoutingAppleWatch */ = { + isa = PBXGroup; + children = ( + 313EB75321BB4C0100BEF926 /* AppleWatch */, + 313EBDFE21BC3D7500BEF926 /* ExtensionDelegate */, + 313EB76121BB4CA300BEF926 /* PlatformRouting.h */, + 313EB76221BB4CA300BEF926 /* Info.plist */, + ); + path = PlatformRoutingAppleWatch; + sourceTree = ""; + }; + 313EBBE621BB7B7D00BEF926 /* PlatformRoutingAppleTV */ = { + isa = PBXGroup; + children = ( + 313EBBE721BB7B7D00BEF926 /* PlatformRouting.h */, + 313EBBE821BB7B7D00BEF926 /* Info.plist */, + ); + path = PlatformRoutingAppleTV; + sourceTree = ""; + }; + 313EBBF121BB7B7E00BEF926 /* PlatformRoutingAppleTVTests */ = { + isa = PBXGroup; + children = ( + 313EBBF221BB7B7E00BEF926 /* PlatformRoutingAppleTVTests.swift */, + 313EBBF421BB7B7E00BEF926 /* Info.plist */, + ); + path = PlatformRoutingAppleTVTests; + sourceTree = ""; + }; + 313EBDFE21BC3D7500BEF926 /* ExtensionDelegate */ = { + isa = PBXGroup; + children = ( + 313EBDFF21BC3D9000BEF926 /* RoutingExtensionDelegate.swift */, + ); + path = ExtensionDelegate; + sourceTree = ""; + }; + 314B624E23DCCE8900139EB3 /* _ViewController */ = { + isa = PBXGroup; + children = ( + 314B624F23DCCE8900139EB3 /* RoutingDrawerController.swift */, + 314B625123DCCE8900139EB3 /* RoutingSplitViewController.swift */, + 314B625323DCCE8900139EB3 /* RoutingTabBarController.swift */, + 314B625023DCCE8900139EB3 /* RoutingViewController.swift */, + ); + path = _ViewController; + sourceTree = ""; + }; + 314B625423DCCE8900139EB3 /* _Router */ = { + isa = PBXGroup; + children = ( + 314B625823DCCE8900139EB3 /* RoutingMap+iOS.swift */, + 314B625623DCCE8900139EB3 /* UINavigationViewController+Routing.swift */, + 314B625723DCCE8900139EB3 /* UISplitViewController+Routing.swift */, + 314B625523DCCE8900139EB3 /* UITabBarController+Routing.swift */, + ); + path = _Router; + sourceTree = ""; + }; + 314B625923DCCE8900139EB3 /* _Floating */ = { + isa = PBXGroup; + children = ( + 314B625B23DCCE8900139EB3 /* EmbeddingFloatingManager.swift */, + 314B625A23DCCE8900139EB3 /* EmbeddingProtocols.swift */, + 314D9C162485AA2D00582A1A /* RoutingContainingController.swift */, + 314B625C23DCCE8900139EB3 /* RoutingEmbeddingController.swift */, + 314B625D23DCCE8900139EB3 /* UINavigationController+Embedded.swift */, + ); + path = _Floating; + sourceTree = ""; + }; + 314B625E23DCCE8900139EB3 /* _iOS */ = { + isa = PBXGroup; + children = ( + 278A4ECE2B92C4EA003898EB /* _Transitions */, + 314B626123DCCE8900139EB3 /* _Action */, + 314B626923DCCE8900139EB3 /* _App */, + 314B626523DCCE8900139EB3 /* _Extensions */, + 31331DC3255345E40050D74C /* _LocalNotification */, + ); + path = _iOS; + sourceTree = ""; + }; + 314B626123DCCE8900139EB3 /* _Action */ = { + isa = PBXGroup; + children = ( + 314B626223DCCE8900139EB3 /* MailAction.swift */, + 314B626323DCCE8900139EB3 /* RateAction.swift */, + 314B626423DCCE8900139EB3 /* TelAction.swift */, + ); + path = _Action; + sourceTree = ""; + }; + 314B626523DCCE8900139EB3 /* _Extensions */ = { + isa = PBXGroup; + children = ( + 314B626623DCCE8900139EB3 /* MappedUIKitExtensionRouter.swift */, + ); + path = _Extensions; + sourceTree = ""; + }; + 314B626923DCCE8900139EB3 /* _App */ = { + isa = PBXGroup; + children = ( + 314B626C23DCCE8900139EB3 /* MappedUIKitAppRouter.swift */, + 314B626B23DCCE8900139EB3 /* MappedUIKitRouter.swift */, + 314B626A23DCCE8900139EB3 /* RoutingAppDelegate.swift */, + ); + path = _App; + sourceTree = ""; + }; + 314C359D24C7C11C00695F7E /* Resources */ = { + isa = PBXGroup; + children = ( + 31308B5624FAEDB5003B5B9A /* _UIKit */, + 314C35B324C7C1B600695F7E /* _iOS */, + ); + path = Resources; + sourceTree = ""; + }; + 314C35AA24C7C12400695F7E /* _Routing */ = { + isa = PBXGroup; + children = ( + 314C35AB24C7C12A00695F7E /* _Storyboards */, + 314C35AC24C7C13100695F7E /* _Xib */, + ); + path = _Routing; + sourceTree = ""; + }; + 314C35AB24C7C12A00695F7E /* _Storyboards */ = { + isa = PBXGroup; + children = ( + 314C35AD24C7C1A000695F7E /* Drawer.storyboard */, + 314C35AE24C7C1A000695F7E /* Embed.storyboard */, + ); + path = _Storyboards; + sourceTree = ""; + }; + 314C35AC24C7C13100695F7E /* _Xib */ = { + isa = PBXGroup; + children = ( + 311BAFDE255083C1004331D8 /* Mail.xib */, + 314C35B124C7C1AA00695F7E /* EnableDebug.xib */, + 311BB01E2550845A004331D8 /* Share.xib */, + ); + path = _Xib; + sourceTree = ""; + }; + 314C35B324C7C1B600695F7E /* _iOS */ = { + isa = PBXGroup; + children = ( + 314C35AA24C7C12400695F7E /* _Routing */, + ); + path = _iOS; + sourceTree = ""; + }; + 314C35B424C7C27300695F7E /* _Actions */ = { + isa = PBXGroup; + children = ( + 314C35B524C7C29600695F7E /* DebugEnableAction.swift */, + ); + path = _Actions; + sourceTree = ""; + }; + 31CEE9AB2170FA2700DC61DA = { + isa = PBXGroup; + children = ( + 02684F0328BD41660007CEFF /* Dependencies */, + 31CEE9CF2170FA3600DC61DA /* Frameworks */, + 31CEE9B72170FA2700DC61DA /* PlatformRouting */, + 313EBBE621BB7B7D00BEF926 /* PlatformRoutingAppleTV */, + 313EBBF121BB7B7E00BEF926 /* PlatformRoutingAppleTVTests */, + 313EB76021BB4CA300BEF926 /* PlatformRoutingAppleWatch */, + 31CEE9C22170FA2700DC61DA /* PlatformRoutingTests */, + 31CEE9B62170FA2700DC61DA /* Products */, + 4EACDCC2C6801774B6FA000F /* Pods */, + ); + sourceTree = ""; + }; + 31CEE9B62170FA2700DC61DA /* Products */ = { + isa = PBXGroup; + children = ( + 31CEE9B52170FA2700DC61DA /* PlatformRouting.framework */, + 31CEE9BE2170FA2700DC61DA /* PlatformRoutingTests.xctest */, + 313EB75F21BB4CA300BEF926 /* PlatformRouting.framework */, + 313EBBE521BB7B7D00BEF926 /* PlatformRouting.framework */, + 313EBBED21BB7B7D00BEF926 /* PlatformRoutingAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31CEE9B72170FA2700DC61DA /* PlatformRouting */ = { + isa = PBXGroup; + children = ( + 31CEE9B82170FA2700DC61DA /* PlatformRouting.h */, + 31CEE9B92170FA2700DC61DA /* Info.plist */, + 314C35B424C7C27300695F7E /* _Actions */, + 314B625923DCCE8900139EB3 /* _Floating */, + 314B625E23DCCE8900139EB3 /* _iOS */, + 314B625423DCCE8900139EB3 /* _Router */, + 314B624E23DCCE8900139EB3 /* _ViewController */, + 314C359D24C7C11C00695F7E /* Resources */, + ); + path = PlatformRouting; + sourceTree = ""; + }; + 31CEE9C22170FA2700DC61DA /* PlatformRoutingTests */ = { + isa = PBXGroup; + children = ( + 31CEE9C32170FA2700DC61DA /* PlatformRoutingTests.swift */, + 31CEE9C52170FA2700DC61DA /* Info.plist */, + ); + path = PlatformRoutingTests; + sourceTree = ""; + }; + 31CEE9CF2170FA3600DC61DA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 31CEE9D02170FA3600DC61DA /* RoutingKit.framework */, + 21D0A34C88FFB5FB1165548D /* Pods_iOS_PlatformRouting.framework */, + 1A4A40F832E3D20D7A978DE2 /* Pods_iOS_PlatformRoutingTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 31F762BE24BA3F48001EA293 /* Products */ = { + isa = PBXGroup; + children = ( + 31F762C624BA3F48001EA293 /* ParticlesKit.framework */, + 31F762C824BA3F48001EA293 /* ParticlesKit.framework */, + 31F762CA24BA3F48001EA293 /* ParticlesKit.framework */, + 31F762CC24BA3F48001EA293 /* ParticlesKitTests.xctest */, + 31F762CE24BA3F48001EA293 /* ParticlesKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 4EACDCC2C6801774B6FA000F /* Pods */ = { + isa = PBXGroup; + children = ( + 306E17287D7FE48D8EEA3648 /* Pods-iOS-PlatformRouting.debug.xcconfig */, + 86201953C1DEEFBB5C8207DA /* Pods-iOS-PlatformRouting.release.xcconfig */, + C92D500101D7BB756DE1D820 /* Pods-iOS-PlatformRoutingTests.debug.xcconfig */, + F0F3C6868E6D2A68FE400D98 /* Pods-iOS-PlatformRoutingTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 313EB75A21BB4CA300BEF926 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB76321BB4CA300BEF926 /* PlatformRouting.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBBE021BB7B7D00BEF926 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBBF521BB7B7E00BEF926 /* PlatformRouting.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CEE9B02170FA2700DC61DA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 31CEE9C62170FA2700DC61DA /* PlatformRouting.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 313EB75E21BB4CA300BEF926 /* PlatformRoutingAppleWatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EB76421BB4CA300BEF926 /* Build configuration list for PBXNativeTarget "PlatformRoutingAppleWatch" */; + buildPhases = ( + 313EB75A21BB4CA300BEF926 /* Headers */, + 313EB75B21BB4CA300BEF926 /* Sources */, + 313EB75C21BB4CA300BEF926 /* Frameworks */, + 313EB75D21BB4CA300BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EB76A21BB4CFE00BEF926 /* PBXTargetDependency */, + 313EB76C21BB4CFE00BEF926 /* PBXTargetDependency */, + ); + name = PlatformRoutingAppleWatch; + productName = PlatformRoutingAppleWatch; + productReference = 313EB75F21BB4CA300BEF926 /* PlatformRouting.framework */; + productType = "com.apple.product-type.framework"; + }; + 313EBBE421BB7B7D00BEF926 /* PlatformRoutingAppleTV */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EBC1421BB7B7E00BEF926 /* Build configuration list for PBXNativeTarget "PlatformRoutingAppleTV" */; + buildPhases = ( + 313EBBE021BB7B7D00BEF926 /* Headers */, + 313EBBE121BB7B7D00BEF926 /* Sources */, + 313EBBE221BB7B7D00BEF926 /* Frameworks */, + 313EBBE321BB7B7D00BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 310CDBAB21C6F9F6009665CF /* PBXTargetDependency */, + 313EBC3321BB7BA900BEF926 /* PBXTargetDependency */, + 313EBC3521BB7BA900BEF926 /* PBXTargetDependency */, + ); + name = PlatformRoutingAppleTV; + productName = PlatformRoutingAppleTV; + productReference = 313EBBE521BB7B7D00BEF926 /* PlatformRouting.framework */; + productType = "com.apple.product-type.framework"; + }; + 313EBBEC21BB7B7D00BEF926 /* PlatformRoutingAppleTVTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EBC1521BB7B7E00BEF926 /* Build configuration list for PBXNativeTarget "PlatformRoutingAppleTVTests" */; + buildPhases = ( + 313EBBE921BB7B7D00BEF926 /* Sources */, + 313EBBEA21BB7B7D00BEF926 /* Frameworks */, + 313EBBEB21BB7B7D00BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EBBF021BB7B7E00BEF926 /* PBXTargetDependency */, + ); + name = PlatformRoutingAppleTVTests; + productName = PlatformRoutingAppleTVTests; + productReference = 313EBBED21BB7B7D00BEF926 /* PlatformRoutingAppleTVTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 31CEE9B42170FA2700DC61DA /* PlatformRouting */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31CEE9C92170FA2700DC61DA /* Build configuration list for PBXNativeTarget "PlatformRouting" */; + buildPhases = ( + 0AAF26DCE230F7273CCC8A8A /* [CP] Check Pods Manifest.lock */, + 31CEE9B02170FA2700DC61DA /* Headers */, + 31CEE9B12170FA2700DC61DA /* Sources */, + 31CEE9B22170FA2700DC61DA /* Frameworks */, + 31CEE9B32170FA2700DC61DA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 31F762D024BA3F51001EA293 /* PBXTargetDependency */, + 314B6EDD23DD0AB200139EB3 /* PBXTargetDependency */, + 311D6F762176EF8600655040 /* PBXTargetDependency */, + 311D6F782176EF8600655040 /* PBXTargetDependency */, + 311D6F742176EF8600655040 /* PBXTargetDependency */, + ); + name = PlatformRouting; + productName = PlatformRouting; + productReference = 31CEE9B52170FA2700DC61DA /* PlatformRouting.framework */; + productType = "com.apple.product-type.framework"; + }; + 31CEE9BD2170FA2700DC61DA /* PlatformRoutingTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31CEE9CC2170FA2700DC61DA /* Build configuration list for PBXNativeTarget "PlatformRoutingTests" */; + buildPhases = ( + C850F16E67CF33B541CC64E5 /* [CP] Check Pods Manifest.lock */, + 31CEE9BA2170FA2700DC61DA /* Sources */, + 31CEE9BB2170FA2700DC61DA /* Frameworks */, + 31CEE9BC2170FA2700DC61DA /* Resources */, + F1A644C23DA0EB7D8A6B84BD /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 31CEE9C12170FA2700DC61DA /* PBXTargetDependency */, + ); + name = PlatformRoutingTests; + productName = PlatformRoutingTests; + productReference = 31CEE9BE2170FA2700DC61DA /* PlatformRoutingTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 31CEE9AC2170FA2700DC61DA /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "dYdX Trading Inc"; + TargetAttributes = { + 313EB75E21BB4CA300BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 313EBBE421BB7B7D00BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 313EBBEC21BB7B7D00BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 31CEE9B42170FA2700DC61DA = { + CreatedOnToolsVersion = 10.0; + LastSwiftMigration = 1020; + }; + 31CEE9BD2170FA2700DC61DA = { + CreatedOnToolsVersion = 10.0; + }; + }; + }; + buildConfigurationList = 31CEE9AF2170FA2700DC61DA /* Build configuration list for PBXProject "PlatformRouting" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 31CEE9AB2170FA2700DC61DA; + productRefGroup = 31CEE9B62170FA2700DC61DA /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 31F762BE24BA3F48001EA293 /* Products */; + ProjectRef = 31F762BD24BA3F48001EA293 /* ParticlesKit.xcodeproj */; + }, + { + ProductGroup = 311D6F592176EF6E00655040 /* Products */; + ProjectRef = 311D6F582176EF6E00655040 /* RoutingKit.xcodeproj */; + }, + { + ProductGroup = 311D6F622176EF7500655040 /* Products */; + ProjectRef = 311D6F612176EF7500655040 /* UIToolkits.xcodeproj */; + }, + { + ProductGroup = 311D6F6B2176EF7B00655040 /* Products */; + ProjectRef = 311D6F6A2176EF7B00655040 /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 31CEE9B42170FA2700DC61DA /* PlatformRouting */, + 313EB75E21BB4CA300BEF926 /* PlatformRoutingAppleWatch */, + 313EBBE421BB7B7D00BEF926 /* PlatformRoutingAppleTV */, + 31CEE9BD2170FA2700DC61DA /* PlatformRoutingTests */, + 313EBBEC21BB7B7D00BEF926 /* PlatformRoutingAppleTVTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 310C3E0021DAAE210081E56D /* UIAppToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIAppToolkits.framework; + remoteRef = 310C3DFF21DAAE210081E56D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 310C3E0221DAAE210081E56D /* UIAppToolkitsTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UIAppToolkitsTests.xctest; + remoteRef = 310C3E0121DAAE210081E56D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 311D6F5E2176EF6E00655040 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 311D6F5D2176EF6E00655040 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 311D6F602176EF6E00655040 /* RoutingKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitTests.xctest; + remoteRef = 311D6F5F2176EF6E00655040 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 311D6F672176EF7500655040 /* UIToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIToolkits.framework; + remoteRef = 311D6F662176EF7500655040 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 311D6F692176EF7500655040 /* UIToolkitsTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UIToolkitsTests.xctest; + remoteRef = 311D6F682176EF7500655040 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 311D6F702176EF7B00655040 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 311D6F6F2176EF7B00655040 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 311D6F722176EF7B00655040 /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 311D6F712176EF7B00655040 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB73721BB4BE800BEF926 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 313EB73621BB4BE800BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB73921BB4BE800BEF926 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 313EB73821BB4BE800BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB73D21BB4BE800BEF926 /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 313EB73C21BB4BE800BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB74721BB4BE800BEF926 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 313EB74621BB4BE800BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB74921BB4BE800BEF926 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 313EB74821BB4BE800BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB74D21BB4BE800BEF926 /* RoutingKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitAppleTVTests.xctest; + remoteRef = 313EB74C21BB4BE800BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBC0B21BB7B7E00BEF926 /* UIToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIToolkits.framework; + remoteRef = 313EBC0A21BB7B7E00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBC0D21BB7B7E00BEF926 /* UIToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIToolkits.framework; + remoteRef = 313EBC0C21BB7B7E00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EBC1121BB7B7E00BEF926 /* UIToolkitsAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UIToolkitsAppleTVTests.xctest; + remoteRef = 313EBC1021BB7B7E00BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F762C624BA3F48001EA293 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31F762C524BA3F48001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F762C824BA3F48001EA293 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31F762C724BA3F48001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F762CA24BA3F48001EA293 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31F762C924BA3F48001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F762CC24BA3F48001EA293 /* ParticlesKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitTests.xctest; + remoteRef = 31F762CB24BA3F48001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F762CE24BA3F48001EA293 /* ParticlesKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitAppleTVTests.xctest; + remoteRef = 31F762CD24BA3F48001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 313EB75D21BB4CA300BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBBE321BB7B7D00BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31308B6524FAEDCC003B5B9A /* routing_shared.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBBEB21BB7B7D00BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CEE9B32170FA2700DC61DA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314C35B024C7C1A000695F7E /* Embed.storyboard in Resources */, + 311BAFEB255083C1004331D8 /* Mail.xib in Resources */, + 31308B6424FAEDCC003B5B9A /* routing_shared.json in Resources */, + 314C35B224C7C1AA00695F7E /* EnableDebug.xib in Resources */, + 314C35AF24C7C1A000695F7E /* Drawer.storyboard in Resources */, + 311BB01F2550845A004331D8 /* Share.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CEE9BC2170FA2700DC61DA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0AAF26DCE230F7273CCC8A8A /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-PlatformRouting-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C850F16E67CF33B541CC64E5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-PlatformRoutingTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + F1A644C23DA0EB7D8A6B84BD /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-PlatformRoutingTests/Pods-iOS-PlatformRoutingTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-PlatformRoutingTests/Pods-iOS-PlatformRoutingTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-PlatformRoutingTests/Pods-iOS-PlatformRoutingTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 313EB75B21BB4CA300BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB76821BB4CCB00BEF926 /* RoutingInterfaceController.swift in Sources */, + 313EBE0021BC3D9000BEF926 /* RoutingExtensionDelegate.swift in Sources */, + 314C35B724C7C29600695F7E /* DebugEnableAction.swift in Sources */, + 313EB76721BB4CCB00BEF926 /* MappedAppleWatchRouter.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBBE121BB7B7D00BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B6E9123DD06B500139EB3 /* MappedUIKitAppRouter.swift in Sources */, + 314B6E9323DD070600139EB3 /* RoutingMap+iOS.swift in Sources */, + 314B6E9423DD079900139EB3 /* RoutingTabBarController.swift in Sources */, + 314B6E8F23DD05EE00139EB3 /* UITabBarController+Routing.swift in Sources */, + 314B6E8523DD05B500139EB3 /* RoutingViewController.swift in Sources */, + 314B6E9023DD065500139EB3 /* RoutingAppDelegate.swift in Sources */, + 314C35B824C7C29600695F7E /* DebugEnableAction.swift in Sources */, + 314B6E9223DD06CB00139EB3 /* MappedUIKitRouter.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBBE921BB7B7D00BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBBF321BB7B7E00BEF926 /* PlatformRoutingAppleTVTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CEE9B12170FA2700DC61DA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B627823DCCE8900139EB3 /* RoutingEmbeddingController.swift in Sources */, + 314B627D23DCCE8900139EB3 /* TelAction.swift in Sources */, + 314B628223DCCE8900139EB3 /* MappedUIKitAppRouter.swift in Sources */, + 314B626D23DCCE8900139EB3 /* RoutingDrawerController.swift in Sources */, + 314C35B624C7C29600695F7E /* DebugEnableAction.swift in Sources */, + 314B628123DCCE8900139EB3 /* MappedUIKitRouter.swift in Sources */, + 314B627523DCCE8900139EB3 /* RoutingMap+iOS.swift in Sources */, + 314B627723DCCE8900139EB3 /* EmbeddingFloatingManager.swift in Sources */, + 314B627C23DCCE8900139EB3 /* RateAction.swift in Sources */, + 278A4EDC2B92C62A003898EB /* FadeTransition.swift in Sources */, + 31331DC6255345E40050D74C /* SimpleLocalNotification.swift in Sources */, + 314B626E23DCCE8900139EB3 /* RoutingViewController.swift in Sources */, + 314D9C172485AA2D00582A1A /* RoutingContainingController.swift in Sources */, + 314B627623DCCE8900139EB3 /* EmbeddingProtocols.swift in Sources */, + 314B627423DCCE8900139EB3 /* UISplitViewController+Routing.swift in Sources */, + 278A4EDE2B92C639003898EB /* TransitionDelegate.swift in Sources */, + 314B627223DCCE8900139EB3 /* UITabBarController+Routing.swift in Sources */, + 314B626F23DCCE8900139EB3 /* RoutingSplitViewController.swift in Sources */, + 314B627E23DCCE8900139EB3 /* MappedUIKitExtensionRouter.swift in Sources */, + 314B627123DCCE8900139EB3 /* RoutingTabBarController.swift in Sources */, + 314B628023DCCE8900139EB3 /* RoutingAppDelegate.swift in Sources */, + 314B627923DCCE8900139EB3 /* UINavigationController+Embedded.swift in Sources */, + 314B627323DCCE8900139EB3 /* UINavigationViewController+Routing.swift in Sources */, + 314B627B23DCCE8900139EB3 /* MailAction.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31CEE9BA2170FA2700DC61DA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31CEE9C42170FA2700DC61DA /* PlatformRoutingTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 310CDBAB21C6F9F6009665CF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UIToolkitsAppleTV; + targetProxy = 310CDBAA21C6F9F6009665CF /* PBXContainerItemProxy */; + }; + 311D6F742176EF8600655040 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKit; + targetProxy = 311D6F732176EF8600655040 /* PBXContainerItemProxy */; + }; + 311D6F762176EF8600655040 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 311D6F752176EF8600655040 /* PBXContainerItemProxy */; + }; + 311D6F782176EF8600655040 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UIToolkits; + targetProxy = 311D6F772176EF8600655040 /* PBXContainerItemProxy */; + }; + 313EB76A21BB4CFE00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleWatch; + targetProxy = 313EB76921BB4CFE00BEF926 /* PBXContainerItemProxy */; + }; + 313EB76C21BB4CFE00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKitAppleWatch; + targetProxy = 313EB76B21BB4CFE00BEF926 /* PBXContainerItemProxy */; + }; + 313EBBF021BB7B7E00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 313EBBE421BB7B7D00BEF926 /* PlatformRoutingAppleTV */; + targetProxy = 313EBBEF21BB7B7E00BEF926 /* PBXContainerItemProxy */; + }; + 313EBC3321BB7BA900BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleTV; + targetProxy = 313EBC3221BB7BA900BEF926 /* PBXContainerItemProxy */; + }; + 313EBC3521BB7BA900BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKitAppleTV; + targetProxy = 313EBC3421BB7BA900BEF926 /* PBXContainerItemProxy */; + }; + 314B6EDD23DD0AB200139EB3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UIAppToolkits; + targetProxy = 314B6EDC23DD0AB200139EB3 /* PBXContainerItemProxy */; + }; + 31CEE9C12170FA2700DC61DA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31CEE9B42170FA2700DC61DA /* PlatformRouting */; + targetProxy = 31CEE9C02170FA2700DC61DA /* PBXContainerItemProxy */; + }; + 31F762D024BA3F51001EA293 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKit; + targetProxy = 31F762CF24BA3F51001EA293 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 313EB76521BB4CA300BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PlatformRoutingAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformRoutingAppleWatch; + PRODUCT_NAME = PlatformRouting; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _watchOS"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Debug; + }; + 313EB76621BB4CA300BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PlatformRoutingAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformRoutingAppleWatch; + PRODUCT_NAME = PlatformRouting; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _watchOS"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Release; + }; + 313EBBF621BB7B7E00BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PlatformRoutingAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformRoutingAppleTV; + PRODUCT_NAME = PlatformRouting; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _tvOS"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313EBBF721BB7B7E00BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PlatformRoutingAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformRoutingAppleTV; + PRODUCT_NAME = PlatformRouting; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _tvOS"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 313EBBF821BB7B7E00BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = PlatformRoutingAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformRoutingAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313EBBF921BB7B7E00BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = PlatformRoutingAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformRoutingAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 31CEE9C72170FA2700DC61DA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 31CEE9C82170FA2700DC61DA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 31CEE9CA2170FA2700DC61DA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 306E17287D7FE48D8EEA3648 /* Pods-iOS-PlatformRouting.debug.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PlatformRouting/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.PlatformRouting; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _iOS"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31CEE9CB2170FA2700DC61DA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 86201953C1DEEFBB5C8207DA /* Pods-iOS-PlatformRouting.release.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = PlatformRouting/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.PlatformRouting; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _iOS"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 31CEE9CD2170FA2700DC61DA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C92D500101D7BB756DE1D820 /* Pods-iOS-PlatformRoutingTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = PlatformRoutingTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformRoutingTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31CEE9CE2170FA2700DC61DA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F0F3C6868E6D2A68FE400D98 /* Pods-iOS-PlatformRoutingTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = PlatformRoutingTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.PlatformRoutingTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 313EB76421BB4CA300BEF926 /* Build configuration list for PBXNativeTarget "PlatformRoutingAppleWatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EB76521BB4CA300BEF926 /* Debug */, + 313EB76621BB4CA300BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313EBC1421BB7B7E00BEF926 /* Build configuration list for PBXNativeTarget "PlatformRoutingAppleTV" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EBBF621BB7B7E00BEF926 /* Debug */, + 313EBBF721BB7B7E00BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313EBC1521BB7B7E00BEF926 /* Build configuration list for PBXNativeTarget "PlatformRoutingAppleTVTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EBBF821BB7B7E00BEF926 /* Debug */, + 313EBBF921BB7B7E00BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31CEE9AF2170FA2700DC61DA /* Build configuration list for PBXProject "PlatformRouting" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31CEE9C72170FA2700DC61DA /* Debug */, + 31CEE9C82170FA2700DC61DA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31CEE9C92170FA2700DC61DA /* Build configuration list for PBXNativeTarget "PlatformRouting" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31CEE9CA2170FA2700DC61DA /* Debug */, + 31CEE9CB2170FA2700DC61DA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31CEE9CC2170FA2700DC61DA /* Build configuration list for PBXNativeTarget "PlatformRoutingTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31CEE9CD2170FA2700DC61DA /* Debug */, + 31CEE9CE2170FA2700DC61DA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 31CEE9AC2170FA2700DC61DA /* Project object */; +} diff --git a/PlatformRouting/PlatformRouting/Info.plist b/PlatformRouting/PlatformRouting/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/PlatformRouting/PlatformRouting/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/PlatformRouting/PlatformRouting/PlatformRouting.h b/PlatformRouting/PlatformRouting/PlatformRouting.h new file mode 100644 index 000000000..41b48f472 --- /dev/null +++ b/PlatformRouting/PlatformRouting/PlatformRouting.h @@ -0,0 +1,17 @@ +// +// PlatformRouting.h +// PlatformRouting +// +// Created by Qiang Huang on 10/12/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for PlatformRouting. +FOUNDATION_EXPORT double PlatformRoutingVersionNumber; + +//! Project version string for PlatformRouting. +FOUNDATION_EXPORT const unsigned char PlatformRoutingVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/PlatformRouting/PlatformRouting/Resources/_UIKit/routing_shared.json b/PlatformRouting/PlatformRouting/Resources/_UIKit/routing_shared.json new file mode 100644 index 000000000..1123953fe --- /dev/null +++ b/PlatformRouting/PlatformRouting/Resources/_UIKit/routing_shared.json @@ -0,0 +1,110 @@ +{ + "/action/debug/enable":{ + "destination":"EnableDebug.xib" + }, + "/action/logout":{ + "destination":"Logout.xib" + }, + "/action/photos/add":{ + "destination":"PhotosAdd.xib" + }, + "/action/photos/add/camera":{ + "destination":"PhotosAddFromCamera.xib", + "dependencies":[ + "/state/loggedin", + "/authorization/camera" + ] + }, + "/action/photos/add/album":{ + "destination":"PhotosAddFromAlbum.xib", + "dependencies":[ + "/state/loggedin", + "/authorization/album" + ] + }, + "/action/photos/remove":{ + "destination":"PhotosRemove.xib" + }, + "/action/protected":{ + "destination":"Protected.xib" + }, + "/action/refresh":{ + "destination":"Refresh.xib" + }, + "/action/share":{ + "destination":"dydxPresenters.dydxShareActionBuilder" + }, + "/action/wallet":{ + "destination":"Wallet.xib" + }, + "/authorization/calendar":{ + "destination":"CalendarPermissionAction.xib" + }, + "/authorization/camera":{ + "destination":"CameraPermissionAction.xib" + }, + "/authorization/location":{ + "destination":"LocationPermissionAction.xib" + }, + "/authorization/notification":{ + "destination":"NotificationPermissionAction.xib" + }, + "/authorization/photoalbum":{ + "destination":"PhotoAlbumsPermissionAction.xib" + }, + "/state/loggedin":{ + "destination":"Loggedin.xib" + }, + "/confirm":{ + "destination":"ConfirmAction.xib" + }, + "/settings/debug":{ + "destination":"Debug.storyboard", + "presentation":"prompt", + "dependencies":[ + "/action/protected" + ] + }, + "/features":{ + "destination":"Features.storyboard", + "presentation":"prompt", + "dependencies":[ + "/action/protected" + ] + }, + "/login":{ + "destination":"Login.xib", + "presentation":"half" + }, + "/primer/camera":{ + "destination":"PrimerCamera.storyboard", + "presentation":"half" + }, + "/primer/calendar":{ + "destination":"PrimerCalendar.storyboard", + "presentation":"half" + }, + "/primer/location":{ + "destination":"PrimerLocation.storyboard", + "presentation":"half" + }, + "/primer/notification":{ + "destination":"PrimerNotification.storyboard", + "presentation":"prompt" + }, + "/primer/photoalbum":{ + "destination":"PrimerPhotoAlbum.storyboard", + "presentation":"half" + }, + "/settings/theme":{ + "destination":"Theme.storyboard", + "presentation":"prompt", + "dependencies":[ + "/settings" + ] + }, + "/upgrade":{ + "destination":"Upgrade.storyboard", + "presentation":"root" + } +} diff --git a/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Storyboards/Drawer.storyboard b/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Storyboards/Drawer.storyboard new file mode 100644 index 000000000..9d5982f47 --- /dev/null +++ b/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Storyboards/Drawer.storyboard @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Storyboards/Embed.storyboard b/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Storyboards/Embed.storyboard new file mode 100644 index 000000000..7254bac6c --- /dev/null +++ b/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Storyboards/Embed.storyboard @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Xib/EnableDebug.xib b/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Xib/EnableDebug.xib new file mode 100644 index 000000000..80b45004a --- /dev/null +++ b/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Xib/EnableDebug.xib @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Xib/Mail.xib b/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Xib/Mail.xib new file mode 100644 index 000000000..8987e1310 --- /dev/null +++ b/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Xib/Mail.xib @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Xib/Share.xib b/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Xib/Share.xib new file mode 100644 index 000000000..f3227367e --- /dev/null +++ b/PlatformRouting/PlatformRouting/Resources/_iOS/_Routing/_Xib/Share.xib @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/PlatformRouting/PlatformRouting/_Actions/DebugEnableAction.swift b/PlatformRouting/PlatformRouting/_Actions/DebugEnableAction.swift new file mode 100644 index 000000000..dfcca24e7 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_Actions/DebugEnableAction.swift @@ -0,0 +1,30 @@ +// +// DebugEnableAction.swift +// PlatformRouting +// +// Created by Qiang Huang on 7/21/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import RoutingKit +import Utilities + +public class DebugEnableActionBuilder: NSObject, ObjectBuilderProtocol { + public func build() -> T? { + let action = DebugEnableAction() + return action as? T + } +} + +open class DebugEnableAction: NSObject, NavigableProtocol { + open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + switch request?.path { + case "/action/debug/enable": + UserDefaults.standard.set(true, forKey: DebugEnabled.key) + completion?(nil, true) + + default: + completion?(nil, false) + } + } +} diff --git a/PlatformRouting/PlatformRouting/_Floating/EmbeddingFloatingManager.swift b/PlatformRouting/PlatformRouting/_Floating/EmbeddingFloatingManager.swift new file mode 100644 index 000000000..8bf24775e --- /dev/null +++ b/PlatformRouting/PlatformRouting/_Floating/EmbeddingFloatingManager.swift @@ -0,0 +1,173 @@ +// +// EmbeddingFloatingManager.swift +// PlatformRouting +// +// Created by Qiang Huang on 10/22/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import FloatingPanel +import RoutingKit +import UIAppToolkits +import UIToolkits +import Utilities + +open class EmbeddingFloatingManager: FloatingManager, EmbeddingProtocol { + public weak var embeddingController: RoutingEmbeddingController? { + return parent as? RoutingEmbeddingController + } + + public var embeddingContainer: UIView? { + return embeddingController?.embeddingContainer + } + + public var floatingContainer: UIView? { + return embeddingController?.floatingContainer + } + + public var embedded: UIViewController? { + didSet { + if embedded !== oldValue { + parent?.remove(oldValue) + installEmbedded() + } + } + } + + open var floating: FloatingPanelController? { + didSet { + if floating !== oldValue { + if let oldValue = oldValue { + oldValue.removePanelFromParent(animated: true) + } + if let floating = floating, let parent = parent { + floating.addPanel(toParent: parent) + } else { + (embedded as? EmbeddedDelegate)?.floatingEdge = 0 + } + } + } + } + + public var floated: UIViewController? { + didSet { + if floated !== oldValue { + float(floated) + } + } + } + + private var debouncer: Debouncer = Debouncer() + + open func embed(_ viewController: UIViewController?, animated: Bool) { + if let nav = embedded as? UINavigationController { + if let viewController = viewController { + nav.pushViewController(viewController, animated: animated) + } + } else { + if animated { + UIView.animate(embeddingContainer, type: .fade, direction: .none, duration: UIView.defaultAnimationDuration, animations: { [weak self] in + self?.embedded = viewController + }, completion: nil) + } else { + embedded = viewController + } + } + } + + open func float(_ viewController: UIViewController?, animated: Bool) { + floated = viewController + } + + open func float(_ viewController: UIViewController?) { + if let viewController = viewController { + let floater = FloatingPanelController() + + // Initialize FloatingPanelController and add the view + floater.surfaceView.layer.cornerRadius = 6.0 + floater.surfaceView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner] + if #available(iOS 13.0, *) { + floater.surfaceView.backgroundColor = UIColor.systemBackground + } else { + // Fallback on earlier versions + } + floater.surfaceView.layer.shadowOpacity = 0 + if #available(iOS 13.0, *) { + floater.surfaceView.backgroundColor = UIColor.systemBackground + } else { + // Fallback on earlier versions + } + floater.isRemovalInteractionEnabled = false + floater.modalPresentationStyle = .overCurrentContext + + // Set a content view controller + floater.set(contentViewController: viewController) + floater.delegate = self + + floating = floater + + if let floated = viewController as? FloatedDelegate, let scrollView = floated.floatTracking { + floater.track(scrollView: scrollView) + } + } else { + floating = nil + } + } + + override open func floatingPanelDidRemove(_ vc: FloatingPanelController) { + if vc === floating { + floating = nil + } else { + super.floatingPanelDidRemove(vc) + } + } + + override public func floatingPanelDidChangeState(_ vc: FloatingPanelController) { + if vc === floating { + (floated as? FloatedDelegate)?.position = vc.state + floatingChanged(vc) + } + } + + override public func floatingPanelDidEndAttracting(_ vc: FloatingPanelController) { + if vc === floating { + floatingChanged(vc) + } + } + + override public func floatingPanelShouldBeginDragging(_ fpc: FloatingPanelController) -> Bool { + if fpc === floating { + let panGesture = fpc.panGestureRecognizer + let velocity = panGesture.velocity(in: panGesture.view) + + return (floated as? FloatedDelegate)?.shouldPan(currentState: fpc.state, velocity: velocity) ?? true + } + + return super.floatingPanelShouldBeginDragging(fpc) + } + + private func floatingChanged(_ vc: FloatingPanelController) { + if vc === floating { + if let handler = debouncer.debounce() { + handler.run({ [weak self] in + if let embedded = self?.embedded { + (embedded as? EmbeddedDelegate)?.floatingEdge = vc.surfaceView.frame.origin.y + } + (self?.floated as? FloatedDelegate)?.floatingChanged() + }, delay: 0.025) + } + } + } + + override open func dismiss(_ viewController: UIViewController?, animated: Bool) { + if viewController == floating { + floating = nil + } else { + super.dismiss(viewController, animated: animated) + } + } + + open func installEmbedded() { + parent?.embed(embedded, in: embeddingContainer) + } +} diff --git a/PlatformRouting/PlatformRouting/_Floating/EmbeddingProtocols.swift b/PlatformRouting/PlatformRouting/_Floating/EmbeddingProtocols.swift new file mode 100644 index 000000000..f65221303 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_Floating/EmbeddingProtocols.swift @@ -0,0 +1,54 @@ +// +// EmbeddingProtocols.swift +// PlatformRouting +// +// Created by Qiang Huang on 1/20/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import FloatingPanel + +public protocol EmbeddedDelegate: AnyObject { + var floatingEdge: CGFloat? { get set } +} + +public protocol FloatedDelegate: AnyObject { + var position: FloatingPanelState? { get set } + var floatTracking: UIScrollView? { get } + func floatingChanged() + func shouldPan(currentState: FloatingPanelState, velocity: CGPoint) -> Bool +} + +public protocol FloatingInsetProvider: AnyObject { + var anchors: [FloatingPanel.FloatingPanelState: FloatingPanel.FloatingPanelLayoutAnchoring] { get } + var initialPosition: FloatingPanelState { get } +} + +public extension FloatingInsetProvider { + var initialPosition: FloatingPanelState { + .tip + } +} + +extension UINavigationController: FloatedDelegate { + public var position: FloatingPanelState? { + get { + return (topViewController as? FloatedDelegate)?.position + } + set { + (topViewController as? FloatedDelegate)?.position = newValue + } + } + + public var floatTracking: UIScrollView? { + return (topViewController as? FloatedDelegate)?.floatTracking + } + + public func floatingChanged() { + (topViewController as? FloatedDelegate)?.floatingChanged() + } + + public func shouldPan(currentState: FloatingPanelState, velocity: CGPoint) -> Bool { + true + } +} diff --git a/PlatformRouting/PlatformRouting/_Floating/RoutingContainingController.swift b/PlatformRouting/PlatformRouting/_Floating/RoutingContainingController.swift new file mode 100644 index 000000000..0e607b5ea --- /dev/null +++ b/PlatformRouting/PlatformRouting/_Floating/RoutingContainingController.swift @@ -0,0 +1,129 @@ +// +// RoutingContainingController.swift +// PlatformRouting +// +// Created by Qiang Huang on 6/1/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import RoutingKit +import UIAppToolkits +import UIToolkits +import Utilities + +open class RoutingContainingController: UIViewController, UIViewControllerEmbeddingProtocol { + @IBOutlet public var embedding: UIView? + @IBOutlet public var floating: UIView? + + public var stacked: Bool = false { + didSet { + if stacked != oldValue { + updateStacked() + } + } + } + + @IBOutlet public var stackedContaints: [NSLayoutConstraint]? + + private var navigationDebouncer: Debouncer = Debouncer() + + public var embedded: UIViewController? { + get { + if let embedding = embedding { + return children.first { (viewController) -> Bool in + viewController.view.superview === embedding + } + } + return nil + } + set { + if newValue !== embedded { + remove(embedded) + embed(newValue, in: embedding) + } + } + } + + public var floated: UIViewController? { + get { + if let floating = floating { + return children.first { (viewController) -> Bool in + viewController.view.superview === floating + } + } + return nil + } + set { + if newValue !== floated { + remove(floated) + embed(newValue, in: floating) + } + } + } + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + stacked = traitCollection.horizontalSizeClass == .compact + } + + open func embed(_ viewController: UIViewController?, animated: Bool) -> Bool { + if embedding != nil { + embedded = viewController + return true + } + return false + } + + open func float(_ viewController: UIViewController?, animated: Bool) -> Bool { + if floating != nil { + floated = viewController + return true + } + return false + } + + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + stacked = (traitCollection.horizontalSizeClass == .compact) + } + + open func updateStacked() { + let priority: Float = stacked ? 751 : 749 + if let constraints = stackedContaints { + for constraint in constraints { + constraint.priority = UILayoutPriority(rawValue: priority) + } + } + } +} + +extension RoutingContainingController: NavigableProtocol { + public func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in + self?.navigate(to: request, viewControllerIndex: 0, animated: animated) { [weak self] object, completed in + if completed { + self?.navigationItem.title = (object as? UIViewController)?.navigationItem.title + } + completion?(object, completed) + } + } + } + + public func navigate(to request: RoutingRequest?, viewControllerIndex: Int, animated: Bool, completion: RoutingCompletionBlock?) { + if viewControllerIndex < children.count { + let viewController = children[viewControllerIndex] + if let destination = viewController as? NavigableProtocol { + destination.navigate(to: request, animated: animated) { [weak self] _, completed in + if completed { + completion?(destination, true) + } else { + self?.navigate(to: request, viewControllerIndex: viewControllerIndex + 1, animated: animated, completion: completion) + } + } + } else { + navigate(to: request, viewControllerIndex: viewControllerIndex + 1, animated: animated, completion: completion) + } + } else { + completion?(nil, false) + } + } +} diff --git a/PlatformRouting/PlatformRouting/_Floating/RoutingEmbeddingController.swift b/PlatformRouting/PlatformRouting/_Floating/RoutingEmbeddingController.swift new file mode 100644 index 000000000..24386a4c0 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_Floating/RoutingEmbeddingController.swift @@ -0,0 +1,199 @@ +// +// RoutingEmbeddingViewController.swift +// PlatformRouting +// +// Created by Qiang Huang on 1/20/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import FloatingPanel +import RoutingKit +import UIAppToolkits +import UIToolkits +import Utilities + +open class RoutingEmbeddingController: UIViewController, ParsingProtocol, UIViewControllerEmbeddingProtocol { + public var floated: UIViewController? { + get { + return embeddingFloatingManager?.floated + } + set { + embeddingFloatingManager?.float(newValue, animated: true) + } + } + + public var embedded: UIViewController? { + get { + return embeddingFloatingManager?.embedded + } + set { + embeddingFloatingManager?.embed(newValue, animated: true) + } + } + + public static var parserOverwrite: Parser? + override open var parser: Parser { + return RoutingTabBarController.parserOverwrite ?? super.parser + } + + public var embeddingFloatingManager: EmbeddingProtocol? { + return floatingManager as? EmbeddingProtocol + } + + @IBOutlet public var embeddingContainer: UIView? { + didSet { + if embeddingContainer !== oldValue { + installEmbedded() + } + } + } + + private var lastRequest: RoutingRequest? + + public var embed: String? { + didSet { + if embed != oldValue { + installEmbedded() + } + } + } + + @IBOutlet public var floatingContainer: UIView? { + didSet { + if floatingContainer !== oldValue { + installFloated() + } + } + } + + public var float: String? { + didSet { + if float != oldValue { + installFloated() + } + } + } + + @IBInspectable var path: String? + + private var paths: [String]? { + return parser.asStrings(path) + } + + @IBOutlet var floatingHeight: NSLayoutConstraint? + + @IBInspectable open var routingMap: String? { + didSet { + if routingMap != oldValue { + if let destinations = parser.asDictionary(JsonLoader.load(bundles: Bundle.particles, fileName: routingMap)) { + embed = parser.asString(destinations["embed"]) + float = parser.asString(destinations["float"]) + } + } + } + } + + private func installEmbedded() { + if embeddingContainer != nil, let embed = embed { + installViewController(path: embed, nav: false) { [weak self] viewController in + self?.embeddingFloatingManager?.embedded = viewController + } + } + } + + private func installFloated() { + if floatingContainer != nil, let float = float { + installViewController(path: float, nav: false) { [weak self] viewController in + self?.embeddingFloatingManager?.floated = viewController + } + } + } + + open func installViewController(path: String, nav: Bool, completion: @escaping ((UIViewController?) -> Void)) { + if let router = (Router.shared as? MappedUIKitRouter) { + router.viewController(for: path) { [weak self] viewController in + guard let self = self, let viewController = viewController else { + completion(nil) + return + } + if let request = self.lastRequest?.modify(path: path) { + (viewController as? NavigableProtocol)?.navigate(to: request, animated: false, completion: nil) + } + if nav { + completion(UIViewController.navigation(with: viewController)) + } else { + completion(viewController) + } + } + } else { + completion(nil) + } + } + + override open func awakeFromNib() { + super.awakeFromNib() + floatingManager = floatingManager() + } + + open func floatingManager() -> EmbeddingFloatingManager? { + return EmbeddingFloatingManager(parent: self) + } + + override public func topmost() -> UIViewController? { + return embeddingFloatingManager?.floated?.topmost() ?? embeddingFloatingManager?.embedded?.topmost() ?? super.topmost() + } + + open func embed(_ viewController: UIViewController?, animated: Bool) -> Bool { + if let embeddingFloatingManager = embeddingFloatingManager { + embeddingFloatingManager.embed(viewController, animated: animated) + return true + } + return false + } + + open func float(_ viewController: UIViewController?, animated: Bool) -> Bool { + if let embeddingFloatingManager = embeddingFloatingManager { + embeddingFloatingManager.float(viewController, animated: animated) + return true + } + return false + } +} + +extension RoutingEmbeddingController: NavigableProtocol { + public var history: RoutingRequest? { + return RoutingRequest(path: paths?.first ?? "/") + } + + public func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if let paths = paths, let path = request?.path, paths.contains(path) { + lastRequest = request + completion?(self, true) + } else if path == nil, request?.path == "/" { + lastRequest = request + completion?(self, true) + } else { + navigate(to: request, viewController: embeddingFloatingManager?.embedded, animated: animated) { [weak self] destination, succeeded in + if succeeded { + completion?(destination, succeeded) + } else { + self?.navigate(to: request, viewController: self?.embeddingFloatingManager?.floated, animated: animated, completion: completion) + } + } + } + } + + public func navigate(to request: RoutingRequest?, viewController: UIViewController?, animated: Bool, completion: RoutingCompletionBlock?) { + if let viewController = viewController as? NavigableProtocol { + viewController.navigate(to: request, animated: animated) { _, completed in + if completed { + completion?(viewController, true) + } else { + completion?(nil, false) + } + } + } else { + completion?(nil, false) + } + } +} diff --git a/PlatformRouting/PlatformRouting/_Floating/UINavigationController+Embedded.swift b/PlatformRouting/PlatformRouting/_Floating/UINavigationController+Embedded.swift new file mode 100644 index 000000000..599e46208 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_Floating/UINavigationController+Embedded.swift @@ -0,0 +1,20 @@ +// +// UINavigationController+Embedded.swift +// PlatformRouting +// +// Created by Qiang Huang on 1/20/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +extension UINavigationController: EmbeddedDelegate { + public var floatingEdge: CGFloat? { + get { + return (topViewController as? EmbeddedDelegate)?.floatingEdge + } + set { + (topViewController as? EmbeddedDelegate)?.floatingEdge = newValue + } + } +} diff --git a/PlatformRouting/PlatformRouting/_Router/RoutingMap+iOS.swift b/PlatformRouting/PlatformRouting/_Router/RoutingMap+iOS.swift new file mode 100644 index 000000000..bf1037699 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_Router/RoutingMap+iOS.swift @@ -0,0 +1,48 @@ +// +// RoutingiOSMap.swift +// PlatformRouting +// +// Created by Qiang Huang on 10/12/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import RoutingKit +import Utilities + +extension RoutingMap { + public var isStoryboard: Bool { + return destination.ends(with: "storyboard") + } + + public var isXib: Bool { + return destination.ends(with: "xib") + } + + public var isBuilder: Bool { + return destination.ends(with: "Builder") + } + + public var storyboard: String? { + if isStoryboard { + return destination.stringByDeletingPathExtension + } else { + return nil + } + } + + public var xib: String? { + if isXib { + return destination.stringByDeletingPathExtension + } else { + return nil + } + } + + public var builder: String? { + if isBuilder { + return destination + } else { + return nil + } + } +} diff --git a/PlatformRouting/PlatformRouting/_Router/UINavigationViewController+Routing.swift b/PlatformRouting/PlatformRouting/_Router/UINavigationViewController+Routing.swift new file mode 100644 index 000000000..d8da51708 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_Router/UINavigationViewController+Routing.swift @@ -0,0 +1,39 @@ +// +// UINavigationViewController+Routing.swift +// PlatformRouting +// +// Created by Qiang Huang on 11/24/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import RoutingKit +import UIKit + +extension UINavigationController: NavigableProtocol { + public func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + navigate(to: request, viewControllerIndex: 0, animated: animated, completion: completion) + } + + public func navigate(to request: RoutingRequest?, viewControllerIndex: Int, animated: Bool, completion: RoutingCompletionBlock?) { + if viewControllerIndex < viewControllers.count { + let viewController = viewControllers[viewControllerIndex] + if let destination = viewController as? NavigableProtocol { + destination.navigate(to: request, animated: animated) { [weak self] _, completed in + if completed { + self?.popToViewController(viewController, animated: animated) + completion?(destination, true) + } else { + self?.navigate(to: request, viewControllerIndex: viewControllerIndex + 1, animated: animated, completion: completion) + } + } + } else { + navigate(to: request, viewControllerIndex: viewControllerIndex + 1, animated: animated, completion: completion) + } + } else { + completion?(nil, false) + } + } + + open override func showDetailViewController(_ vc: UIViewController, sender: Any?) { + } +} diff --git a/PlatformRouting/PlatformRouting/_Router/UISplitViewController+Routing.swift b/PlatformRouting/PlatformRouting/_Router/UISplitViewController+Routing.swift new file mode 100644 index 000000000..1b4c21000 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_Router/UISplitViewController+Routing.swift @@ -0,0 +1,35 @@ +// +// UISplitViewController+Routing.swift +// PlatformRouting +// +// Created by Qiang Huang on 12/16/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import RoutingKit +import UIKit + +extension UISplitViewController: NavigableProtocol { + public func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + navigate(to: request, viewControllerIndex: 0, animated: animated, completion: completion) + } + + public func navigate(to request: RoutingRequest?, viewControllerIndex: Int, animated: Bool, completion: RoutingCompletionBlock?) { + if viewControllerIndex < viewControllers.count { + let viewController = viewControllers[viewControllerIndex] + if let destination = viewController as? NavigableProtocol { + destination.navigate(to: request, animated: animated) { [weak self] _, completed in + if completed { + completion?(destination, true) + } else { + self?.navigate(to: request, viewControllerIndex: viewControllerIndex + 1, animated: animated, completion: completion) + } + } + } else { + navigate(to: request, viewControllerIndex: viewControllerIndex + 1, animated: animated, completion: completion) + } + } else { + completion?(nil, false) + } + } +} diff --git a/PlatformRouting/PlatformRouting/_Router/UITabBarController+Routing.swift b/PlatformRouting/PlatformRouting/_Router/UITabBarController+Routing.swift new file mode 100644 index 000000000..1445dfec5 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_Router/UITabBarController+Routing.swift @@ -0,0 +1,53 @@ +// +// UITabBarController+Routing.swift +// PlatformRouting +// +// Created by Qiang Huang on 11/24/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import RoutingKit +import UIKit + +extension UITabBarController: NavigableProtocol { + public var history: RoutingRequest? { + return RoutingRequest(path: "/") + } + + public func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if request?.path == "/" { + completion?(self, true) + } else { + if let presented = presentedViewController as? NavigableProtocol { + presented.navigate(to: request, animated: animated) { [weak self] _, completed in + if completed { + completion?(presented, true) + } else { + self?.navigate(to: request, viewControllerIndex: 0, animated: animated, completion: completion) + } + } + } else { + navigate(to: request, viewControllerIndex: 0, animated: animated, completion: completion) + } + } + } + + public func navigate(to request: RoutingRequest?, viewControllerIndex: Int, animated: Bool, completion: RoutingCompletionBlock?) { + if viewControllerIndex < viewControllers?.count ?? 0 { + if let destination = viewControllers?[viewControllerIndex] as? NavigableProtocol { + destination.navigate(to: request, animated: animated) { [weak self] _, completed in + if completed { + self?.selectedIndex = viewControllerIndex + completion?(destination, true) + } else { + self?.navigate(to: request, viewControllerIndex: viewControllerIndex + 1, animated: animated, completion: completion) + } + } + } else { + navigate(to: request, viewControllerIndex: viewControllerIndex + 1, animated: animated, completion: completion) + } + } else { + completion?(nil, false) + } + } +} diff --git a/PlatformRouting/PlatformRouting/_ViewController/RoutingDrawerController.swift b/PlatformRouting/PlatformRouting/_ViewController/RoutingDrawerController.swift new file mode 100644 index 000000000..b4f77aadb --- /dev/null +++ b/PlatformRouting/PlatformRouting/_ViewController/RoutingDrawerController.swift @@ -0,0 +1,134 @@ +// +// RoutingDrawerController.swift +// PlatformRouting +// +// Created by Qiang Huang on 7/12/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import DrawerMenu +import RoutingKit +import UIToolkits +import Utilities + +open class RoutingDrawerController: UIViewController, UIViewControllerDrawerProtocol, ParsingProtocol { + override open var parser: Parser { + return RoutingTabBarController.parserOverwrite ?? super.parser + } + + public var drawer: String? + public var root: String? + + @IBInspectable var path: String? + + @IBInspectable open var routingMap: String? { + didSet { + if routingMap != oldValue { + if let destinations = parser.asDictionary(JsonLoader.load(bundles: Bundle.particles, fileName: routingMap)) { + drawer = parser.asString(destinations["drawer"]) + root = parser.asString(destinations["root"]) + } + } + } + } + + public var drawerMenu: DrawerMenu? + public var center: UIViewController? + public var left: UIViewController? + + public var isOpen: Bool { + return drawerMenu?.isOpenLeft ?? false + } + + override open func viewDidLoad() { + super.viewDidLoad() + installViewController(path: root, nav: false) { [weak self] center in + if let self = self, let center = center { + self.installViewController(path: self.drawer, nav: true) { [weak self] left in + if let self = self, let left = left { + let drawer = DrawerMenu(center: center, left: left) + drawer.panGestureType = .none + if UIDevice.current.userInterfaceIdiom == .pad { + drawer.leftMenuWidth = 320.0 + } + self.addChild(drawer) + self.view.addSubview(drawer.view) + drawer.didMove(toParent: self) + + self.center = center + self.left = left + self.drawerMenu = drawer + } + } + } + } + } + + open func installViewController(path: String?, nav: Bool, completion: @escaping ((UIViewController?) -> Void)) { + if let path = path, let router = (Router.shared as? MappedUIKitRouter) { + router.viewController(for: path) { viewController in + guard let viewController = viewController else { + completion(nil) + return + } + if nav, let nav = UINavigationController.loadNavigator(storyboard: "Nav") { + nav.viewControllers = [viewController] + completion(nav) + } else { + completion(viewController) + } + } + } else { + completion(nil) + } + } + + override public func topmost() -> UIViewController? { + if isOpen { + return left?.topmost() ?? super.topmost() + } else { + return center?.topmost() ?? super.topmost() + } + } +} + +extension RoutingDrawerController: NavigableProtocol { + public var history: RoutingRequest? { + return RoutingRequest(path: path ?? "/") + } + + public func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if request?.path == (path ?? "/") { + completion?(self, true) + } else { + navigate(to: request, viewController: center, animated: animated) { [weak self] destination, succeeded in + if let self = self { + if succeeded { + completion?(destination, succeeded) + } else { + self.navigate(to: request, viewController: self.left, animated: animated, completion: { [weak self] destination, completed in + if completed { + self?.drawerMenu?.open(to: .left) + } + completion?(destination, completed) + }) + } + } + } + } + } + + public func navigate(to request: RoutingRequest?, viewController: UIViewController?, animated: Bool, completion: RoutingCompletionBlock?) { + if let viewController = viewController as? NavigableProtocol { + viewController.navigate(to: request, animated: animated) { _, completed in + if completed { + completion?(viewController, true) + } else { + completion?(nil, false) + } + } + } else { + completion?(nil, false) + } + } +} diff --git a/PlatformRouting/PlatformRouting/_ViewController/RoutingSplitViewController.swift b/PlatformRouting/PlatformRouting/_ViewController/RoutingSplitViewController.swift new file mode 100644 index 000000000..0e2ff8f17 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_ViewController/RoutingSplitViewController.swift @@ -0,0 +1,126 @@ +// +// RoutingSplitViewController.swift +// PlatformRouting +// +// Created by Qiang Huang on 1/20/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import RoutingKit +import UIToolkits +import Utilities + +open class RoutingSplitViewController: UISplitViewController, ParsingProtocol { + override open var parser: Parser { + return RoutingTabBarController.parserOverwrite ?? super.parser + } + + public var show: String? + public var detail: String? + + @IBInspectable var path: String? + + @IBInspectable open var routingMap: String? { + didSet { + if routingMap != oldValue { + if let destinations = parser.asDictionary(JsonLoader.load(bundles: Bundle.particles, fileName: routingMap)) { + show = parser.asString(destinations["show"]) + detail = parser.asString(destinations["detail"]) + } + } + } + } + + public var left: UIViewController? { + didSet { + if left !== oldValue { + installLeft() + } + } + } + + public var center: UIViewController? { + didSet { + if center !== oldValue { + installCenter() + } + } + } + + override open func viewDidLoad() { + super.viewDidLoad() + preferredDisplayMode = .allVisible + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in + self?.installViewControllers() + } + } + + open func installViewController(path: String?, nav: Bool, completion: @escaping ((UIViewController?) -> Void)) { + if let path = path, let router = (Router.shared as? MappedUIKitRouter) { + router.viewController(for: path) { viewController in + guard let viewController = viewController else { + completion(nil) + return + } + if nav, let nav = UINavigationController.loadNavigator(storyboard: "Nav") { + nav.viewControllers = [viewController] + completion(nav) + } else { + completion(viewController) + } + } + } else { + completion(nil) + } + } + + open func installViewControllers() { + installViewController(path: show, nav: true) { [weak self] left in + if let self = self { + self.left = left + self.installViewController(path: self.detail, nav: true) { [weak self] center in + self?.center = center + } + } + } + } + + open func installLeft() { + if let left = left { + viewControllers = [left] + if let show = show { + DispatchQueue.main.async { [weak self] in + if let self = self { + (self.left as? NavigableProtocol)?.navigate(to: RoutingRequest(path: show), animated: true, completion: nil) + } + } + } + } + } + + open func installCenter() { + if let center = center, let left = left { + viewControllers = [left, center] + if let detail = detail { + DispatchQueue.main.async { [weak self] in + if let self = self { + (self.center as? NavigableProtocol)?.navigate(to: RoutingRequest(path: detail), animated: true, completion: nil) + } + } + } + } + } + + public var history: RoutingRequest? { + return RoutingRequest(path: path ?? "/") + } + + override public func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if request?.path == (path ?? "/") { + completion?(self, true) + } else { + super.navigate(to: request, animated: animated, completion: completion) + } + } +} diff --git a/PlatformRouting/PlatformRouting/_ViewController/RoutingTabBarController.swift b/PlatformRouting/PlatformRouting/_ViewController/RoutingTabBarController.swift new file mode 100644 index 000000000..78219de3d --- /dev/null +++ b/PlatformRouting/PlatformRouting/_ViewController/RoutingTabBarController.swift @@ -0,0 +1,321 @@ +// +// RoutingTabBarController.swift +// PlatformRouting +// +// Created by Qiang Huang on 12/1/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Differ +import ParticlesKit +import RoutingKit +import UIToolkits +import Utilities + +open class RoutingTabBarController: UITabBarController, ParsingProtocol { + @IBOutlet public var centerButton: UIButton? { + didSet { + didSetCenterButton(oldValue: oldValue) + } + } + + @objc public dynamic var badging: UrlBadgingInteractor? { + didSet { + changeObservation(from: oldValue, to: badging, keyPath: #keyPath(UrlBadgingInteractor.dictionary)) { [weak self] _, _, _, _ in + if let self = self { + self.updateBadging() + } + } + } + } + + public static var parserOverwrite: Parser? + + override open var parser: Parser { + return RoutingTabBarController.parserOverwrite ?? super.parser + } + + public var maps: [TabbarItemInfo]? { + didSet { + if maps != oldValue { + let current = maps ?? [TabbarItemInfo]() + let old = oldValue ?? [TabbarItemInfo]() + let diff: Diff = self.diff(current: current, old: old) + let patches = self.patches(diff: diff, current: current, old: old) + update(diff: diff, patches: patches) + updateBadging() + } + } + } + + @IBInspectable var path: String? + + private var actionPath: String? + + @IBInspectable open var routingMap: String? { + didSet { + if routingMap != oldValue { + if let destinations = parser.asArray(JsonLoader.load(bundles: Bundle.particles, fileName: routingMap)) { + parse(array: destinations) + } + } + } + } + + var previousController: UIViewController? + + override open func viewDidLoad() { + super.viewDidLoad() + delegate = self + } + + override open func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + RoutingHistory.shared.record(destination: self) + alignCenterButton() + } + + public func alignCenterButton() { + if let centerButton = centerButton { + if let barItems = tabBar.items, barItems.count > 0 { + for i in 0 ..< barItems.count { + if isAction(index: i) { + barItems[i].isEnabled = false + } + } + } + tabBar.addSubview(centerButton) + let x = tabBar.centerXAnchor.constraint(equalTo: centerButton.centerXAnchor) + x.priority = .required + x.isActive = true + let constraint = tabBar.topAnchor.constraint(equalTo: centerButton.centerYAnchor) + constraint.constant = -18 + constraint.priority = .required + constraint.isActive = true + + tabBar.layoutIfNeeded() + } + } + + open func didSetCenterButton(oldValue: UIButton?) { + if centerButton !== oldValue { + oldValue?.removeTarget() + centerButton?.addTarget(self, action: #selector(action(_:))) + } + } + + public func parse(array: [Any]) { + if let data = array as? [[String: Any]] { + var maps = [TabbarItemInfo]() + for dictionary in data { + let routing = TabbarItemInfo() + routing.parse(dictionary: dictionary) + maps.append(routing) + } + self.maps = maps + } + } + + open func diff(current: [TabbarItemInfo], old: [TabbarItemInfo]) -> Diff { + return old.diff(current) { (object1, object2) -> Bool in + object1.path == object2.path + } + } + + open func patches(diff: Diff, current: [TabbarItemInfo], old: [TabbarItemInfo]) -> [Patch] { + return diff.patch(from: old, to: current) { (element1, element2) -> Bool in + switch (element1, element2) { + case let (.insert(at1), .insert(at2)): + return at1 < at2 + case (.insert, .delete): + return false + case (.delete, .insert): + return true + case let (.delete(at1), .delete(at2)): + return at1 > at2 + } + } + } + + open func update(diff: Diff, patches: [Patch]) { + var viewControllers = self.viewControllers ?? [UIViewController]() + for change in patches { + switch change { + case let .deletion(index): + viewControllers.remove(at: index) + + case let .insertion(index: index, element: item): + installViewController(for: item) { viewController in + if let viewController = viewController { + if index >= viewControllers.count { + viewControllers.append(viewController) + } else { + viewControllers.insert(viewController, at: index) + } + } + } + } + } + self.viewControllers = viewControllers + } + + private func path(info: TabbarItemInfo) -> String? { + if let router = Router.shared as? MappedUIKitRouter, let path = info.path { + let request = RoutingRequest(path: path) + return router.transform(request: request).path + } else { + return nil + } + } + + open func installViewController(for info: TabbarItemInfo, completion: @escaping ((UIViewController?) -> Void)) { + if let path = path(info: info) { + if isActionTab(item: info) { + actionPath = path + let viewController = UIViewController() + setup(viewController: viewController, info: info) + completion(viewController) + } else { + if let router = (Router.shared as? MappedUIKitRouter) { + router.viewController(for: path) { [weak self] viewController in + if let viewController = viewController { + if let nav = viewController as? NavigableProtocol { + let request = RoutingRequest(path: path) + nav.navigate(to: request, animated: false, completion: nil) + } + if UIDevice.current.canSplit && (info.split ?? false) { + let splitter = UISplitViewController() + splitter.preferredDisplayMode = .allVisible + if let nav = UINavigationController.load(storyboard: "Nav", with: viewController), let rightNav = UINavigationController.loadNavigator(storyboard: "Nav") { + splitter.viewControllers = [nav, rightNav] + + self?.setup(viewController: splitter, info: info) + completion(splitter) + } else { + completion(nil) + } + } else { + var tabViewController: UIViewController = viewController + //if !(viewController is UIViewControllerEmbeddingProtocol) { + if let nav = UINavigationController.load(storyboard: "Nav", with: viewController) { + tabViewController = nav + } + //} + self?.setup(viewController: tabViewController, info: info) + completion(tabViewController) + } + } else { + self?.actionPath = path + let viewController = UIViewController() + self?.setup(viewController: viewController, info: info) + completion(viewController) + } + } + } else { + actionPath = path + let viewController = UIViewController() + setup(viewController: viewController, info: info) + completion(viewController) + } + } + } else { + completion(nil) + } + } + + private func setup(viewController: UIViewController, info: TabbarItemInfo) { + let tabbarItem = UITabBarItem() + tabbarItem.title = info.title?.localized ?? " " + if let image = info.image { + tabbarItem.image = UIImage.named(image, bundles: Bundle.particles) + } + if let selected = info.selected { + tabbarItem.selectedImage = UIImage.named(selected, bundles: Bundle.particles) + } + viewController.tabBarItem = tabbarItem + } + + open func updateBadging() { + if let viewControllers = viewControllers, let maps = maps, viewControllers.count == maps.count { + for i in 0 ..< maps.count { + let viewController = viewControllers[i] + if let tabbarItem = viewController.tabBarItem { + let map = maps[i] + if let path = map.path { + tabbarItem.badgeValue = badging?.badge(for: path) + } else { + tabbarItem.badgeValue = nil + } + } + } + } + } + + private func isAction(index: Int) -> Bool { + if centerButton !== nil, let maps = maps { + return (maps.count == 3 && index == 1) || (maps.count == 5 && index == 2) + } else { + return false + } + } + + private func isActionTab(item: TabbarItemInfo) -> Bool { + if item.image == nil { + return true + } else if let index = maps?.firstIndex(of: item) { + return isAction(index: index) + } else { + return false + } + } + + @IBAction func action(_ sender: Any?) { + if let path = actionPath { + Router.shared?.navigate(to: RoutingRequest(path: path), animated: true, completion: nil) + } + } +} + +public class TabbarItemInfo: NSObject, ParsingProtocol { + override open var parser: Parser { + return RoutingTabBarController.parserOverwrite ?? super.parser + } + + public var path: String? + public var title: String? + public var image: String? + public var selected: String? + public var split: Bool? + + public func parse(dictionary: [String: Any]) { + path = parser.asString(dictionary["path"]) + title = parser.asString(dictionary["title"]) + image = parser.asString(dictionary["image"]) + selected = parser.asString(dictionary["selected"]) + split = parser.asBoolean(dictionary["split"])?.boolValue + } +} + +extension RoutingTabBarController: UITabBarControllerDelegate { + public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { + if previousController == viewController { + var vc: UIViewController? = viewController + if let navVC = viewController as? UINavigationController { + vc = navVC.topViewController + } + if let vc = vc { + if vc.isViewLoaded && (vc.view.window != nil) { + vc.scrollToTop() + } + } + } + + previousController = viewController + + if let index = viewControllers?.firstIndex(of: viewController) { + return !isAction(index: index) + } else { + return true + } + } +} diff --git a/PlatformRouting/PlatformRouting/_ViewController/RoutingViewController.swift b/PlatformRouting/PlatformRouting/_ViewController/RoutingViewController.swift new file mode 100644 index 000000000..2766925f9 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_ViewController/RoutingViewController.swift @@ -0,0 +1,64 @@ +// +// NavigableViewController.swift +// PlatformRouting +// +// Created by Qiang Huang on 11/20/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import RoutingKit +import UIKit +import UIToolkits + +open class NavigableViewController: KeyboardAdjustingViewController, NavigableProtocol { + open var routingRequest: RoutingRequest? + + open var history: RoutingRequest? { + return routingRequest + } + + override open func viewDidLoad() { + super.viewDidLoad() + #if _iOS + if navigationItem.backBarButtonItem == nil { + navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: nil, action: nil) + } + #endif + } + + override open func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) +// print("\(String(describing: type(of: self))) viewWillAppear") +// printTransitionStates() + if let tabbarController = tabBarController ?? navigationController?.tabBarController { + _ = RoutingHistory.shared.makeLast(destination: tabbarController) + } + RoutingHistory.shared.record(destination: self) + } + + override open func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) +// print("\(String(describing: type(of: self))) viewWillDisappear") +// printTransitionStates() +// if !dismissing() && !popping() { +// RoutingHistory.shared.remove(destination: self) +// } + } + + open func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { + if let routingRequest = routingRequest { + return routingRequest.path == request?.path && (routingRequest.params as NSDictionary?) == (request?.params as NSDictionary?) + } else { + return true + } + } + + open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if arrive(to: request, animated: animated) { + routingRequest = request + completion?(self, true) + } else { + completion?(self, false) + } + } +} diff --git a/PlatformRouting/PlatformRouting/_iOS/_Action/MailAction.swift b/PlatformRouting/PlatformRouting/_iOS/_Action/MailAction.swift new file mode 100644 index 000000000..9f6f15f1e --- /dev/null +++ b/PlatformRouting/PlatformRouting/_iOS/_Action/MailAction.swift @@ -0,0 +1,58 @@ +// +// Mail.swift +// PlatformRouting +// +// Created by Qiang Huang on 5/19/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import MessageUI +import RoutingKit +import UIToolkits +import Utilities + +open class MailAction: NSObject, NavigableProtocol, MFMailComposeViewControllerDelegate { + public var completion: RoutingCompletionBlock? + open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + switch request?.scheme { + case "mailto": + mail(request: request, completion: completion) + + default: + completion?(nil, false) + } + } + + open func mail(request: RoutingRequest?, completion: RoutingCompletionBlock?) { + if MFMailComposeViewController.canSendMail() { + if let mail = configuredMailComposeViewController(request: request), let topmost = ViewControllerStack.shared?.topmost() { + self.completion = completion + topmost.present(mail, animated: true, completion: nil) + return + } else { + self.completion?(nil, false) + } + } else { + self.completion?(nil, false) + } + } + + open func configuredMailComposeViewController(request: RoutingRequest?) -> MFMailComposeViewController? { + if let path = request?.path { + let mail = MFMailComposeViewController() + mail.mailComposeDelegate = self // Extremely important to set the --mailComposeDelegate-- property, NOT the --delegate-- property + mail.setToRecipients([path]) + if let subject = parser.asString(request?.params?["subject"]) { + mail.setSubject(subject) + } + return mail + } + return nil + } + + public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + controller.dismiss(animated: true) { [weak self] in + self?.completion?(nil, result == .sent) + } + } +} diff --git a/PlatformRouting/PlatformRouting/_iOS/_Action/RateAction.swift b/PlatformRouting/PlatformRouting/_iOS/_Action/RateAction.swift new file mode 100644 index 000000000..c1d0b629c --- /dev/null +++ b/PlatformRouting/PlatformRouting/_iOS/_Action/RateAction.swift @@ -0,0 +1,27 @@ +// +// RateAction.swift +// PlatformRouting +// +// Created by Qiang Huang on 5/11/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import RoutingKit +import UIKit +import Utilities + +open class RateAction: NSObject, NavigableProtocol { + open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + switch request?.path { + case "/action/rate": + rate() + completion?(nil, true) + + default: + completion?(nil, false) + } + } + + open func rate() { + } +} diff --git a/PlatformRouting/PlatformRouting/_iOS/_Action/TelAction.swift b/PlatformRouting/PlatformRouting/_iOS/_Action/TelAction.swift new file mode 100644 index 000000000..1f9ccfb74 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_iOS/_Action/TelAction.swift @@ -0,0 +1,9 @@ +// +// TelAction.swift +// PlatformRouting +// +// Created by Qiang Huang on 5/19/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation diff --git a/PlatformRouting/PlatformRouting/_iOS/_App/MappedUIKitAppRouter.swift b/PlatformRouting/PlatformRouting/_iOS/_App/MappedUIKitAppRouter.swift new file mode 100644 index 000000000..258d04e9b --- /dev/null +++ b/PlatformRouting/PlatformRouting/_iOS/_App/MappedUIKitAppRouter.swift @@ -0,0 +1,58 @@ +// +// MappedUIKitAppRouter.swift +// PlatformRouting +// +// Created by Qiang Huang on 10/12/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import RoutingKit +import UIToolkits + +open class MappedUIKitAppRouter: MappedUIKitRouter { + internal override func root(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + if let applicationDelegate = UIApplication.shared.delegate, let window = applicationDelegate.window { + UIView.animate(window, type: .fade, direction: .none, duration: UIView.defaultAnimationDuration, animations: { + if viewController is UITabBarController { + window?.rootViewController = viewController + } else if viewController is UISplitViewController { + window?.rootViewController = viewController + (viewController as? UISplitViewController)?.delegate = self + } else if viewController is UIViewControllerEmbeddingProtocol { + window?.rootViewController = viewController + } else if viewController is UIViewControllerDrawerProtocol { + window?.rootViewController = viewController + } else { + window?.rootViewController = UIViewController.navigation(with: viewController) + } + }) { _ in + completion?(viewController, true) + } + } else { + completion?(nil, false) + } + } + + private var primary: UIViewController? + private var secondary: UIViewController? +} + +extension MappedUIKitAppRouter: UISplitViewControllerDelegate { + open func primaryViewController(forCollapsing splitViewController: UISplitViewController) -> UIViewController? { + primary = splitViewController.viewControllers[0] + secondary = splitViewController.viewControllers[1] + return primary + } + + open func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool { + return true + } + + open func primaryViewController(forExpanding splitViewController: UISplitViewController) -> UIViewController? { + return primary + } + + open func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? { + return secondary + } +} diff --git a/PlatformRouting/PlatformRouting/_iOS/_App/MappedUIKitRouter.swift b/PlatformRouting/PlatformRouting/_iOS/_App/MappedUIKitRouter.swift new file mode 100644 index 000000000..e15c7576b --- /dev/null +++ b/PlatformRouting/PlatformRouting/_iOS/_App/MappedUIKitRouter.swift @@ -0,0 +1,511 @@ +// +// MappedUIKitRouter.swift +// PlatformRouting +// +// Created by John Huang on 12/26/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import DrawerMenu +import PanModal +import RoutingKit +import UIToolkits +import Utilities + +public typealias BacktrackRoutingCompletionBlock = (UIViewController?) -> Void + +open class MappedUIKitRouter: MappedRouter { + let fadeTransitioner = CustomTransition.fade.transitionDelegate + + var actions: [NSObject & NavigableProtocol] = [NSObject & NavigableProtocol]() + override open func backtrack(request: RoutingRequest, animated: Bool, completion: RoutingCompletionBlock?) { +// backtrack(to: ViewControllerStack.shared?.topmost(), request: request, animated: animated, completion: completion) + backtrack(root: ViewControllerStack.shared?.root(), request: request, animated: animated, completion: completion) + } + + open func backtrack(root viewController: UIViewController?, request: RoutingRequest, animated: Bool, completion: RoutingCompletionBlock?) { + if request.host != defaults?["host"] { + completion?(nil, false) + } else { + backtracked(viewController: viewController, request: request, animated: animated) { [weak self] found in + if let found = found { + self?.unwind(from: ViewControllerStack.shared?.topmost(), to: found, completion: completion) + } else { + completion?(nil, false) + } + } + } + } + + open func backtracked(viewController: UIViewController?, request: RoutingRequest, animated: Bool, completion: BacktrackRoutingCompletionBlock?) { + if let destination = viewController as? NavigableProtocol { + destination.navigate(to: request, animated: animated, completion: { [weak self] finalDestination, completed in + if completed { + completion?(finalDestination as? UIViewController) + } else { + self?.backtracked(presenting: viewController, request: request, animated: animated, completion: { [weak self] found in + if found != nil { + completion?(found) + } else { + self?.backtracked(tabbarController: viewController as? UITabBarController, index: 0, request: request, animated: animated) { [weak self] found in + if found != nil { + completion?(found) + } else { + self?.backtracked(navigationController: viewController as? UINavigationController, index: 0, request: request, animated: animated) { [weak self] found in + if found != nil { + completion?(found) + } else { + self?.backtracked(embeddingController: viewController as? UIViewControllerEmbeddingProtocol, index: 0, request: request, animated: animated, completion: { [weak self] found in + if found != nil { + completion?(found) + } else { + let temp: Any? = viewController + self?.backtracked(halfController: temp as? UIViewControllerHalfProtocol, request: request, animated: animated, completion: completion) + } + }) + } + } + } + } + } + }) + } + }) + } else { + completion?(nil) + } + } + + open func backtracked(presenting: UIViewController?, request: RoutingRequest, animated: Bool, completion: BacktrackRoutingCompletionBlock?) { + if let child = presenting?.presentedViewController { + backtracked(viewController: child, request: request, animated: animated, completion: completion) + } else { + completion?(nil) + } + } + + open func backtracked(tabbarController: UITabBarController?, index: Int, request: RoutingRequest, animated: Bool, completion: BacktrackRoutingCompletionBlock?) { + if let children = tabbarController?.children, index < children.count { + let child = children[index] + backtracked(viewController: child, request: request, animated: animated) { [weak self] found in + if found != nil { + completion?(child) + } else { + self?.backtracked(tabbarController: tabbarController, index: index + 1, request: request, animated: animated, completion: completion) + } + } + } else { + completion?(nil) + } + } + + open func backtracked(navigationController: UINavigationController?, index: Int, request: RoutingRequest, animated: Bool, completion: BacktrackRoutingCompletionBlock?) { + if let children = navigationController?.viewControllers, index < children.count { + let child = children[index] + backtracked(viewController: child, request: request, animated: animated) { [weak self] found in + if found != nil { + completion?(child) + } else { + self?.backtracked(navigationController: navigationController, index: index + 1, request: request, animated: animated, completion: completion) + } + } + } else { + completion?(nil) + } + } + + open func backtracked(embeddingController: UIViewControllerEmbeddingProtocol?, index: Int, request: RoutingRequest, animated: Bool, completion: BacktrackRoutingCompletionBlock?) { + var children: [UIViewController] = [] + if let embedded = embeddingController?.embedded { + children.append(embedded) + } + if let floated = embeddingController?.floated { + children.append(floated) + } + if index < children.count { + let child = children[index] + backtracked(viewController: child, request: request, animated: animated) { [weak self] found in + if found != nil { + completion?(child) + } else { + self?.backtracked(embeddingController: embeddingController, index: index + 1, request: request, animated: animated, completion: completion) + } + } + } else { + completion?(nil) + } + } + + open func backtracked(halfController: UIViewControllerHalfProtocol?, request: RoutingRequest, animated: Bool, completion: BacktrackRoutingCompletionBlock?) { + if let child = halfController?.floatingManager?.halved { + backtracked(viewController: child, request: request, animated: animated, completion: completion) + } else { + completion?(nil) + } + } + + open func backtrack(to viewController: UIViewController?, request: RoutingRequest, animated: Bool, completion: RoutingCompletionBlock?) { + if let viewController = viewController { + if let destination = viewController as? NavigableProtocol { + destination.navigate(to: request, animated: animated, completion: { [weak self] _, completed in + if completed { + self?.unwind(from: ViewControllerStack.shared?.topmost(), to: viewController, completion: completion) + } else { + self?.backtrackParent(of: viewController, request: request, animated: animated, completion: completion) + } + }) + } else { + backtrackParent(of: viewController, request: request, animated: animated, completion: completion) + } + } else { + completion?(self, false) + } + } + + open func backtrackParent(of viewController: UIViewController, request: RoutingRequest, animated: Bool, completion: RoutingCompletionBlock?) { + if let nav = viewController.navigationController { + backtrack(to: nav, request: request, animated: animated, completion: completion) + } else if let presenting = viewController.presentingViewController { + backtrack(to: presenting, request: request, animated: animated, completion: completion) + } else if let tabbar = viewController.tabBarController { + backtrack(to: tabbar, request: request, animated: animated, completion: completion) + } else { + // this is the root + completion?(nil, false) + } + } + + open func unwind(from viewController: UIViewController?, to target: UIViewController, completion: RoutingCompletionBlock?) { + if viewController !== target { + if target.presentedViewController != nil { + target.dismiss(animated: true, completion: nil) + } + target.navigationController?.popToViewController(target, animated: true) + completion?(target, true) + } else { + completion?(viewController, true) + } + } + + override open func navigate(to map: RoutingMap, request: RoutingRequest, presentation: RoutingPresentation?, animated: Bool, completion: RoutingCompletionBlock?) { + Console.shared.log(request.path) + route(storyboardOrBuilder: map, request: request, presentation: presentation, animated: animated) { [weak self] object, completed in + if completed { + completion?(object, true) + } else { + self?.route(xib: map, request: request, completion: completion) + } + } + } + + private func route(storyboardOrBuilder map: RoutingMap, request: RoutingRequest, presentation: RoutingPresentation?, animated: Bool, completion: RoutingCompletionBlock?) { + loadViewController(from: map) { [weak self] viewController in + if let viewController = viewController { + self?.route(viewController: viewController, presentation: presentation ?? map.presentation, animated: animated) { object, success in + if let destination = (viewController as? NavigableProtocol) { + destination.navigate(to: request, animated: true, completion: completion) + ViewControllerStack.shared?.didShow(viewController: viewController) + } else if let embedder = viewController as? UIViewControllerEmbeddingProtocol { + self?.backtracked(embeddingController: embedder, index: 0, request: request, animated: true) { viewController in + if viewController != nil { + completion?(viewController, true) + } else { + completion?(nil, false) + } + } + } else { + completion?(object, success) + } + } + } else { + completion?(nil, false) + } + } + } + + internal func route(viewController: UIViewController, presentation: RoutingPresentation?, animated: Bool, completion: RoutingCompletionBlock?) { + let presentation = presentation ?? defaultPresentation(for: viewController) + switch presentation { + case .root: + root(viewController, animated: animated, completion: completion) + + case .show: + show(viewController, animated: animated, completion: completion) + + case .detail: + detail(viewController, animated: animated, completion: completion) + + case .prompt: + prompt(viewController, animated: animated, completion: completion) + + case .callout: + callout(viewController, animated: animated, completion: completion) + + case .float: + float(viewController, animated: animated, completion: completion) + + case .half: + half(viewController, animated: animated, completion: completion) + + case .embed: + embed(viewController, animated: animated, completion: completion) + + case .drawer: + drawer(viewController, animated: animated, completion: completion) + + case .popup: + popup(viewController, animated: animated, completion: completion) + + case .presentOverFullScreen: + presentOverFullScreen(viewController, animated: animated, completion: completion) + } + } + + private func defaultPresentation(for viewController: UIViewController) -> RoutingPresentation { + if viewController is UINavigationController { + return .prompt + } else { + return .show + } + } + + internal func root(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + completion?(nil, false) + } + + private func show(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + if UIDevice.current.canSplit, let navigationController = ViewControllerStack.shared?.topmost()?.navigationController, let splitter = navigationController.splitViewController { + if let nav = splitter.viewControllers.first as? UINavigationController { + nav.pushViewController(viewController, animated: animated) + } else { + let nav = UIViewController.navigation(with: viewController) + splitter.show(nav, sender: animated) + } + completion?(viewController, true) + } else { + push(viewController, animated: animated, completion: completion) + } + } + + private func detail(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + if UIDevice.current.canSplit { + if let navigationController = ViewControllerStack.shared?.topmost()?.navigationController { + if let splitter = navigationController.splitViewController { + let nav = UIViewController.navigation(with: viewController) + splitter.showDetailViewController(nav, sender: nil) + completion?(viewController, true) + return + } + } + } + push(viewController, animated: animated, completion: completion) + } + + private func push(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + if let navigationController = ViewControllerStack.shared?.topmost()?.navigationController { + navigationController.pushViewController(viewController, animated: animated) + completion?(viewController, true) + } else { + completion?(nil, false) + } + } + + private func prompt(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + if let topmost = ViewControllerStack.shared?.topParent() { + let navigationController = UIViewController.navigation(with: viewController) + topmost.present(navigationController, animated: animated) { + } + completion?(viewController, true) + } else { + completion?(nil, false) + } + } + + private func callout(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + prompt(viewController, animated: animated, completion: completion) + } + + private func half(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + if let topmost = ViewControllerStack.shared?.topParent() { + if topmost.isPanModalPresented { + let parent = topmost.presentingViewController + topmost.dismiss(animated: true) { /* [weak self] in */ + viewController.modalPresentationStyle = .custom + parent?.presentPanModal(viewController) + completion?(viewController, true) + } + } else { + topmost.presentPanModal(viewController) + completion?(viewController, true) + } +// if let floater = (topmost as Any?) as? UIViewControllerHalfProtocol { +// floater.floatingManager?.half(viewController, animated: animated) +// } else { +// let navigationController = UIViewController.navigation(with: viewController) +// topmost.present(navigationController, animated: animated) { +// } +// } + } else { + completion?(nil, false) + } + } + + private func popup(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + if let root = UIViewController.root() { + // wrapper is necessary for custom transitions + let wrapperViewController = UIViewController() + wrapperViewController.modalPresentationStyle = .overFullScreen // or .custom for other presentation styles + wrapperViewController.transitioningDelegate = fadeTransitioner + + // Add the UIHostingController to the wrapper UIViewController + wrapperViewController.addChild(viewController) + wrapperViewController.view.addSubview(viewController.view) + viewController.didMove(toParent: wrapperViewController) + + // Ensure the hosting controller's view fills the wrapper view + viewController.view.translatesAutoresizingMaskIntoConstraints = false + viewController.view.snp.makeConstraints { $0.edges.equalTo(wrapperViewController.view) } + + // Present the wrapper UIViewController + root.present(wrapperViewController, animated: true, completion: nil) + + completion?(viewController, true) + } else { + completion?(nil, false) + } + } + + private func presentOverFullScreen(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + if let topmost = ViewControllerStack.shared?.topParent() { + let navigationController = UIViewController.navigation(with: viewController) + navigationController.modalPresentationStyle = .overFullScreen + //speeds up the animation by 2x + navigationController.view.layer.speed = 2 + topmost.present(navigationController, animated: animated) { + } + completion?(viewController, true) + } else { + completion?(nil, false) + } + } + + private func float(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + if let topmost = ViewControllerStack.shared?.topParent() { + if let embedder = topmost.parentViewControllerConforming(protocol: UIViewControllerEmbeddingProtocol.self) as? UIViewControllerEmbeddingProtocol, embedder.float(viewController, animated: true) { + } else { + let navigationController = UIViewController.navigation(with: viewController) + topmost.present(navigationController, animated: animated) { + } + } + completion?(viewController, true) + } else { + completion?(nil, false) + } + } + + private func floaterParent(viewController: UIViewController) -> UIViewControllerEmbeddingProtocol? { + if let floater = viewController as? UIViewControllerEmbeddingProtocol { + return floater + } + if let presenting = viewController.navigationController?.presentingViewController { + return floaterParent(viewController: presenting) + } + if let presenting = viewController.presentingViewController { + return floaterParent(viewController: presenting) + } + if let parent = viewController.parent { + return floaterParent(viewController: parent) + } + if let tabbar = viewController.tabBarController { + return floaterParent(viewController: tabbar) + } + return nil + } + + private func embed(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + if let topmost = ViewControllerStack.shared?.topmost() { + if let embedder: UIViewControllerEmbeddingProtocol = topmost.parentViewControllerConforming(protocol: UIViewControllerEmbeddingProtocol.self) as? UIViewControllerEmbeddingProtocol, embedder.embed(viewController, animated: true) { + } else { + let navigationController = UIViewController.navigation(with: viewController) + topmost.present(navigationController, animated: animated) { + } + } + completion?(viewController, true) + } else { + completion?(nil, false) + } + +// if let root = ViewControllerStack.shared?.root() { +// if let embedder = root as? UIViewControllerEmbeddingProtocol { +// embedder.embeddingFloatingManager?.embed(viewController, animated: animated) +// } else if let topmost = ViewControllerStack.shared?.topmost() { +// let navigationController = UIViewController.navigation(with: viewController) +// topmost.present(navigationController, animated: animated) { +// } +// } +// completion?(viewController, true) +// } else { +// completion?(nil, false) +// } + } + + private func drawer(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + if let root = ViewControllerStack.shared?.root() { + root.drawer()?.open(to: .left) + completion?(viewController, true) + } else { + completion?(nil, false) + } + } + + private func route(xib map: RoutingMap, request: RoutingRequest, completion: RoutingCompletionBlock?) { + loadAction(from: map) { [weak self] action in + if let action = action { + self?.actions.append(action) + weak var actionReference = action + action.navigate(to: request, animated: true) { data, success in + self?.actions.removeAll(where: { (actionInList) -> Bool in + actionReference === actionInList + }) + completion?(data, success) + } + } else { + assertionFailure("Action not found: \(String(describing: request.url))") + completion?(nil, false) + } + } + } + + public func viewController(for path: String, completion: @escaping ((UIViewController?) -> Void)) { + let request = RoutingRequest(path: path) + if let map = self.map(for: request) { + loadViewController(from: map, completion: completion) + } else { + completion(nil) + } + } + + private func loadViewController(from map: RoutingMap, completion: @escaping ((UIViewController?) -> Void)) { + if let builder = map.builder { + ClassLoader.load(from: builder, completion: completion) + } else if let storyboard = map.storyboard { + let viewController = UIViewController.load(storyboard: storyboard) + completion(viewController) + } else { + Console.shared.log("View Controller not found: \(map)") + completion(nil) + } + } + + private func loadAction(from map: RoutingMap, completion: @escaping (((NSObject & NavigableProtocol)?) -> Void)) { + if let xib = map.xib { + let action: (NSObject & NavigableProtocol)? = XibLoader.load(from: xib) + completion(action) + } else if let builder = map.builder { + ClassLoader.load(from: builder, completion: completion) + } else { + Console.shared.log("Action not found: \(map)") + completion(nil) + } + } +} diff --git a/PlatformRouting/PlatformRouting/_iOS/_App/RoutingAppDelegate.swift b/PlatformRouting/PlatformRouting/_iOS/_App/RoutingAppDelegate.swift new file mode 100644 index 000000000..dfbfa00c0 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_iOS/_App/RoutingAppDelegate.swift @@ -0,0 +1,166 @@ +// +// RoutingAppDelegate.swift +// PlatformRouting +// +// Created by Qiang Huang on 12/3/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import RoutingKit +import UIKit +import Utilities + +open class RoutingAppDelegate: UIResponder, UIApplicationDelegate { + #if _iOS + private var shortcut: UIApplicationShortcutItem? + private var deeplink: URL? + #endif + + private var started: Bool = false + + open func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + #if _iOS + shortcut = launchOptions?[UIApplication.LaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem + #endif + + startup { [weak self] in + NotificationBridge.shared?.launched() + if let self = self { + self.route() { [weak self] in + self?.started = true + self?.handleDeeplink() + } + } + } + return true + } + + open func startup(completion: @escaping () -> Void) { + completion() + } + + open func route(completion: @escaping () -> ()) { + Router.shared = router() + + routeToStart(completion: completion) + } + + open func router() -> RouterProtocol? { + let router = MappedUIKitAppRouter(file: "routing.json") + router.appState = AppState.shared + return router + } + + #if _iOS + open func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { + shortcut = shortcutItem + } + #endif + + // Reports app open from deep link from apps which do not support Universal Links (Twitter) and for iOS8 and below + open func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { + deeplink = url + return true + } + + open func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + deeplink = url + return true + } + + open func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + if let url = userActivity.webpageURL { + deeplink = url + } + return true + } + + // Reports app open from a Universal Link for iOS 9 or later + open func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { + if let url = userActivity.webpageURL { + deeplink = url + } + return true + } + + open func applicationDidBecomeActive(_ application: UIApplication) { + #if _iOS + handleDeeplink() + #endif + } + + + /// Prioritized handling of the deeplink + /// - Parameter url: the deeplink url to handle + /// - Returns: true if the custom handling handled the url + open func customHandle(url: URL) -> Bool { + return false + } + + open func handleDeeplink() { + if started { + if let urlString = shortcut?.type, let url = URL(string: urlString) { + Router.shared?.navigate(to: url, completion: nil) + shortcut = nil + } else if let url = deeplink { + if !customHandle(url: url) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + Router.shared?.navigate(to: url) { [weak self] _, successful in + self?.deepLinkHandled(deeplink: url, successful: successful) + } + } + } + deeplink = nil + } + } + } + + open func deepLinkHandled(deeplink: URL, successful: Bool) { } + + open func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + NotificationBridge.shared?.registered(deviceToken: deviceToken) + } + + open func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + NotificationBridge.shared?.failed(error: error) + } + + open func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + if started { + NotificationBridge.shared?.received(userInfo: userInfo, fetchCompletionHandler: completionHandler) + } else { + deeplink = NotificationBridge.shared?.receivedDeeplink(userInfo: userInfo) + } + } + + open func routingHistory() -> [RoutingRequest]? { + // RoutingHistory.shared.history() + return nil + } + + open func routeToStart(completion: @escaping () -> Void) { + if let history = routingHistory() { + navigate(to: history, index: 0) { _, _ in + completion() + } + } else { + Router.shared?.navigate(to: RoutingRequest(path: "/"), animated: true, completion: { _, _ in + Router.shared?.navigate(to: RoutingRequest(path: "/authorization/notification"), animated: true, completion: { _, _ in + completion() + }) + }) + } + } + + open func navigate(to history: [RoutingRequest], index: Int, completion: RoutingCompletionBlock?) { + if index < history.count { + Router.shared?.navigate(to: history[index], animated: true, completion: { [weak self] _, completed in + if completed { + self?.navigate(to: history, index: index + 1, completion: completion) + } + }) + } else { + completion?(self, true) + } + } +} diff --git a/PlatformRouting/PlatformRouting/_iOS/_Extensions/MappedUIKitExtensionRouter.swift b/PlatformRouting/PlatformRouting/_iOS/_Extensions/MappedUIKitExtensionRouter.swift new file mode 100644 index 000000000..efd353aa9 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_iOS/_Extensions/MappedUIKitExtensionRouter.swift @@ -0,0 +1,23 @@ +// +// MappedUIKitExtensionRouter.swift +// PlatformRouting +// +// Created by Qiang Huang on 12/28/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import RoutingKit +import UIToolkits + +open class MappedUIKitExtensionRouter: MappedUIKitRouter { + internal override func root(_ viewController: UIViewController, animated: Bool, completion: RoutingCompletionBlock?) { + DispatchQueue.main.async { + if var root = (ViewControllerStack.shared as? UIKitExtensionViewControllerStack)?.extensionRoot { + root.embedded = UIViewController.navigation(with: viewController) + completion?(nil, true) + } else { + completion?(nil, false) + } + } + } +} diff --git a/PlatformRouting/PlatformRouting/_iOS/_LocalNotification/SimpleLocalNotification.swift b/PlatformRouting/PlatformRouting/_iOS/_LocalNotification/SimpleLocalNotification.swift new file mode 100644 index 000000000..03ce5d70d --- /dev/null +++ b/PlatformRouting/PlatformRouting/_iOS/_LocalNotification/SimpleLocalNotification.swift @@ -0,0 +1,139 @@ +// +// SimpleLocalNotification.swift +// RoutingKit +// +// Created by Qiang Huang on 11/4/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import RoutingKit +import UIKit +import Utilities +import Combine + +public class SimpleLocalNotification: NSObject, LocalNotificationProtocol, CombineObserving { + public var cancellableMap = [AnyKeyPath : AnyCancellable]() + + private var appState: AppState? { + didSet { + changeObservation(from: oldValue, to: appState, keyPath: #keyPath(AppState.background)) { [weak self] _, _, _, animated in + self?.backgrounded = self?.appState?.background ?? false + } + } + } + private var backgroundId: String? + + private var outstandingIds: [String]? + + public var background: LocalNotificationMessage? { + didSet { + if background !== oldValue { + if let backgroundId = backgroundId { + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [backgroundId]) + UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [backgroundId]) + } + if backgrounded { + backgroundId = sending(message: background) + } + } + } + } + + public var backgrounded: Bool = false { + didSet { + if backgrounded != oldValue { + if backgrounded { + sendBackgrounding() + } else { + removeBackgrounding() + } + } + } + } + + public func sending(message: LocalNotificationMessage?) -> String? { + if let message = message { + let content = UNMutableNotificationContent() + content.title = message.title + + if let subtitle = message.subtitle { + content.subtitle = subtitle + } + if let text = message.text { + content.body = text + } + if let link = message.link { + content.userInfo = ["data": ["firebase": ["link": link]]] + } + content.sound = UNNotificationSound.default + + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: message.delay ?? 0.5, repeats: false) + + let uuidString = UUID().uuidString + let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger) + + let center = UNUserNotificationCenter.current() + center.add(request) { error in + if error != nil { + print("error \(String(describing: error))") + } + } + return uuidString + } + return nil + } + + public func send(message: LocalNotificationMessage) { + if backgrounded, let identifier = sending(message: message) { + if outstandingIds == nil { + outstandingIds = [String]() + } + outstandingIds?.append(identifier) + } + } + + override public init() { + super.init() + DispatchQueue.main.async {[weak self] in + self?.appState = AppState.shared + } + } + + private func sendBackgrounding() { + backgroundId = sending(message: background) + } + + private func removeBackgrounding() { + UNUserNotificationCenter.current().removeAllPendingNotificationRequests() + UNUserNotificationCenter.current().removeAllDeliveredNotifications() + backgroundId = nil + outstandingIds = nil + } +} + +extension SimpleLocalNotification: NotificationBridgeProtocol { + public func launched() { + } + + public func registered(deviceToken: Data) { + } + + public func failed(error: Error) { + } + + public func received(userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + if let routing = userInfo["routing"] as? String { + Router.shared?.navigate(to: RoutingRequest(url: routing), animated: true, completion: nil) + completionHandler(.newData) + } else { + completionHandler(.noData) + } + } + + public func receivedDeeplink(userInfo: [AnyHashable : Any]) -> URL? { + if let routing = userInfo["routing"] as? String { + return RoutingRequest(url: routing).url + } + return nil + } +} diff --git a/PlatformRouting/PlatformRouting/_iOS/_Transitions/FadeTransition.swift b/PlatformRouting/PlatformRouting/_iOS/_Transitions/FadeTransition.swift new file mode 100644 index 000000000..291263516 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_iOS/_Transitions/FadeTransition.swift @@ -0,0 +1,38 @@ +// +// FadeTransition.swift +// PlatformRouting +// +// Created by Michael Maguire on 3/1/24. +// Copyright © 2024 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +// Animator for custom transition +class FadeInFadeOutAnimator: NSObject, RoutingCustomTransitionable { + var isPresenting = true + let duration = 0.5 + + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return duration + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + let key: UITransitionContextViewControllerKey = isPresenting ? .to : .from + guard let controller = transitionContext.viewController(forKey: key) else { return } + + if isPresenting { + transitionContext.containerView.addSubview(controller.view) + controller.view.alpha = 0 + } + + UIView.animate(withDuration: duration, animations: { + controller.view.alpha = self.isPresenting ? 1 : 0 + }) { _ in + if !self.isPresenting { + controller.view.removeFromSuperview() + } + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + } + } +} diff --git a/PlatformRouting/PlatformRouting/_iOS/_Transitions/TransitionDelegate.swift b/PlatformRouting/PlatformRouting/_iOS/_Transitions/TransitionDelegate.swift new file mode 100644 index 000000000..0ee86d5e4 --- /dev/null +++ b/PlatformRouting/PlatformRouting/_iOS/_Transitions/TransitionDelegate.swift @@ -0,0 +1,44 @@ +// +// TransitionDelegate.swift +// PlatformRouting +// +// Created by Michael Maguire on 3/1/24. +// Copyright © 2024 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +enum CustomTransition { + case fade + + var transitionDelegate: UIViewControllerTransitioningDelegate { + switch self { + case .fade: + CustomTransitionDelegate(transitioner: FadeInFadeOutAnimator()) + } + } +} + +protocol RoutingCustomTransitionable: UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning { + var isPresenting: Bool { get set } + var duration: TimeInterval { get } +} + +private class CustomTransitionDelegate: NSObject, UIViewControllerTransitioningDelegate { + let transitioner: RoutingCustomTransitionable + + public init(transitioner: RoutingCustomTransitionable) { + self.transitioner = transitioner + super.init() + } + + public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + transitioner.isPresenting = true + return transitioner + } + + public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + transitioner.isPresenting = false + return transitioner + } +} diff --git a/PlatformRouting/PlatformRoutingAppleTV/Info.plist b/PlatformRouting/PlatformRoutingAppleTV/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/PlatformRouting/PlatformRoutingAppleTV/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/PlatformRouting/PlatformRoutingAppleTV/PlatformRouting.h b/PlatformRouting/PlatformRoutingAppleTV/PlatformRouting.h new file mode 100644 index 000000000..9172d831c --- /dev/null +++ b/PlatformRouting/PlatformRoutingAppleTV/PlatformRouting.h @@ -0,0 +1,17 @@ +// +// PlatformRoutingAppleTV.h +// PlatformRoutingAppleTV +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for PlatformRoutingAppleTV. +FOUNDATION_EXPORT double PlatformRoutingAppleTVVersionNumber; + +//! Project version string for PlatformRoutingAppleTV. +FOUNDATION_EXPORT const unsigned char PlatformRoutingAppleTVVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/PlatformRouting/PlatformRoutingAppleTVTests/Info.plist b/PlatformRouting/PlatformRoutingAppleTVTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/PlatformRouting/PlatformRoutingAppleTVTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/PlatformRouting/PlatformRoutingAppleTVTests/PlatformRoutingAppleTVTests.swift b/PlatformRouting/PlatformRoutingAppleTVTests/PlatformRoutingAppleTVTests.swift new file mode 100644 index 000000000..3ae600dc1 --- /dev/null +++ b/PlatformRouting/PlatformRoutingAppleTVTests/PlatformRoutingAppleTVTests.swift @@ -0,0 +1,32 @@ +// +// PlatformRoutingAppleTVTests.swift +// PlatformRoutingAppleTVTests +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +@testable import PlatformRouting +import XCTest + +class PlatformRoutingAppleTVTests: XCTestCase { + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. + } + } +} diff --git a/PlatformRouting/PlatformRoutingAppleWatch/AppleWatch/InterfaceController/RoutingInterfaceController.swift b/PlatformRouting/PlatformRoutingAppleWatch/AppleWatch/InterfaceController/RoutingInterfaceController.swift new file mode 100644 index 000000000..a859b909c --- /dev/null +++ b/PlatformRouting/PlatformRoutingAppleWatch/AppleWatch/InterfaceController/RoutingInterfaceController.swift @@ -0,0 +1,49 @@ +// +// RoutingInterfaceController.swift +// RoutingPlatformAppleWatchLib +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import RoutingKit +import WatchKit + +open class RoutingInterfaceController: WKInterfaceController, NavigableProtocol { + open var routingRequest: RoutingRequest? + + open var history: RoutingRequest? { + return routingRequest + } + + open override func awake(withContext context: Any?) { + super.awake(withContext: context) + + if let routingRequest = context as? RoutingRequest { + navigate(to: routingRequest, animated: true, completion: nil) + } + } + + open override func willActivate() { + // This method is called when watch view controller is about to be visible to user + super.willActivate() + } + + open override func didDeactivate() { + // This method is called when watch view controller is no longer visible + super.didDeactivate() + } + + open func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { + return routingRequest?.path == request?.path && (routingRequest?.params as NSDictionary?) == (request?.params as NSDictionary?) + } + + public func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + if arrive(to: request, animated: animated) { + routingRequest = request + completion?(self, true) + } else { + completion?(self, false) + } + } +} diff --git a/PlatformRouting/PlatformRoutingAppleWatch/AppleWatch/Router/MappedAppleWatchRouter.swift b/PlatformRouting/PlatformRoutingAppleWatch/AppleWatch/Router/MappedAppleWatchRouter.swift new file mode 100644 index 000000000..919aba267 --- /dev/null +++ b/PlatformRouting/PlatformRoutingAppleWatch/AppleWatch/Router/MappedAppleWatchRouter.swift @@ -0,0 +1,43 @@ +// +// MappedAppleWatchRouter.swift +// RoutingPlatformAppleWatchLib +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation +import RoutingKit + +open class MappedAppleWatchRouter: MappedRouter { + open override func navigate(to map: RoutingMap, request: RoutingRequest, presentation: RoutingPresentation?, animated: Bool, completion: RoutingCompletionBlock?) { + route(storyboard: map, request: request, presentation: presentation, animated: animated) { object, completed in + completion?(object, completed) + } + } + + private func route(storyboard map: RoutingMap, request: RoutingRequest, presentation: RoutingPresentation?, animated: Bool, completion: RoutingCompletionBlock?) { + route(name: map.destination, request: request, presentation: presentation ?? map.presentation, animated: animated, completion: completion) + } + + private func route(name: String?, request: RoutingRequest, presentation: RoutingPresentation?, animated: Bool, completion: RoutingCompletionBlock?) { + if let topmost = WKExtension.shared().visibleInterfaceController, let name = name { + let presentation = presentation ?? .show + switch presentation { + case .show: + fallthrough + case .detail: + topmost.pushController(withName: name, context: request) + + case .prompt: + topmost.presentController(withName: name, context: request) + + default: + break + } + completion?(nil, true) + } else { + completion?(nil, false) + } + } +} diff --git a/PlatformRouting/PlatformRoutingAppleWatch/ExtensionDelegate/RoutingExtensionDelegate.swift b/PlatformRouting/PlatformRoutingAppleWatch/ExtensionDelegate/RoutingExtensionDelegate.swift new file mode 100644 index 000000000..fe7db3687 --- /dev/null +++ b/PlatformRouting/PlatformRoutingAppleWatch/ExtensionDelegate/RoutingExtensionDelegate.swift @@ -0,0 +1,52 @@ +// +// RoutingExtensionDelegate.swift +// PlatformRoutingAppleWatch +// +// Created by Qiang Huang on 12/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities +import RoutingKit +import UIKit + +open class RoutingExtensionDelegate: NSObject, WKExtensionDelegate { + open func applicationDidFinishLaunching() { + Router.shared = MappedAppleWatchRouter(file:"routing.json") + + routeToStart() + } + + open func routeToStart() { + } + + open func handle(_ backgroundTasks: Set) { + // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one. + for task in backgroundTasks { + // Use a switch statement to check the task type + switch task { + case let backgroundTask as WKApplicationRefreshBackgroundTask: + // Be sure to complete the background task once you’re done. + backgroundTask.setTaskCompletedWithSnapshot(false) + case let snapshotTask as WKSnapshotRefreshBackgroundTask: + // Snapshot tasks have a unique completion call, make sure to set your expiration date + snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil) + case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask: + // Be sure to complete the connectivity task once you’re done. + connectivityTask.setTaskCompletedWithSnapshot(false) + case let urlSessionTask as WKURLSessionRefreshBackgroundTask: + // Be sure to complete the URL session task once you’re done. + urlSessionTask.setTaskCompletedWithSnapshot(false) + case let relevantShortcutTask as WKRelevantShortcutRefreshBackgroundTask: + // Be sure to complete the relevant-shortcut task once you're done. + relevantShortcutTask.setTaskCompletedWithSnapshot(false) + case let intentDidRunTask as WKIntentDidRunRefreshBackgroundTask: + // Be sure to complete the intent-did-run task once you're done. + intentDidRunTask.setTaskCompletedWithSnapshot(false) + default: + // make sure to complete unhandled task types + task.setTaskCompletedWithSnapshot(false) + } + } + } +} diff --git a/PlatformRouting/PlatformRoutingAppleWatch/Info.plist b/PlatformRouting/PlatformRoutingAppleWatch/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/PlatformRouting/PlatformRoutingAppleWatch/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/PlatformRouting/PlatformRoutingAppleWatch/PlatformRouting.h b/PlatformRouting/PlatformRoutingAppleWatch/PlatformRouting.h new file mode 100644 index 000000000..678d68db5 --- /dev/null +++ b/PlatformRouting/PlatformRoutingAppleWatch/PlatformRouting.h @@ -0,0 +1,19 @@ +// +// PlatformRoutingAppleWatch.h +// PlatformRoutingAppleWatch +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for PlatformRoutingAppleWatch. +FOUNDATION_EXPORT double PlatformRoutingAppleWatchVersionNumber; + +//! Project version string for PlatformRoutingAppleWatch. +FOUNDATION_EXPORT const unsigned char PlatformRoutingAppleWatchVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/PlatformRouting/PlatformRoutingTests/Info.plist b/PlatformRouting/PlatformRoutingTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/PlatformRouting/PlatformRoutingTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/PlatformRouting/PlatformRoutingTests/PlatformRoutingTests.swift b/PlatformRouting/PlatformRoutingTests/PlatformRoutingTests.swift new file mode 100644 index 000000000..4ab05a3fe --- /dev/null +++ b/PlatformRouting/PlatformRoutingTests/PlatformRoutingTests.swift @@ -0,0 +1,34 @@ +// +// PlatformRoutingTests.swift +// PlatformRoutingTests +// +// Created by Qiang Huang on 10/12/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import XCTest +@testable import PlatformRouting + +class PlatformRoutingTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/PlatformUI/PlatformUI.xcodeproj/project.pbxproj b/PlatformUI/PlatformUI.xcodeproj/project.pbxproj new file mode 100644 index 000000000..bea8ca59c --- /dev/null +++ b/PlatformUI/PlatformUI.xcodeproj/project.pbxproj @@ -0,0 +1,989 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 0230377828C15C0E00412B72 /* PlatformViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0230377728C15C0E00412B72 /* PlatformViewModel.swift */; }; + 023788F528B9924D00F212E1 /* PlatformButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023788F428B9924D00F212E1 /* PlatformButton.swift */; }; + 0242E3E92A9B1AE1007605F9 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0242E3E82A9B1AE1007605F9 /* Media.xcassets */; }; + 0243A73729BB8DBB00A083FE /* PlatformListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0243A73629BB8DBB00A083FE /* PlatformListViewModel.swift */; }; + 0243A73E29BE2D7C00A083FE /* Divider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0243A73D29BE2D7C00A083FE /* Divider.swift */; }; + 024B794528B6A6A200F7C386 /* PlatformIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024B794428B6A6A200F7C386 /* PlatformIcon.swift */; }; + 024B794B28B6AC1A00F7C386 /* PlatformOverlayIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024B794A28B6AC1A00F7C386 /* PlatformOverlayIcon.swift */; }; + 024B795028B6D95D00F7C386 /* SignedAmount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024B794F28B6D95D00F7C386 /* SignedAmount.swift */; }; + 0258B9EE2991C9FF0098E1BE /* PlatformWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0258B9ED2991C9FF0098E1BE /* PlatformWebView.swift */; }; + 02714C8C29E0C3AD00CC1C44 /* ComboBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02714C8B29E0C3AD00CC1C44 /* ComboBox.swift */; }; + 0273A32D2ACDFFAC001B89F5 /* ThemeFontCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0273A32C2ACDFFAC001B89F5 /* ThemeFontCache.swift */; }; + 0273A3332ACE06EE001B89F5 /* ThemeColorCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0273A3322ACE06EE001B89F5 /* ThemeColorCache.swift */; }; + 0278DD0E2A7C7A1400FE6ABE /* PlatformViewModel+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0278DD0D2A7C7A1400FE6ABE /* PlatformViewModel+Ext.swift */; }; + 027A7B45291E090F00DF402D /* ColoredText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027A7B44291E090F00DF402D /* ColoredText.swift */; }; + 02ABDAF028D9150A00728C54 /* PlatformInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02ABDAEF28D9150A00728C54 /* PlatformInput.swift */; }; + 02B120B528A430DF00281498 /* ThemeViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B120B428A430DF00281498 /* ThemeViewModifiers.swift */; }; + 02D1342F28EB68FC00B46941 /* TabGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D1342E28EB68FC00B46941 /* TabGroup.swift */; }; + 02DE889C28A2C91B00728FF3 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02DE889128A2C91200728FF3 /* Utilities.framework */; }; + 02DE88F828A2DB9C00728FF3 /* PlatformUIBundleClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE88F728A2DB9C00728FF3 /* PlatformUIBundleClass.swift */; }; + 02DE89A628A2F10A00728FF3 /* SampleThemeInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02DE89A528A2F10A00728FF3 /* SampleThemeInput.swift */; }; + 02E2C93028A1C8A400F7C3BE /* PlatformUI.docc in Sources */ = {isa = PBXBuildFile; fileRef = 02E2C92F28A1C8A400F7C3BE /* PlatformUI.docc */; }; + 02E2C93628A1C8A400F7C3BE /* PlatformUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02E2C92B28A1C8A400F7C3BE /* PlatformUI.framework */; }; + 02E2C93B28A1C8A400F7C3BE /* PlatformUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E2C93A28A1C8A400F7C3BE /* PlatformUITests.swift */; }; + 02E2C93C28A1C8A400F7C3BE /* PlatformUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 02E2C92E28A1C8A400F7C3BE /* PlatformUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 02E2C9A028A1C98700F7C3BE /* SampleTheme.json in Resources */ = {isa = PBXBuildFile; fileRef = 02E2C99F28A1C98700F7C3BE /* SampleTheme.json */; }; + 02E2C9A228A1D0B600F7C3BE /* ThemeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E2C9A128A1D0B600F7C3BE /* ThemeConfig.swift */; }; + 02E2C9C128A2C22B00F7C3BE /* SampleThemeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E2C9C028A2C22B00F7C3BE /* SampleThemeLabel.swift */; }; + 02F16FD928B47E2D0085DC58 /* PlatformTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F16FD828B47E2D0085DC58 /* PlatformTableViewCell.swift */; }; + 02F16FDB28B491EE0085DC58 /* PlatformUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F16FDA28B491EE0085DC58 /* PlatformUI.swift */; }; + 02F16FDF28B539850085DC58 /* SampleStyle.json in Resources */ = {isa = PBXBuildFile; fileRef = 02F16FDE28B539850085DC58 /* SampleStyle.json */; }; + 02F16FE228B53A200085DC58 /* SampleStyleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F16FE128B53A200085DC58 /* SampleStyleLabel.swift */; }; + 02F38BF22A9AAE3700969E06 /* CircularProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F38BF12A9AAE3700969E06 /* CircularProgressView.swift */; }; + 1C811E336064517E256D1290 /* Pods_iOS_PlatformUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6793D8074DF2E7FE4592138 /* Pods_iOS_PlatformUITests.framework */; }; + 27044F882BBB2ADF004C750D /* Text+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27044F872BBB2ADF004C750D /* Text+Ext.swift */; }; + 274C47EA2C0FC6CF000212C3 /* EdgeInsets+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274C47E92C0FC6CF000212C3 /* EdgeInsets+Ext.swift */; }; + 276581F82C139F36009E072A /* SingleAxisGeometryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 276581F72C139F36009E072A /* SingleAxisGeometryReader.swift */; }; + 277E7AC62BBF3BE8009F95DE /* InlineAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277E7AC52BBF3BE8009F95DE /* InlineAlert.swift */; }; + 27E6A7322AB8D5F600026CB5 /* SwipeActionsViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E6A7312AB8D5F600026CB5 /* SwipeActionsViewModifier.swift */; }; + 6488BBDC296F6AEA0096502F /* TabItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6488BBDB296F6AEA0096502F /* TabItemViewModel.swift */; }; + 64A4DC5F29677BCB008D8E20 /* PlatformOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A4DC5E29677BCB008D8E20 /* PlatformOutput.swift */; }; + CEE5B6E4D97A1CD73E8B2718 /* Pods_iOS_PlatformUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 418D5C02425B3C680BF32DA4 /* Pods_iOS_PlatformUI.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 02DE889028A2C91200728FF3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02DE888828A2C91200728FF3 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 02DE889228A2C91200728FF3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02DE888828A2C91200728FF3 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 02DE889428A2C91200728FF3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02DE888828A2C91200728FF3 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 02DE889628A2C91200728FF3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02DE888828A2C91200728FF3 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 02DE889828A2C91200728FF3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02DE888828A2C91200728FF3 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 02DE889A28A2C91500728FF3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02DE888828A2C91200728FF3 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 02E2C93728A1C8A400F7C3BE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02E2C92228A1C8A400F7C3BE /* Project object */; + proxyType = 1; + remoteGlobalIDString = 02E2C92A28A1C8A400F7C3BE; + remoteInfo = PlatformUI; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0230377728C15C0E00412B72 /* PlatformViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformViewModel.swift; sourceTree = ""; }; + 023788F428B9924D00F212E1 /* PlatformButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformButton.swift; sourceTree = ""; }; + 0242E3E82A9B1AE1007605F9 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; + 0243A73629BB8DBB00A083FE /* PlatformListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformListViewModel.swift; sourceTree = ""; }; + 0243A73D29BE2D7C00A083FE /* Divider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Divider.swift; sourceTree = ""; }; + 024B794428B6A6A200F7C386 /* PlatformIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformIcon.swift; sourceTree = ""; }; + 024B794A28B6AC1A00F7C386 /* PlatformOverlayIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformOverlayIcon.swift; sourceTree = ""; }; + 024B794F28B6D95D00F7C386 /* SignedAmount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignedAmount.swift; sourceTree = ""; }; + 0258B9ED2991C9FF0098E1BE /* PlatformWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformWebView.swift; sourceTree = ""; }; + 02714C8B29E0C3AD00CC1C44 /* ComboBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComboBox.swift; sourceTree = ""; }; + 0273A32C2ACDFFAC001B89F5 /* ThemeFontCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeFontCache.swift; sourceTree = ""; }; + 0273A3322ACE06EE001B89F5 /* ThemeColorCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeColorCache.swift; sourceTree = ""; }; + 0278DD0D2A7C7A1400FE6ABE /* PlatformViewModel+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlatformViewModel+Ext.swift"; sourceTree = ""; }; + 027A7B44291E090F00DF402D /* ColoredText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoredText.swift; sourceTree = ""; }; + 02ABDAEF28D9150A00728C54 /* PlatformInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformInput.swift; sourceTree = ""; }; + 02B120B428A430DF00281498 /* ThemeViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeViewModifiers.swift; sourceTree = ""; }; + 02D1342E28EB68FC00B46941 /* TabGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabGroup.swift; sourceTree = ""; }; + 02DE888828A2C91200728FF3 /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 02DE88F728A2DB9C00728FF3 /* PlatformUIBundleClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformUIBundleClass.swift; sourceTree = ""; }; + 02DE89A528A2F10A00728FF3 /* SampleThemeInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleThemeInput.swift; sourceTree = ""; }; + 02E2C92B28A1C8A400F7C3BE /* PlatformUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlatformUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 02E2C92E28A1C8A400F7C3BE /* PlatformUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformUI.h; sourceTree = ""; }; + 02E2C92F28A1C8A400F7C3BE /* PlatformUI.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = PlatformUI.docc; sourceTree = ""; }; + 02E2C93528A1C8A400F7C3BE /* PlatformUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PlatformUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 02E2C93A28A1C8A400F7C3BE /* PlatformUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformUITests.swift; sourceTree = ""; }; + 02E2C99F28A1C98700F7C3BE /* SampleTheme.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SampleTheme.json; sourceTree = ""; }; + 02E2C9A128A1D0B600F7C3BE /* ThemeConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeConfig.swift; sourceTree = ""; }; + 02E2C9C028A2C22B00F7C3BE /* SampleThemeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleThemeLabel.swift; sourceTree = ""; }; + 02F16FD828B47E2D0085DC58 /* PlatformTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTableViewCell.swift; sourceTree = ""; }; + 02F16FDA28B491EE0085DC58 /* PlatformUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformUI.swift; sourceTree = ""; }; + 02F16FDE28B539850085DC58 /* SampleStyle.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SampleStyle.json; sourceTree = ""; }; + 02F16FE128B53A200085DC58 /* SampleStyleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleStyleLabel.swift; sourceTree = ""; }; + 02F38BF12A9AAE3700969E06 /* CircularProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressView.swift; sourceTree = ""; }; + 27044F872BBB2ADF004C750D /* Text+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Text+Ext.swift"; sourceTree = ""; }; + 274C47E92C0FC6CF000212C3 /* EdgeInsets+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EdgeInsets+Ext.swift"; sourceTree = ""; }; + 276581F72C139F36009E072A /* SingleAxisGeometryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleAxisGeometryReader.swift; sourceTree = ""; }; + 277E7AC52BBF3BE8009F95DE /* InlineAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineAlert.swift; sourceTree = ""; }; + 27E6A7312AB8D5F600026CB5 /* SwipeActionsViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeActionsViewModifier.swift; sourceTree = ""; }; + 366BD14FE1ED4F2AF21D924E /* Pods-iOS-PlatformUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformUI.release.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformUI/Pods-iOS-PlatformUI.release.xcconfig"; sourceTree = ""; }; + 418D5C02425B3C680BF32DA4 /* Pods_iOS_PlatformUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_PlatformUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6488BBDB296F6AEA0096502F /* TabItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabItemViewModel.swift; sourceTree = ""; }; + 64A4DC5E29677BCB008D8E20 /* PlatformOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformOutput.swift; sourceTree = ""; }; + 7900D7C9D4E7016DDF14E98A /* Pods-iOS-PlatformUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformUITests.release.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformUITests/Pods-iOS-PlatformUITests.release.xcconfig"; sourceTree = ""; }; + C889AD4B581381A925B00E9D /* Pods-iOS-PlatformUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformUI.debug.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformUI/Pods-iOS-PlatformUI.debug.xcconfig"; sourceTree = ""; }; + F6793D8074DF2E7FE4592138 /* Pods_iOS_PlatformUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_PlatformUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FAAE8CE674BDAAFF34EB30CA /* Pods-iOS-PlatformUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformUITests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformUITests/Pods-iOS-PlatformUITests.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 02E2C92828A1C8A400F7C3BE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 02DE889C28A2C91B00728FF3 /* Utilities.framework in Frameworks */, + CEE5B6E4D97A1CD73E8B2718 /* Pods_iOS_PlatformUI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 02E2C93228A1C8A400F7C3BE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 02E2C93628A1C8A400F7C3BE /* PlatformUI.framework in Frameworks */, + 1C811E336064517E256D1290 /* Pods_iOS_PlatformUITests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 023788EF28B991F100F212E1 /* Buttons */ = { + isa = PBXGroup; + children = ( + 023788F428B9924D00F212E1 /* PlatformButton.swift */, + ); + path = Buttons; + sourceTree = ""; + }; + 0243A73C29BE2D6700A083FE /* Divider */ = { + isa = PBXGroup; + children = ( + 0243A73D29BE2D7C00A083FE /* Divider.swift */, + ); + path = Divider; + sourceTree = ""; + }; + 024B794C28B6D73D00F7C386 /* Cells */ = { + isa = PBXGroup; + children = ( + 02F16FD828B47E2D0085DC58 /* PlatformTableViewCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 024B794D28B6D74400F7C386 /* Icons */ = { + isa = PBXGroup; + children = ( + 024B794428B6A6A200F7C386 /* PlatformIcon.swift */, + 024B794A28B6AC1A00F7C386 /* PlatformOverlayIcon.swift */, + ); + path = Icons; + sourceTree = ""; + }; + 024B794E28B6D74C00F7C386 /* Labels */ = { + isa = PBXGroup; + children = ( + 024B794F28B6D95D00F7C386 /* SignedAmount.swift */, + 027A7B44291E090F00DF402D /* ColoredText.swift */, + 277E7AC52BBF3BE8009F95DE /* InlineAlert.swift */, + ); + path = Labels; + sourceTree = ""; + }; + 0258B9E82991C9D10098E1BE /* WebView */ = { + isa = PBXGroup; + children = ( + 0258B9ED2991C9FF0098E1BE /* PlatformWebView.swift */, + ); + path = WebView; + sourceTree = ""; + }; + 025B844028AACBDE0037FF7C /* Components */ = { + isa = PBXGroup; + children = ( + 276581F22C139F27009E072A /* Containers */, + 27044F862BBB2ACB004C750D /* Extensions */, + 27E6A72C2AB8D5B500026CB5 /* Utility */, + 02F38BEC2A9AAE0800969E06 /* CircularProgressView */, + 02714C8629E0C39D00CC1C44 /* ComboBox */, + 0243A73C29BE2D6700A083FE /* Divider */, + 0258B9E82991C9D10098E1BE /* WebView */, + 02D1342928EB68BE00B46941 /* TabGroup */, + 02ABDAE828D914E200728C54 /* Input */, + 023788EF28B991F100F212E1 /* Buttons */, + 024B794E28B6D74C00F7C386 /* Labels */, + 024B794D28B6D74400F7C386 /* Icons */, + 024B794C28B6D73D00F7C386 /* Cells */, + ); + path = Components; + sourceTree = ""; + }; + 02684F2828BD41AB0007CEFF /* Dependencies */ = { + isa = PBXGroup; + children = ( + 02DE888828A2C91200728FF3 /* Utilities.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 02714C8629E0C39D00CC1C44 /* ComboBox */ = { + isa = PBXGroup; + children = ( + 02714C8B29E0C3AD00CC1C44 /* ComboBox.swift */, + ); + path = ComboBox; + sourceTree = ""; + }; + 02ABDAE828D914E200728C54 /* Input */ = { + isa = PBXGroup; + children = ( + 02ABDAEF28D9150A00728C54 /* PlatformInput.swift */, + 64A4DC5E29677BCB008D8E20 /* PlatformOutput.swift */, + ); + path = Input; + sourceTree = ""; + }; + 02D1342928EB68BE00B46941 /* TabGroup */ = { + isa = PBXGroup; + children = ( + 02D1342E28EB68FC00B46941 /* TabGroup.swift */, + 6488BBDB296F6AEA0096502F /* TabItemViewModel.swift */, + ); + path = TabGroup; + sourceTree = ""; + }; + 02DE888928A2C91200728FF3 /* Products */ = { + isa = PBXGroup; + children = ( + 02DE889128A2C91200728FF3 /* Utilities.framework */, + 02DE889328A2C91200728FF3 /* Utilities.framework */, + 02DE889528A2C91200728FF3 /* Utilities.framework */, + 02DE889728A2C91200728FF3 /* UtilitiesTests.xctest */, + 02DE889928A2C91200728FF3 /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 02E2C92128A1C8A400F7C3BE = { + isa = PBXGroup; + children = ( + 02684F2828BD41AB0007CEFF /* Dependencies */, + 02E2C92D28A1C8A400F7C3BE /* PlatformUI */, + 02E2C93928A1C8A400F7C3BE /* PlatformUITests */, + 02E2C92C28A1C8A400F7C3BE /* Products */, + 50951D95B8B38B407429BEF2 /* Pods */, + E54678776BE37222580E0427 /* Frameworks */, + ); + sourceTree = ""; + }; + 02E2C92C28A1C8A400F7C3BE /* Products */ = { + isa = PBXGroup; + children = ( + 02E2C92B28A1C8A400F7C3BE /* PlatformUI.framework */, + 02E2C93528A1C8A400F7C3BE /* PlatformUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 02E2C92D28A1C8A400F7C3BE /* PlatformUI */ = { + isa = PBXGroup; + children = ( + 02F16FDA28B491EE0085DC58 /* PlatformUI.swift */, + 0230377728C15C0E00412B72 /* PlatformViewModel.swift */, + 0278DD0D2A7C7A1400FE6ABE /* PlatformViewModel+Ext.swift */, + 0243A73629BB8DBB00A083FE /* PlatformListViewModel.swift */, + 025B844028AACBDE0037FF7C /* Components */, + 02E2C99E28A1C96800F7C3BE /* DesignSystem */, + 02E2C92E28A1C8A400F7C3BE /* PlatformUI.h */, + 02E2C92F28A1C8A400F7C3BE /* PlatformUI.docc */, + 02DE88F728A2DB9C00728FF3 /* PlatformUIBundleClass.swift */, + 0242E3E82A9B1AE1007605F9 /* Media.xcassets */, + ); + path = PlatformUI; + sourceTree = ""; + }; + 02E2C93928A1C8A400F7C3BE /* PlatformUITests */ = { + isa = PBXGroup; + children = ( + 02E2C93A28A1C8A400F7C3BE /* PlatformUITests.swift */, + ); + path = PlatformUITests; + sourceTree = ""; + }; + 02E2C99E28A1C96800F7C3BE /* DesignSystem */ = { + isa = PBXGroup; + children = ( + 02E2C9A328A1D5E900F7C3BE /* Theme */, + ); + path = DesignSystem; + sourceTree = ""; + }; + 02E2C9A328A1D5E900F7C3BE /* Theme */ = { + isa = PBXGroup; + children = ( + 02E2C9BF28A2C20800F7C3BE /* Samples */, + 02E2C99F28A1C98700F7C3BE /* SampleTheme.json */, + 02F16FDE28B539850085DC58 /* SampleStyle.json */, + 02E2C9A128A1D0B600F7C3BE /* ThemeConfig.swift */, + 0273A3322ACE06EE001B89F5 /* ThemeColorCache.swift */, + 0273A32C2ACDFFAC001B89F5 /* ThemeFontCache.swift */, + 02B120B428A430DF00281498 /* ThemeViewModifiers.swift */, + ); + path = Theme; + sourceTree = ""; + }; + 02E2C9BF28A2C20800F7C3BE /* Samples */ = { + isa = PBXGroup; + children = ( + 02F16FE128B53A200085DC58 /* SampleStyleLabel.swift */, + 02E2C9C028A2C22B00F7C3BE /* SampleThemeLabel.swift */, + 02DE89A528A2F10A00728FF3 /* SampleThemeInput.swift */, + ); + path = Samples; + sourceTree = ""; + }; + 02F38BEC2A9AAE0800969E06 /* CircularProgressView */ = { + isa = PBXGroup; + children = ( + 02F38BF12A9AAE3700969E06 /* CircularProgressView.swift */, + ); + path = CircularProgressView; + sourceTree = ""; + }; + 27044F862BBB2ACB004C750D /* Extensions */ = { + isa = PBXGroup; + children = ( + 27044F872BBB2ADF004C750D /* Text+Ext.swift */, + 274C47E92C0FC6CF000212C3 /* EdgeInsets+Ext.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 276581F22C139F27009E072A /* Containers */ = { + isa = PBXGroup; + children = ( + 276581F72C139F36009E072A /* SingleAxisGeometryReader.swift */, + ); + path = Containers; + sourceTree = ""; + }; + 27E6A72C2AB8D5B500026CB5 /* Utility */ = { + isa = PBXGroup; + children = ( + 27E6A7312AB8D5F600026CB5 /* SwipeActionsViewModifier.swift */, + ); + path = Utility; + sourceTree = ""; + }; + 50951D95B8B38B407429BEF2 /* Pods */ = { + isa = PBXGroup; + children = ( + C889AD4B581381A925B00E9D /* Pods-iOS-PlatformUI.debug.xcconfig */, + 366BD14FE1ED4F2AF21D924E /* Pods-iOS-PlatformUI.release.xcconfig */, + FAAE8CE674BDAAFF34EB30CA /* Pods-iOS-PlatformUITests.debug.xcconfig */, + 7900D7C9D4E7016DDF14E98A /* Pods-iOS-PlatformUITests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; + E54678776BE37222580E0427 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 418D5C02425B3C680BF32DA4 /* Pods_iOS_PlatformUI.framework */, + F6793D8074DF2E7FE4592138 /* Pods_iOS_PlatformUITests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 02E2C92628A1C8A400F7C3BE /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 02E2C93C28A1C8A400F7C3BE /* PlatformUI.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 02E2C92A28A1C8A400F7C3BE /* PlatformUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = 02E2C93F28A1C8A400F7C3BE /* Build configuration list for PBXNativeTarget "PlatformUI" */; + buildPhases = ( + D8FB6CF93871A4AFE500D3BF /* [CP] Check Pods Manifest.lock */, + 02E2C92628A1C8A400F7C3BE /* Headers */, + 02E2C92728A1C8A400F7C3BE /* Sources */, + 02E2C92828A1C8A400F7C3BE /* Frameworks */, + 02E2C92928A1C8A400F7C3BE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 02DE889B28A2C91500728FF3 /* PBXTargetDependency */, + ); + name = PlatformUI; + packageProductDependencies = ( + ); + productName = PlatformUI; + productReference = 02E2C92B28A1C8A400F7C3BE /* PlatformUI.framework */; + productType = "com.apple.product-type.framework"; + }; + 02E2C93428A1C8A400F7C3BE /* PlatformUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 02E2C94228A1C8A400F7C3BE /* Build configuration list for PBXNativeTarget "PlatformUITests" */; + buildPhases = ( + 74961D630C239663B2CD100A /* [CP] Check Pods Manifest.lock */, + 02E2C93128A1C8A400F7C3BE /* Sources */, + 02E2C93228A1C8A400F7C3BE /* Frameworks */, + 02E2C93328A1C8A400F7C3BE /* Resources */, + B2CF303B6AF214DBE22D1F40 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 02E2C93828A1C8A400F7C3BE /* PBXTargetDependency */, + ); + name = PlatformUITests; + productName = PlatformUITests; + productReference = 02E2C93528A1C8A400F7C3BE /* PlatformUITests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 02E2C92228A1C8A400F7C3BE /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1330; + LastUpgradeCheck = 1330; + TargetAttributes = { + 02E2C92A28A1C8A400F7C3BE = { + CreatedOnToolsVersion = 13.3.1; + }; + 02E2C93428A1C8A400F7C3BE = { + CreatedOnToolsVersion = 13.3.1; + }; + }; + }; + buildConfigurationList = 02E2C92528A1C8A400F7C3BE /* Build configuration list for PBXProject "PlatformUI" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 02E2C92128A1C8A400F7C3BE; + packageReferences = ( + ); + productRefGroup = 02E2C92C28A1C8A400F7C3BE /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 02DE888928A2C91200728FF3 /* Products */; + ProjectRef = 02DE888828A2C91200728FF3 /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 02E2C92A28A1C8A400F7C3BE /* PlatformUI */, + 02E2C93428A1C8A400F7C3BE /* PlatformUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 02DE889128A2C91200728FF3 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 02DE889028A2C91200728FF3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02DE889328A2C91200728FF3 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 02DE889228A2C91200728FF3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02DE889528A2C91200728FF3 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 02DE889428A2C91200728FF3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02DE889728A2C91200728FF3 /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 02DE889628A2C91200728FF3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02DE889928A2C91200728FF3 /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 02DE889828A2C91200728FF3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 02E2C92928A1C8A400F7C3BE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 02F16FDF28B539850085DC58 /* SampleStyle.json in Resources */, + 0242E3E92A9B1AE1007605F9 /* Media.xcassets in Resources */, + 02E2C9A028A1C98700F7C3BE /* SampleTheme.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 02E2C93328A1C8A400F7C3BE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 74961D630C239663B2CD100A /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-PlatformUITests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B2CF303B6AF214DBE22D1F40 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-PlatformUITests/Pods-iOS-PlatformUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-PlatformUITests/Pods-iOS-PlatformUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-PlatformUITests/Pods-iOS-PlatformUITests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + D8FB6CF93871A4AFE500D3BF /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-PlatformUI-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 02E2C92728A1C8A400F7C3BE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0273A32D2ACDFFAC001B89F5 /* ThemeFontCache.swift in Sources */, + 02714C8C29E0C3AD00CC1C44 /* ComboBox.swift in Sources */, + 02F16FE228B53A200085DC58 /* SampleStyleLabel.swift in Sources */, + 6488BBDC296F6AEA0096502F /* TabItemViewModel.swift in Sources */, + 27044F882BBB2ADF004C750D /* Text+Ext.swift in Sources */, + 0258B9EE2991C9FF0098E1BE /* PlatformWebView.swift in Sources */, + 274C47EA2C0FC6CF000212C3 /* EdgeInsets+Ext.swift in Sources */, + 02F38BF22A9AAE3700969E06 /* CircularProgressView.swift in Sources */, + 02E2C93028A1C8A400F7C3BE /* PlatformUI.docc in Sources */, + 02B120B528A430DF00281498 /* ThemeViewModifiers.swift in Sources */, + 024B795028B6D95D00F7C386 /* SignedAmount.swift in Sources */, + 02DE88F828A2DB9C00728FF3 /* PlatformUIBundleClass.swift in Sources */, + 02F16FD928B47E2D0085DC58 /* PlatformTableViewCell.swift in Sources */, + 024B794528B6A6A200F7C386 /* PlatformIcon.swift in Sources */, + 0230377828C15C0E00412B72 /* PlatformViewModel.swift in Sources */, + 276581F82C139F36009E072A /* SingleAxisGeometryReader.swift in Sources */, + 0278DD0E2A7C7A1400FE6ABE /* PlatformViewModel+Ext.swift in Sources */, + 0273A3332ACE06EE001B89F5 /* ThemeColorCache.swift in Sources */, + 02ABDAF028D9150A00728C54 /* PlatformInput.swift in Sources */, + 02DE89A628A2F10A00728FF3 /* SampleThemeInput.swift in Sources */, + 023788F528B9924D00F212E1 /* PlatformButton.swift in Sources */, + 02E2C9C128A2C22B00F7C3BE /* SampleThemeLabel.swift in Sources */, + 0243A73729BB8DBB00A083FE /* PlatformListViewModel.swift in Sources */, + 277E7AC62BBF3BE8009F95DE /* InlineAlert.swift in Sources */, + 02F16FDB28B491EE0085DC58 /* PlatformUI.swift in Sources */, + 64A4DC5F29677BCB008D8E20 /* PlatformOutput.swift in Sources */, + 0243A73E29BE2D7C00A083FE /* Divider.swift in Sources */, + 02D1342F28EB68FC00B46941 /* TabGroup.swift in Sources */, + 02E2C9A228A1D0B600F7C3BE /* ThemeConfig.swift in Sources */, + 024B794B28B6AC1A00F7C386 /* PlatformOverlayIcon.swift in Sources */, + 27E6A7322AB8D5F600026CB5 /* SwipeActionsViewModifier.swift in Sources */, + 027A7B45291E090F00DF402D /* ColoredText.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 02E2C93128A1C8A400F7C3BE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 02E2C93B28A1C8A400F7C3BE /* PlatformUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 02DE889B28A2C91500728FF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 02DE889A28A2C91500728FF3 /* PBXContainerItemProxy */; + }; + 02E2C93828A1C8A400F7C3BE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 02E2C92A28A1C8A400F7C3BE /* PlatformUI */; + targetProxy = 02E2C93728A1C8A400F7C3BE /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 02E2C93D28A1C8A400F7C3BE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 02E2C93E28A1C8A400F7C3BE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 02E2C94028A1C8A400F7C3BE /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C889AD4B581381A925B00E9D /* Pods-iOS-PlatformUI.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 75C6UARB5H; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + "\"${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/CryptoSwift\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/KVOController\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNCryptor\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSwiftUI\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SVGKit\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SimpleKeychain\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Starscream\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Validator\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/libPhoneNumber-iOS\"", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.PlatformUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 02E2C94128A1C8A400F7C3BE /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 366BD14FE1ED4F2AF21D924E /* Pods-iOS-PlatformUI.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 75C6UARB5H; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + "\"${PODS_CONFIGURATION_BUILD_DIR}/CocoaLumberjack\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/CryptoSwift\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/KVOController\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNCryptor\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSVGKitPlugin\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImageSwiftUI\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SVGKit\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SimpleKeychain\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Starscream\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Validator\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/libPhoneNumber-iOS\"", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.PlatformUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 02E2C94328A1C8A400F7C3BE /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FAAE8CE674BDAAFF34EB30CA /* Pods-iOS-PlatformUITests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 75C6UARB5H; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.PlatformUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 02E2C94428A1C8A400F7C3BE /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7900D7C9D4E7016DDF14E98A /* Pods-iOS-PlatformUITests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 75C6UARB5H; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.PlatformUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 02E2C92528A1C8A400F7C3BE /* Build configuration list for PBXProject "PlatformUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 02E2C93D28A1C8A400F7C3BE /* Debug */, + 02E2C93E28A1C8A400F7C3BE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 02E2C93F28A1C8A400F7C3BE /* Build configuration list for PBXNativeTarget "PlatformUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 02E2C94028A1C8A400F7C3BE /* Debug */, + 02E2C94128A1C8A400F7C3BE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 02E2C94228A1C8A400F7C3BE /* Build configuration list for PBXNativeTarget "PlatformUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 02E2C94328A1C8A400F7C3BE /* Debug */, + 02E2C94428A1C8A400F7C3BE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 02E2C92228A1C8A400F7C3BE /* Project object */; +} diff --git a/PlatformUI/PlatformUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/PlatformUI/PlatformUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/PlatformUI/PlatformUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/PlatformUI/PlatformUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PlatformUI/PlatformUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/PlatformUI/PlatformUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/PlatformUI/PlatformUI.xcodeproj/xcshareddata/xcschemes/PlatformUI.xcscheme b/PlatformUI/PlatformUI.xcodeproj/xcshareddata/xcschemes/PlatformUI.xcscheme new file mode 100644 index 000000000..c2d4b2855 --- /dev/null +++ b/PlatformUI/PlatformUI.xcodeproj/xcshareddata/xcschemes/PlatformUI.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformUI/PlatformUI.xcodeproj/xcshareddata/xcschemes/PlatformUITests.xcscheme b/PlatformUI/PlatformUI.xcodeproj/xcshareddata/xcschemes/PlatformUITests.xcscheme new file mode 100644 index 000000000..0f02e358a --- /dev/null +++ b/PlatformUI/PlatformUI.xcodeproj/xcshareddata/xcschemes/PlatformUITests.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformUI/PlatformUI/Components/Buttons/PlatformButton.swift b/PlatformUI/PlatformUI/Components/Buttons/PlatformButton.swift new file mode 100644 index 000000000..a0b22a407 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Buttons/PlatformButton.swift @@ -0,0 +1,181 @@ +// +// PlatformButton.swift +// PlatformUI +// +// Created by Rui Huang on 8/26/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI + +public enum PlatformButtonState { + case primary, secondary, disabled, destructive +} + +public enum PlatformButtonType { + case defaultType(fillWidth: Bool = true, padding: EdgeInsets = .init(all: 14)), iconType, pill, small +} + +public class PlatformButtonViewModel: PlatformViewModel { + @Published public var action: () -> () + @Published public var content: Content + @Published public var type: PlatformButtonType + @Published public var state: PlatformButtonState + + public init(content: Content, + type: PlatformButtonType = .defaultType(), + state: PlatformButtonState = .primary, + action: @escaping () -> ()) { + self.content = content + self.type = type + self.state = state + self.action = action + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + let disabled = .disabled == self.state + return AnyView( + Group { + switch self.type { + case .defaultType(let fillWidth, let padding): + let button = Button(action: self.action) { + HStack { + if fillWidth { + Spacer() + } + self.content + .createView(parentStyle: style.themeFont(fontType: .plus), styleKey: self.buttonStyleKey) + if fillWidth { + Spacer() + } + } + } + .buttonStyle(BorderlessButtonStyle()) + .disabled(disabled) + .padding(padding) + .if(fillWidth) { view in + view.frame(maxWidth: .infinity) + } + .themeStyle(styleKey: self.buttonStyleKey, parentStyle: style) + .cornerRadius(8) + + let borderWidth: CGFloat = 1 + let cornerRadius: CGFloat = 8 + switch self.state { + case .primary: + button + case .destructive: + button + .border(borderWidth: borderWidth, cornerRadius: cornerRadius, borderColor: ThemeColor.SemanticColor.colorFadedRed.color) + case .secondary: + button + .border(borderWidth: borderWidth, cornerRadius: cornerRadius, borderColor: ThemeColor.SemanticColor.layer7.color) + case .disabled: + button + .border(borderWidth: borderWidth, cornerRadius: cornerRadius, borderColor: ThemeColor.SemanticColor.layer6.color) + } + + case .small: + Button(action: self.action) { + self.content + .createView(parentStyle: style, styleKey: self.buttonStyleKey) + } + .buttonStyle(BorderlessButtonStyle()) + .disabled(disabled) + .padding(9) + .themeStyle(styleKey: self.buttonStyleKey, parentStyle: style) + .cornerRadius(6) + + case .iconType: + Button(action: self.action) { + self.content.createView(parentStyle: style, styleKey: nil) + } + .buttonStyle(BorderlessButtonStyle()) + .disabled(disabled) + + case .pill: + Button(action: self.action) { + self.content.createView(parentStyle: style, styleKey: nil) + } + .buttonStyle(BorderlessButtonStyle()) + .disabled(disabled) + .padding([.bottom, .top], 4) + .padding([.leading, .trailing], 12) + .themeStyle(styleKey: self.buttonStyleKey, parentStyle: style) + .clipShape(Capsule()) + } + } + ) + } + } + + private var buttonStyleKey: String { + switch state { + case .primary: + return "button-primary" + case .secondary: + return "button-secondary" + case .disabled: + return "button-disabled" + case .destructive: + return "button-destructive" + } + } +} + +#if DEBUG +struct PlatformButton_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + Group { + PlatformButtonViewModel(content: PlatformViewModel() { _ in AnyView(Text("Primary")) }, + state: .primary) {} + .createView() + .previewLayout(.sizeThatFits) + + PlatformButtonViewModel(content: PlatformViewModel() { _ in AnyView(Text("Secondary")) }, + state: .secondary) {} + .createView() + .previewLayout(.sizeThatFits) + + PlatformButtonViewModel(content: PlatformViewModel() { _ in AnyView(Text("Disabled")) }, + state: .disabled) {} + .createView() + .previewLayout(.sizeThatFits) + + PlatformButtonViewModel(content: PlatformViewModel() { _ in AnyView(Text("Destructive")) }, + state: .destructive) {} + .createView() + .previewLayout(.sizeThatFits) + + PlatformButtonViewModel(content: PlatformIconViewModel(type: .system(name: "heart.fill"), + clip: .circle(background: .layer3, spacing: 15)), + type: .iconType) {} + .createView() + .previewLayout(.sizeThatFits) + + PlatformButtonViewModel(content: PlatformViewModel() { _ in AnyView(Text("Pill")) }, + type: .pill, + state: .secondary) {} + .createView() + .previewLayout(.sizeThatFits) + + PlatformButtonViewModel(content: PlatformIconViewModel(type: .system(name: "heart.fill")), + type: .pill) {} + .createView() + .previewLayout(.sizeThatFits) + + PlatformButtonViewModel(content: PlatformViewModel() { _ in AnyView(Text("Smaller")) }, + type: .small) {} + .createView() + .previewLayout(.sizeThatFits) + + } + } +} +#endif + diff --git a/PlatformUI/PlatformUI/Components/Cells/PlatformTableViewCell.swift b/PlatformUI/PlatformUI/Components/Cells/PlatformTableViewCell.swift new file mode 100644 index 000000000..9c19fd446 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Cells/PlatformTableViewCell.swift @@ -0,0 +1,103 @@ +// +// TableCell.swift +// PlatformUI +// +// Created by Rui Huang on 8/22/22. +// + +import SwiftUI + +public class PlatformTableViewCellViewModel: PlatformViewModel { + + @Published public var leading: PlatformViewModel? + @Published public var logo: PlatformViewModel? + @Published public var main: PlatformViewModel + @Published public var trailing: PlatformViewModel? + @Published public var edgeInsets: EdgeInsets + + public init(leading: PlatformViewModel? = nil, + logo: PlatformViewModel? = nil, + main: PlatformViewModel, + trailing: PlatformViewModel? = nil, + edgeInsets: EdgeInsets = EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) { + self.leading = leading + self.logo = logo + self.main = main + self.trailing = trailing + self.edgeInsets = edgeInsets + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + return AnyView( + HStack(spacing: 0) { + self.leading?.createView(parentStyle: style, styleKey: nil) + .themeStyle(styleKey: "table-cell-subtitle-style", parentStyle: style) + .padding(.trailing, 16) + + self.logo?.createView(parentStyle: style, styleKey: nil) + .padding(.trailing, 16) + + self.main.createView(parentStyle: style, styleKey: nil) + .themeStyle(styleKey: "table-cell-title-style", parentStyle: style) + + if self.trailing != nil { + Spacer() + self.trailing?.createView(parentStyle: style, styleKey: nil) + .themeStyle(styleKey: "table-cell-subtitle-style", parentStyle: style) + } + } + .padding(self.edgeInsets) + .frame(maxWidth: .infinity) + .contentShape(Rectangle()) // to enable onTapGesture() even when background is .clear + ) + } + } +} + +#if DEBUG +struct PlatformTableViewCell_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings() + + static var imageView: some View { + Image(systemName: "heart.fill") + .resizable() + .scaledToFit() + .frame(width: 32, height: 32) + } + + static var previews: some View { + Group { + PlatformTableViewCellViewModel(leading: Text("2d").wrappedViewModel, + logo: imageView.wrappedViewModel, + main: Text("main title").wrappedViewModel, + trailing: Text("trailing title").wrappedViewModel) + .createView() + .previewLayout(.sizeThatFits) + + PlatformTableViewCellViewModel(leading: PlatformView.nilViewModel, + logo: imageView.wrappedViewModel, + main: Text("main title").wrappedViewModel, + trailing: Text("trailing title").wrappedViewModel) + .createView() + .previewLayout(.sizeThatFits) + + PlatformTableViewCellViewModel(leading: PlatformView.nilViewModel, + logo: PlatformView.nilViewModel, + main: Text("main title").wrappedViewModel, + trailing: Text("trailing title").wrappedViewModel) + .createView() + .previewLayout(.sizeThatFits) + + PlatformTableViewCellViewModel(leading: PlatformView.nilViewModel, + logo: PlatformView.nilViewModel, + main: Text("main title").wrappedViewModel, + trailing: PlatformView.nilViewModel) + .createView() + .previewLayout(.sizeThatFits) + } + } +} +#endif diff --git a/PlatformUI/PlatformUI/Components/CircularProgressView/CircularProgressView.swift b/PlatformUI/PlatformUI/Components/CircularProgressView/CircularProgressView.swift new file mode 100644 index 000000000..412480138 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/CircularProgressView/CircularProgressView.swift @@ -0,0 +1,60 @@ +// +// CircularProgressBar.swift +// dydxUI +// +// Created by Rui Huang on 8/26/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI + +public class CircularProgressViewModel: PlatformViewModel { + @Published public var progress: Double = 0 + @Published public var outerTrackColor: Color = Color.pink + @Published public var innerTrackColor: Color = Color.red + @Published public var lineWidth: Double = 20 + + public static var previewValue: CircularProgressViewModel = { + let vm = CircularProgressViewModel() + vm.progress = 0.6 + return vm + }() + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + return AnyView( + ZStack { + Circle() + .stroke(lineWidth: self.lineWidth) + .opacity(0.2) + .foregroundColor(self.outerTrackColor) + + Circle() + .trim(from: 0.0, to: CGFloat(min(self.progress, 1.0))) + .stroke(style: StrokeStyle(lineWidth: self.lineWidth, lineCap: .round, lineJoin: .round)) + .foregroundColor(self.innerTrackColor) + .rotationEffect(Angle(degrees: 270.0)) + .animation(Animation.linear, value: self.progress) + } + ) + } + } +} + +#if DEBUG +struct CircularProgressBar_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + Group { + CircularProgressViewModel.previewValue + .createView() + .environmentObject(themeSettings) + .previewLayout(.sizeThatFits) + } + } +} +#endif + diff --git a/PlatformUI/PlatformUI/Components/ComboBox/ComboBox.swift b/PlatformUI/PlatformUI/Components/ComboBox/ComboBox.swift new file mode 100644 index 000000000..cb722a3bd --- /dev/null +++ b/PlatformUI/PlatformUI/Components/ComboBox/ComboBox.swift @@ -0,0 +1,85 @@ +// +// ComboBox.swift +// dydxUI +// +// Created by Rui Huang on 4/7/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI + +public class ComboBoxModel: PlatformViewModel { + @Published public var title: String? + @Published public var content: PlatformViewModel? + @Published public var onTapAction: (() -> Void)? + + public init() { } + + public init(title: String? = nil, content: PlatformViewModel? = nil, onTapAction: (() -> Void)? = nil) { + self.title = title + self.content = content + self.onTapAction = onTapAction + } + + public static var previewValue: ComboBoxModel { + let vm = ComboBoxModel() + vm.title = "Title" + vm.content = Text("value").wrappedViewModel + return vm + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + let main = VStack(alignment: .leading, spacing: 4) { + if let title = self.title { + Text(title) + .themeFont(fontSize: .smaller) + .themeColor(foreground: .textTertiary) + } + + self.content?.createView(parentStyle: style) + } + + let trailing: PlatformViewModel + if self.onTapAction != nil { + trailing = PlatformIconViewModel(type: .asset(name: "combo_box_tick", bundle: Bundle(for: PlatformUIBundleClass.self)), + size: CGSize(width: 10, height: 10)) + .createView(parentStyle: style) + .wrappedViewModel + } else { + trailing = PlatformView.nilViewModel + } + + return AnyView( + PlatformTableViewCellViewModel(leading: PlatformView.nilViewModel, + logo: PlatformView.nilViewModel, + main: main.wrappedViewModel, + trailing: trailing) + .createView(parentStyle: style) + .themeColor(background: .layer4) + .borderAndClip(style: .cornerRadius(12), borderColor: .borderDefault, lineWidth: 1) + .onTapGesture { [weak self] in + self?.onTapAction?() + } + ) + } + } +} + +#if DEBUG +struct ComboBox_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + Group { + ComboBoxModel.previewValue + .createView() + .environmentObject(themeSettings) + .previewLayout(.sizeThatFits) + } + } +} +#endif + diff --git a/PlatformUI/PlatformUI/Components/Containers/SingleAxisGeometryReader.swift b/PlatformUI/PlatformUI/Components/Containers/SingleAxisGeometryReader.swift new file mode 100644 index 000000000..d94be8c08 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Containers/SingleAxisGeometryReader.swift @@ -0,0 +1,46 @@ +// +// SingleAxisGeometryReader.swift +// PlatformUI +// +// Created by Michael Maguire on 6/7/24. +// + +import SwiftUI + +// https://stackoverflow.com/questions/64778379/how-to-use-geometry-reader-so-that-the-view-does-not-expand +// "this view can get messed up in transition animations, beware" +/// A view which acts as a geometry reader being greedy only in a single axis.. +public struct SingleAxisGeometryReader: View { + public static var defaultSize: CGFloat { 10 } + + public init(size: CGFloat = SingleAxisGeometryReader.defaultSize, axis: Axis = .horizontal, alignment: Alignment = .center, content: @escaping ((CGFloat) -> Content)) { + self.size = size + self.axis = axis + self.alignment = alignment + self.content = content + } + + private struct SizeKey: PreferenceKey { + static var defaultValue: CGFloat { SingleAxisGeometryReader.defaultSize } + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = max(value, nextValue()) + } + } + + @State private var size: CGFloat + /// the greedy dimension + var axis: Axis = .horizontal + var alignment: Alignment = .center + let content: (CGFloat)->Content + + public var body: some View { + content(size) + .frame(maxWidth: axis == .horizontal ? .infinity : nil, + maxHeight: axis == .vertical ? .infinity : nil, + alignment: alignment) + .background(GeometryReader { + proxy in + Color.clear.preference(key: SizeKey.self, value: axis == .horizontal ? proxy.size.width : proxy.size.height) + }).onPreferenceChange(SizeKey.self) { size = $0 } + } +} diff --git a/PlatformUI/PlatformUI/Components/Divider/Divider.swift b/PlatformUI/PlatformUI/Components/Divider/Divider.swift new file mode 100644 index 000000000..4676d7391 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Divider/Divider.swift @@ -0,0 +1,43 @@ +// +// Divider.swift +// PlatformUI +// +// Created by Rui Huang on 10/11/22. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI + +public class DividerModel: PlatformViewModel { + public static var previewValue: DividerModel = { + let vm = DividerModel() + return vm + }() + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { _ in + let overlayColor = ThemeColor.SemanticColor.layer6.color + + return AnyView( + Divider() + .overlay(overlayColor) + ) + } + } +} + +#if DEBUG +struct Divider_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + Group { + DividerModel.previewValue + .createView() + .environmentObject(themeSettings) + .previewLayout(.sizeThatFits) + } + } +} +#endif + diff --git a/PlatformUI/PlatformUI/Components/Extensions/EdgeInsets+Ext.swift b/PlatformUI/PlatformUI/Components/Extensions/EdgeInsets+Ext.swift new file mode 100644 index 000000000..25779655b --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Extensions/EdgeInsets+Ext.swift @@ -0,0 +1,18 @@ +// +// EdgeInsets+ext.swift +// PlatformUI +// +// Created by Michael Maguire on 6/4/24. +// + +import SwiftUI + +public extension EdgeInsets { + init(all: CGFloat) { + self.init(top: all, leading: all, bottom: all, trailing: all) + } + + init(horizontal: CGFloat, vertical: CGFloat) { + self.init(top: vertical, leading: horizontal, bottom: vertical, trailing: horizontal) + } +} diff --git a/PlatformUI/PlatformUI/Components/Extensions/Text+Ext.swift b/PlatformUI/PlatformUI/Components/Extensions/Text+Ext.swift new file mode 100644 index 000000000..357a29464 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Extensions/Text+Ext.swift @@ -0,0 +1,16 @@ +// +// Text+Ext.swift +// PlatformUI +// +// Created by Michael Maguire on 4/1/24. +// + +import SwiftUI +import Utilities + +public extension Text { + init(localizerPathKey: String, params: [String: String]? = nil) { + self = Text(DataLocalizer.shared?.localize(path: localizerPathKey, params: params) ?? "") + } +} + diff --git a/PlatformUI/PlatformUI/Components/Icons/PlatformIcon.swift b/PlatformUI/PlatformUI/Components/Icons/PlatformIcon.swift new file mode 100644 index 000000000..354de2a4f --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Icons/PlatformIcon.swift @@ -0,0 +1,175 @@ +// +// PlatformIcon.swift +// PlatformUI +// +// Created by Rui Huang on 8/24/22. +// + +import SwiftUI +import SDWebImageSwiftUI + +public class PlatformIconViewModel: PlatformViewModel { + public enum IconType { + case asset(name: String?, bundle: Bundle?) + case url(url: URL?, placeholderContent: (() -> AnyView)? = nil) + case system(name: String) + case uiImage(image: UIImage) + case any(viewModel: PlatformViewModel) + } + + public enum IconClip { + case noClip + + case circle(background: ThemeColor.SemanticColor, + spacing: CGFloat, + borderColor: ThemeColor.SemanticColor? = nil) + + public static var defaultCircle: Self { + .circle(background: .transparent, spacing: 0) + } + } + + @Published public var type: IconType + @Published public var clip: IconClip + @Published public var size: CGSize + @Published public var templateColor: ThemeColor.SemanticColor? + + public init(type: IconType, clip: IconClip = .noClip, size: CGSize = CGSize(width: 32, height: 32), templateColor: ThemeColor.SemanticColor? = nil) { + self.type = type + self.clip = clip + self.size = size + self.templateColor = templateColor + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformUI.PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + let view = Group { + switch self.type { + case .system(let name): + Image(systemName: name) + .resizable() + .templateColor(self.templateColor) + .scaledToFit() + case .asset(let name, let bundle): + if let name = name { + Image(name, bundle: bundle) + .resizable() + .templateColor(self.templateColor) + .scaledToFit() + } else { + PlatformView.nilView + } + case .url(let url, let placeholderContent): + WebImage(url: url) { image in + image.resizable() // Control layout like SwiftUI.AsyncImage, you must use this modifier or the view will use the image bitmap size + } placeholder: { + placeholderContent?() + } + .resizable() + .templateColor(self.templateColor) + .scaledToFit() + case .uiImage(let image): + if let cgImage = image.cgImage { + Image(decorative: cgImage, scale: 1) + .resizable() + .templateColor(self.templateColor) + .scaledToFit() + } else { + PlatformView.nilView + } + case .any(let viewModel): + viewModel.createView(parentStyle: style) + } + } + + let size = self.size + switch self.clip { + case .noClip: + return AnyView( + view.frame(width: size.width, height: size.height) + .themeStyle(style: style) + ) + case .circle(let background, let spacing, let borderColor): + if spacing <= 0 && size.width > spacing && size.height > spacing { + return AnyView( + view + .frame(width: size.width, height: size.height).clipShape(Circle()) + .themeStyle(style: style) + ) + } else { + let clippedView = Group { + + ZStack { + Circle() + .fill(background.color) + .frame(width: size.width, height: size.height) + .themeColor(background: background) + .clipShape(Circle()) + .overlay( + Circle().stroke(borderColor?.color ?? .clear, lineWidth: 1) + ) + + view + .frame(width: size.width - spacing, height: size.height - spacing) + .clipped() + } + .themeStyle(style: style) + } + return AnyView(clippedView) + } + } + } + } +} + +#if DEBUG +struct PlatformIcon_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings() + + static var imageView: some View { + Image(systemName: "heart.fill") + .resizable() + .scaledToFit() + .frame(width: 32, height: 32) + } + + static var previews: some View { + Group { + PlatformIconViewModel(type: .system(name: "heart.fill")) + .createView() + .previewLayout(.sizeThatFits) + + PlatformIconViewModel(type: .system(name: "heart.fill"), size: CGSize(width: 64, height: 64)) + .createView() + .previewLayout(.sizeThatFits) + + PlatformIconViewModel(type: .system(name: "heart.fill"), templateColor: .colorYellow) + .createView() + .previewLayout(.sizeThatFits) + + let url = URL(string: "https://s3.amazonaws.com/dydx.exchange/logos/walletconnect/lg/9d373b43ad4d2cf190fb1a774ec964a1addf406d6fd24af94ab7596e58c291b2.jpeg") + + PlatformIconViewModel(type: .url(url: url)) + .createView() + .previewLayout(.sizeThatFits) + + PlatformIconViewModel(type: .url(url: url), clip: .defaultCircle) + .createView() + .previewLayout(.sizeThatFits) + + PlatformIconViewModel(type: .system(name: "heart.fill"), clip: .circle(background: .layer0, spacing: 10)) + .createView() + .previewLayout(.sizeThatFits) + + let uiImage = UIImage(named: "heart.fill") + PlatformIconViewModel(type: .uiImage(image: uiImage ?? UIImage())) + .createView() + .previewLayout(.sizeThatFits) + + } + .environmentObject(themeSettings) + } +} +#endif diff --git a/PlatformUI/PlatformUI/Components/Icons/PlatformOverlayIcon.swift b/PlatformUI/PlatformUI/Components/Icons/PlatformOverlayIcon.swift new file mode 100644 index 000000000..2138c1801 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Icons/PlatformOverlayIcon.swift @@ -0,0 +1,67 @@ +// +// PlatformOverlayIcon.swift +// PlatformUI +// +// Created by Rui Huang on 8/24/22. +// + +import SwiftUI + +public class PlatformOverlayIconViewModel: PlatformViewModel { + @Published public var mainIcon: MainIcon + @Published public var overlayIcon: OverlayIcon? + @Published public var size: CGSize + @Published public var offset: CGPoint + + public init(mainIcon: MainIcon, overlayIcon: OverlayIcon? = nil, size: CGSize = CGSize(width: 32, height: 32), offset: CGPoint = CGPoint(x: 12, y: -12)) { + self.mainIcon = mainIcon + self.overlayIcon = overlayIcon + self.size = size + self.offset = offset + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + return AnyView( + Group { + ZStack { + self.mainIcon.createView(parentStyle: style, styleKey: nil) + + if let overlayIcon = self.overlayIcon { + overlayIcon.createView(parentStyle: style, styleKey: nil) + .offset(x: self.offset.x, y: self.offset.y) + } + } + } + .frame(width: self.size.width, height: self.size.height) + ) + } + } +} + +#if DEBUG +struct PlatformOverlayIcon_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings() + + static var imageView: some View { + Image(systemName: "heart.fill") + .resizable() + .scaledToFit() + .frame(width: 32, height: 32) + } + + static var previews: some View { + Group { + let mainIcon = PlatformIconViewModel(type: .system(name: "heart.fill"), size: CGSize(width: 24, height: 24)) + let overlayIcon = PlatformIconViewModel(type: .system(name: "heart.fill"), size: CGSize(width: 8, height: 8)) + + PlatformOverlayIconViewModel(mainIcon: mainIcon, + overlayIcon: overlayIcon) + .createView() + .previewLayout(.sizeThatFits) + } + } +} +#endif diff --git a/PlatformUI/PlatformUI/Components/Input/PlatformInput.swift b/PlatformUI/PlatformUI/Components/Input/PlatformInput.swift new file mode 100644 index 000000000..06daeaf91 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Input/PlatformInput.swift @@ -0,0 +1,596 @@ +// +// PlatformInput.swift +// PlatformUI +// +// Created by Rui Huang on 9/19/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI +import Utilities +import Introspect +import Popovers + +// a View is required here since programmatically focusing a textView requires a @FocusState property wrapper +private struct PlatformInputView: View { + @ObservedObject private var model: PlatformInputModel + @FocusState private var isFocused: Bool + + private var parentStyle: ThemeStyle + private var styleKey: String? + + init(model: PlatformInputModel, parentStyle: ThemeStyle, styleKey: String?) { + self.model = model + self.parentStyle = parentStyle + self.styleKey = styleKey + } + + var body: some View { + return HStack(alignment: .center, spacing: 4) { + VStack(alignment: .leading, spacing: 4) { + header + ZStack(alignment: .leading) { + if model.currentValue == nil || model.currentValue?.length == 0 { + placeholder + } + textField + } + } + model.valueAccessory + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .contentShape(Rectangle()) + .onTapGesture { + isFocused = true + } + .onAppear { + isFocused = model.focusedOnAppear + } + } + + private var fontType: ThemeFont.FontType { + switch model.keyboardType { + case .numberPad, .numbersAndPunctuation, .decimalPad: + return .number + default: + return .base + } + } + + private var textField: some View { + TextField("", text: model.value, onEditingChanged: { editingChanged in + isFocused = editingChanged + model.onEditingChanged?(editingChanged) + }) + .focused($isFocused) + .truncationMode(model.truncateMode) + .keyboardType(model.keyboardType) + .textContentType(model.contentType) + .themeColor(foreground: .textPrimary) + .themeFont(fontType: fontType, fontSize: .large) + } + + private var placeholder: some View { + Text(model.placeHolder) + .themeColor(foreground: .textTertiary) + .themeFont(fontType: fontType, fontSize: .large) + .lineLimit(1) + .minimumScaleFactor(0.5) + .truncationMode(model.truncateMode) + } + + private var header: AnyView? { + guard let headerText = model.label else { return nil } + return HStack(spacing: 4) { + Text(headerText) + .themeColor(foreground: .textTertiary) + .themeFont(fontSize: .smaller) + model.labelAccessory + Spacer() + }.wrappedInAnyView() + } +} + +public class PlatformInputModel: PlatformViewModel { + @Published public var label: String? + @Published public var labelAccessory: AnyView? + @Published public var value: Binding + @Published public var valueAccessory: AnyView? + @Published public var currentValue: String? + @Published public var placeHolder: String = "" + @Published public var keyboardType: UIKeyboardType = .default + @Published public var contentType: UITextContentType? + @Published public var onEditingChanged: ((Bool) -> Void)? + @Published public var truncateMode: Text.TruncationMode = .tail + @Published public var focusedOnAppear: Bool = false + + public init(label: String? = nil, + labelAccessory: AnyView? = nil, + value: Binding, + valueAccessory: AnyView? = nil, + currentValue: String? = nil, + placeHolder: String = "", + keyboardType: UIKeyboardType = .default, + contentType: UITextContentType? = nil, + onEditingChanged: ((Bool) -> Void)? = nil, + truncateMode: Text.TruncationMode = .tail, + focusedOnAppear: Bool = false) { + self.label = label + self.labelAccessory = labelAccessory + self.value = value + self.valueAccessory = valueAccessory + self.currentValue = currentValue + self.placeHolder = placeHolder + self.keyboardType = keyboardType + self.contentType = contentType + self.onEditingChanged = onEditingChanged + self.truncateMode = truncateMode + self.focusedOnAppear = focusedOnAppear + } + + public static var previewValue: PlatformInputModel = { + let vm = PlatformInputModel(value: Binding(get: { "Test String" }, set: { _ = $0 })) + return vm + }() + + override public func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] _ in + guard let self = self else { return AnyView(PlatformView.nilView) } + return AnyView(PlatformInputView(model: self, parentStyle: parentStyle, styleKey: styleKey)) + } + } +} + +// Input + +open class PlatformValueInputViewModel: PlatformViewModel { + @Published public var label: String? + open var value: String? + @Published open var valueAccessoryView: AnyView? { + didSet { + updateView() + } + } + @Published open var labelAccessory: AnyView? { + didSet { + updateView() + } + } + + public var onEdited: ((String?) -> Void)? + + public init(label: String? = nil, labelAccessory: AnyView? = nil, value: String? = nil, valueAccessoryView: AnyView? = nil, onEdited: ((String?) -> Void)? = nil) { + self.label = label + self.labelAccessory = labelAccessory + self.value = value + self.valueAccessoryView = valueAccessoryView + self.onEdited = onEdited + } + + open func valueChanged(value: String?) { + onEdited?(value) + } + + open var header: PlatformViewModel { + if let label = label, label.length > 0 { + return Text(label) + .themeColor(foreground: .textTertiary) + .themeFont(fontSize: .smaller) + .wrappedViewModel + + } + + return PlatformView.nilViewModel + } +} + +open class PlatformTextInputViewModel: PlatformValueInputViewModel { + public enum InputType { + case `default` + case decimalDigits + case wholeNumber + + fileprivate var keyboardType: UIKeyboardType { + switch self { + case .default: return .default + case .decimalDigits: return .decimalPad + case .wholeNumber: return .numberPad + } + } + + fileprivate var sanitize: (String) -> String? { + switch self { + case .default: return { $0 } + case .decimalDigits: return { $0.cleanAsDecimalNumber() } + case .wholeNumber: return { $0.truncateToWholeNumber() } + } + } + } + + private var debouncer = Debouncer() + + open private (set) var inputType: InputType + + /// Prefer to set `value` directly if forcing is not needed + /// - Parameters: + /// - value: value to set + /// - shouldForce: whether setting shoudl happen even if input is focused + /// we need to refactor how we do inputs to be more flexible + public final func programmaticallySet(value: String) { + input = value + self.valueChanged(value: self.input) + updateView() + } + + override open var value: String? { + didSet { + if !focused { + input = value ?? "" + updateView() + } + } + } + + open override func valueChanged(value: String?) { + let handler = debouncer.debounce() + handler?.run({ [weak self] in + self?.onEdited?(value) + }, delay: 0.25) + } + + @Published private var input: String = "" + + public lazy var inputBinding = Binding( + get: { + return self.input + }, + set: { newInput in + if self.focused { + let sanitized = self.inputType.sanitize(newInput) + if let sanitized { + self.input = sanitized + } else if newInput.isEmpty { + self.input = "" + } else { + // this is necessary to make binding work properly + self.input = self.input + } + self.valueChanged(value: self.input) + } + } + ) + + @Published public var placeHolder: String? + private var focused: Bool = false { + didSet { + if focused != oldValue { + if !focused { + input = value ?? "" + } + } + } + } + + public var contentType: UITextContentType? + + private let truncateMode: Text.TruncationMode + + public init(label: String? = nil, + labelAccessory: AnyView? = nil, + value: String? = nil, + placeHolder: String? = nil, + valueAccessoryView: AnyView? = nil, + inputType: InputType = .default, + contentType: UITextContentType? = nil, + onEdited: ((String?) -> Void)? = nil, + truncateMode: Text.TruncationMode = .middle) { + self.inputType = inputType + self.truncateMode = truncateMode + super.init(label: label, labelAccessory: labelAccessory, valueAccessoryView: valueAccessoryView, onEdited: onEdited) + self.value = value + input = value ?? "" + self.placeHolder = placeHolder + self.contentType = contentType + } + + override open func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + let model = PlatformInputModel( + label: self.label, + labelAccessory: self.labelAccessory, + value: self.inputBinding, + valueAccessory: self.valueAccessoryView, + currentValue: self.input, + placeHolder: self.placeHolder ?? "", + keyboardType: self.inputType.keyboardType, + onEditingChanged: { focused in + self.focused = focused + }, + truncateMode: self.truncateMode + ) + + return AnyView( PlatformInputView(model: model, + parentStyle: parentStyle, + styleKey: styleKey) ) + } + } +} + +public struct InputSelectOption { + public var value: String + public var string: String + + public init(value: String, string: String) { + self.value = value + self.string = string + } +} + +open class PlatformOptionsInputViewModel: PlatformValueInputViewModel { + @Published public var options: [InputSelectOption]? // options of values to select from, set at update + + public var optionTitles: [String]? { + options?.compactMap { $0.string } + } + + override open var value: String? { + didSet { + if value != oldValue { + index = valueIndex() + //onEdited?(value) + } + } + } + + @Published public var index: Int? + + public init(label: String? = nil, value: String? = nil, options: [InputSelectOption]? = nil, onEdited: ((String?) -> Void)? = nil) { + super.init(label: label, value: value, onEdited: onEdited) + self.options = options + index = valueIndex() + } + + internal func valueIndex() -> Int? { + return options?.firstIndex(where: { option in + option.value == self.value + }) + } +} + +open class PlatformButtonOptionsInputViewModel: PlatformOptionsInputViewModel { + override open func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + let titles = self.optionTitles + + let items = titles?.compactMap { + self.unselected(item: $0) + } + let selectedItems = titles?.compactMap { + self.selected(item: $0) + } + return AnyView( + ScrollViewReader { value in + ScrollView(.horizontal, showsIndicators: false) { + TabGroupModel(items: items, + selectedItems: selectedItems, + currentSelection: self.index, + onSelectionChanged: { [weak self] index in + withAnimation(Animation.easeInOut(duration: 0.05)) { + value.scrollTo(index) + self?.updateSelection(index: index) + } + }) + .createView(parentStyle: style) + .padding() + .animation(.none) + } + } + ) + } + } + + open func updateSelection(index: Int) { + if index < options?.count ?? 0 { + value = options?[index].value + onEdited?(value) + } + } + + open func unselected(item: String) -> PlatformViewModel { + Text(item) + .themeFont(fontType: .plus, fontSize: .largest) + .themeColor(foreground: .textTertiary) + .wrappedViewModel + } + + open func selected(item: String) -> PlatformViewModel { + Text(item) + .themeFont(fontType: .plus, fontSize: .largest) + .wrappedViewModel + } +} + +open class PlatformPopoverOptionsInputViewModel: PlatformOptionsInputViewModel { + @Published public var position = Popover.Attributes.Position.absolute( + originAnchor: .topRight, + popoverAnchor: .bottomRight + ) + + @Published private var present: Bool = false + + private lazy var presentBinding = Binding( + get: { [weak self] in + self?.present ?? false + }, + set: { [weak self] in + self?.present = $0 + } + ) + + override open func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + guard let titles = self.optionTitles else { + return AnyView(PlatformView.nilView) + } + + return AnyView( + Button(action: { [weak self] in + if !(self?.present ?? false) { + self?.present = true + } + }, label: { + VStack(alignment: .leading, spacing: 4) { + self.header.createView(parentStyle: style) + self.selectedItemView + .createView(parentStyle: style) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .contentShape(RoundedRectangle(cornerRadius: 12)) + }) + .popover(present: self.presentBinding, attributes: { [weak self] attrs in + guard let self = self else { + return + } + attrs.position = self.position + attrs.sourceFrameInset.top = -8 + let animation = Animation.easeOut(duration: 0.2) + attrs.presentation.animation = animation + attrs.dismissal.animation = animation + attrs.rubberBandingMode = .none + attrs.blocksBackgroundTouches = true + attrs.onTapOutside = { + self.present = false + } + + }, view: { + VStack(alignment: .leading, spacing: 0) { + ForEach(Array(titles.enumerated()), id: \.element) { index, title in + Button(action: { + if index != self.index { + self.index = index + self.onEdited?(self.options?[index].value) + } + self.present = false + }) { + HStack { + Text(title) + .themeFont(fontSize: .medium) + .themeColor(foreground: .textPrimary) + Spacer() + if index == self.index { + PlatformIconViewModel(type: .system(name: "checkmark"), size: CGSize(width: 16, height: 16)) + .createView(parentStyle: parentStyle, styleKey: styleKey) + } + } + .contentShape(Rectangle()) + .padding(.horizontal, 16) + .padding(.vertical, 12) + } + let isLast = index == titles.count - 1 + if !isLast { + DividerModel().createView(parentStyle: style) + } + } + } + .frame(maxWidth: 300) + .fixedSize() + .themeColor(background: .layer3) + .cornerRadius(16, corners: .allCorners) + .border(cornerRadius: 16) + .environmentObject(ThemeSettings.shared) + }, background: { + ThemeColor.SemanticColor.layer0.color.opacity(0.7) + }) + + ) + } + } + + open var selectedItemView: PlatformViewModel { + let index = index ?? 0 + if let titles = optionTitles, index < titles.count { + let selectedText = titles[index] + return Text(selectedText) + .themeFont(fontSize: .medium) + .leftAligned() + .wrappedViewModel + } + return PlatformView.nilViewModel + } +} + +open class PlatformBooleanInputViewModel: PlatformValueInputViewModel { + + open var isEnabled: Bool = true + + open override var header: PlatformViewModel { + if let label = label, label.length > 0 { + return Text(label) + .themeColor(foreground: isEnabled ? .textSecondary : .textTertiary) + .themeFont(fontSize: .medium) + .wrappedViewModel + + } + return PlatformView.nilViewModel + } + + override open var value: String? { + didSet { + inputBinding.update() + } + } + + public lazy var inputBinding = Binding( + get: { self.value == "true" }, + set: { + self.value = $0 ? "true" : "false" + self.valueChanged(value: self.value) + } + ) + + override open func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + return AnyView( + HStack(spacing: 0) { + self.header.createView(parentStyle: style) + .fixedSize() + Spacer() + Toggle("", isOn: self.inputBinding) + .toggleStyle(SwitchToggleStyle(tint: ThemeColor.SemanticColor.colorPurple.color)) + .disabled(!self.isEnabled) + } + ) + } + } +} + +public extension String { + var unlocalizedNumericValue: String? { + Parser.standard.asInputNumber(self)?.stringValue ?? self + } +} + +#if DEBUG + struct PlatformInput_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + Group { + PlatformInputModel.previewValue + .createView() + .environmentObject(themeSettings) + .previewLayout(.sizeThatFits) + } + } + } +#endif diff --git a/PlatformUI/PlatformUI/Components/Input/PlatformOutput.swift b/PlatformUI/PlatformUI/Components/Input/PlatformOutput.swift new file mode 100644 index 000000000..7dfd86ea7 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Input/PlatformOutput.swift @@ -0,0 +1,14 @@ +// +// PlatformOutput.swift +// PlatformUI +// +// Created by John Huang on 1/5/23. +// + +import SwiftUI +import Utilities + +open class PlatformValueOutputViewModel: PlatformViewModel { + public var label: String? + public var value: String? +} diff --git a/PlatformUI/PlatformUI/Components/Labels/ColoredText.swift b/PlatformUI/PlatformUI/Components/Labels/ColoredText.swift new file mode 100644 index 000000000..65808d478 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Labels/ColoredText.swift @@ -0,0 +1,52 @@ +// +// ColoredText.swift +// PlatformUI +// +// Created by Rui Huang on 11/10/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI + +public class ColoredTextModel: PlatformViewModel { + @Published public var text: String? + @Published public var color: ThemeColor.SemanticColor = .textSecondary + + public init(text: String? = nil, color: ThemeColor.SemanticColor = .textSecondary) { + self.text = text + self.color = color + } + + public static var previewValue: ColoredTextModel = { + let vm = ColoredTextModel() + vm.text = "Test String" + return vm + }() + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + return AnyView( + Text(self.text ?? "") + .themeColor(foreground: self.color) + ) + } + } +} + +#if DEBUG +struct ColoredText_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + Group { + ColoredTextModel.previewValue + .createView() + .environmentObject(themeSettings) + .previewLayout(.sizeThatFits) + } + } +} +#endif + diff --git a/PlatformUI/PlatformUI/Components/Labels/InlineAlert.swift b/PlatformUI/PlatformUI/Components/Labels/InlineAlert.swift new file mode 100644 index 000000000..e0541b770 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Labels/InlineAlert.swift @@ -0,0 +1,125 @@ +// +// InlineAlert.swift +// dydxUI +// +// Created by Michael Maguire on 4/4/24. +// Copyright © 2024 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI + +public class InlineAlertViewModel: PlatformViewModel { + + @Published public var config: Config + + public init(_ config: Config) { + self.config = config + } + + public static var previewValue: InlineAlertViewModel = { + let vm = InlineAlertViewModel(Config(title: "Title", body: "Body", level: .error)) + return vm + }() + + private var title: AnyView? { + guard let titleText = config.title else { return nil } + return Text(titleText) + .themeColor(foreground: .textPrimary) + .themeFont(fontType: .plus, fontSize: .medium) + .wrappedInAnyView() + } + + private var body: AnyView? { + guard let bodyText = config.body else { return nil } + return Text(bodyText) + .themeColor(foreground: .textPrimary) + .themeFont(fontType: .base, fontSize: .small) + .wrappedInAnyView() + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + let config = self.config + + return HStack(spacing: 0) { + config.level.tabColor.color + .frame(width: 6) + HStack(spacing: 0) { + VStack(alignment: .leading) { + self.title + self.body + + } + Spacer() + } + .padding(.all, 10) + .themeColor(background: config.level.backgroundColor) + } + + .fixedSize(horizontal: false, vertical: true) + .clipShape(.rect(cornerRadius: 6)) + .wrappedInAnyView() + } + } +} + +public extension InlineAlertViewModel { + struct Config { + public var title: String? + public var body: String? + public var level: Level + + public init(title: String?, body: String?, level: Level) { + self.title = title + self.body = body + self.level = level + } + } +} + +public extension InlineAlertViewModel { + enum Level { + case error + case warning + case success + + fileprivate var tabColor: ThemeColor.SemanticColor { + switch self { + case .error: + return .colorRed + case .warning: + return .colorYellow + case .success: + return .colorGreen + } + } + + fileprivate var backgroundColor: ThemeColor.SemanticColor { + switch self { + case .error: + return .colorFadedRed + case .warning: + return .colorFadedYellow + case .success: + return .colorFadedGreen + } + } + } +} + +#if DEBUG +struct InlineAlert_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + Group { + InlineAlertViewModel.previewValue + .createView() + .environmentObject(themeSettings) + .previewLayout(.sizeThatFits) + } + } +} +#endif + diff --git a/PlatformUI/PlatformUI/Components/Labels/SignedAmount.swift b/PlatformUI/PlatformUI/Components/Labels/SignedAmount.swift new file mode 100644 index 000000000..4010e0ed5 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Labels/SignedAmount.swift @@ -0,0 +1,122 @@ +// +// SignedAmount.swift +// PlatformUI +// +// Created by Rui Huang on 8/24/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI + +public class SignedAmountViewModel: PlatformViewModel, Hashable { + public enum ColoringOption { + case signOnly + case textOnly + case allText + } + + public enum DisplayType { + case dollar + case percent + } + + @Published public var text: String? + @Published public var sign: PlatformUISign + @Published public var coloringOption: ColoringOption + @Published public var positiveTextStyleKey: String + @Published public var negativeTextStyleKey: String + + public init(text: String? = nil, sign: PlatformUISign = .plus, coloringOption: ColoringOption, positiveTextStyleKey: String, negativeTextStyleKey: String) { + self.text = text + self.sign = sign + self.coloringOption = coloringOption + self.positiveTextStyleKey = positiveTextStyleKey + self.negativeTextStyleKey = negativeTextStyleKey + } + + + public static func == (lhs: SignedAmountViewModel, rhs: SignedAmountViewModel) -> Bool { + lhs.text == rhs.text && + lhs.sign == rhs.sign && + lhs.coloringOption == rhs.coloringOption + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(text) + hasher.combine(sign) + hasher.combine(coloringOption) + } + + public static var previewValue = SignedAmountViewModel(text: "2.02", sign: .plus, coloringOption: .allText, positiveTextStyleKey: "signed-plus", negativeTextStyleKey: "signed-minus") + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + return AnyView( + HStack(alignment: .center, spacing: 2) { + if let text = self.text { + switch self.coloringOption { + case .signOnly, .allText: + switch self.sign { + case .plus: + Text("+") + .themeStyle(styleKey: self.positiveTextStyleKey, parentStyle: style) + case .minus: + Text("-") + .themeStyle(styleKey: self.negativeTextStyleKey, parentStyle: style) + + case .none: + Text("") + } + case .textOnly: + PlatformView.nilView + } + + switch self.coloringOption { + case .signOnly: + Text(text) + case .allText, .textOnly: + switch self.sign { + case .plus: + Text(text) + .themeStyle(styleKey: self.positiveTextStyleKey, parentStyle: style) + case .minus: + Text(text) + .themeStyle(styleKey: self.negativeTextStyleKey, parentStyle: style) + case .none: + Text(text) + } + } + } else { + PlatformView.nilView + } + } + // .minimumScaleFactor(0.5) + ) + } + } +} + +#if DEBUG +struct SignedAmount_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + Group { + SignedAmountViewModel(text: "$2.00", sign: .plus, coloringOption: .allText, positiveTextStyleKey: "signed-plus", negativeTextStyleKey: "signed-minus").createView() + .previewLayout(.sizeThatFits) + + SignedAmountViewModel(text: "$2.00", sign: .minus, coloringOption: .allText, positiveTextStyleKey: "signed-plus", negativeTextStyleKey: "signed-minus").createView() + .previewLayout(.sizeThatFits) + + SignedAmountViewModel(text: "$2.00", sign: .plus, coloringOption: .allText, positiveTextStyleKey: "signed-plus", negativeTextStyleKey: "signed-minus").createView() + .previewLayout(.sizeThatFits) + + SignedAmountViewModel(text: "$2.00", sign: .minus, coloringOption: .allText, positiveTextStyleKey: "signed-plus", negativeTextStyleKey: "signed-minus").createView() + .previewLayout(.sizeThatFits) + } + } +} +#endif + diff --git a/PlatformUI/PlatformUI/Components/TabGroup/TabGroup.swift b/PlatformUI/PlatformUI/Components/TabGroup/TabGroup.swift new file mode 100644 index 000000000..5b55ba77f --- /dev/null +++ b/PlatformUI/PlatformUI/Components/TabGroup/TabGroup.swift @@ -0,0 +1,146 @@ +// +// TabGroup.swift +// PlatformUI +// +// Created by Rui Huang on 10/3/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI + +// a View is required here since `matchedGeometryEffect` is used which requires a @Namespace property wrappers for proper functionality. @Namespace properties must exist within the context of a View +private struct TabGroupView: View { + @ObservedObject var model: TabGroupModel + @Namespace var animation + + private let parentStyle: ThemeStyle + private let styleKey: String? + private let selectionAnimation: Animation? + + init(model: TabGroupModel, selectionAnimation: Animation?, parentStyle: ThemeStyle, styleKey: String?) { + self.model = model + self.parentStyle = parentStyle + self.styleKey = styleKey + self.selectionAnimation = selectionAnimation + } + + public var body: some View { + HStack(spacing: model.spacing) { + ForEach(0..<(model.items?.count ?? 0), id: \.self) { i in + if i == model.currentSelection { + selectedItemView(index: i) + } else { + unselectedItemView(index: i) + } + } + } + .fixedSize(horizontal: false, vertical: true) + .animation(selectionAnimation, value: model.currentSelection) + } + + private func selectedItemView(index: Int) -> some View { + let item = model.selectedItems?[index] + .createView(parentStyle: parentStyle, styleKey: model.selectedStyleKey) + .matchedGeometryEffect(id: index, in: animation) + + return item + .frameIf(condition: model.layoutConfig == .equalSpacing, minWidth: 0, maxWidth: .infinity) + } + + private func unselectedItemView(index: Int) -> some View { + let item = model.items?[index] + .createView(parentStyle: parentStyle, styleKey: model.unselectedStyleKey) + .onTapGesture { + withAnimation(selectionAnimation) { + model.currentSelection = index + } + model.onSelectionChanged?(index) + } + .matchedGeometryEffect(id: index, in: animation) + + return item + .frameIf(condition: model.layoutConfig == .equalSpacing, minWidth: 0, maxWidth: .infinity) + } +} + +private extension View { + func frameIf(condition: Bool, minWidth: CGFloat?, maxWidth: CGFloat?) -> some View { + if condition { + return AnyView(self.frame(minWidth: minWidth, maxWidth: maxWidth)) + } else { + return AnyView(self) + } + } +} + + +public class TabGroupModel : PlatformViewModel { + public enum LayoutConfig { + case naturalSize + case equalSpacing + } + @Published public var items: [ItemContent]? + @Published public var selectedItems: [ItemContent]? + @Published public var currentSelection: Int? + @Published public var unselectedStyleKey: String? + @Published public var selectedStyleKey: String? + @Published public var onSelectionChanged: ((Int) ->())? + @Published public var spacing: CGFloat? + @Published public var layoutConfig = LayoutConfig.naturalSize + @Published public var selectionAnimation: Animation? + + public init(items: [ItemContent]? = nil, selectedItems: [ItemContent]? = nil, currentSelection: Int? = nil, unselectedStyleKey: String? = nil, selectedStyleKey: String? = nil, selectionAnimation: Animation? = nil, onSelectionChanged: ((Int) ->())? = nil, spacing: CGFloat? = 8, layoutConfig: LayoutConfig = .naturalSize) { + self.items = items + if let selectedItems = selectedItems { + self.selectedItems = selectedItems + } else { + self.selectedItems = items + } + self.currentSelection = currentSelection + self.unselectedStyleKey = unselectedStyleKey + self.selectedStyleKey = selectedStyleKey + self.onSelectionChanged = onSelectionChanged + self.spacing = spacing + self.layoutConfig = layoutConfig + self.selectionAnimation = selectionAnimation + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + assert(self.items?.count == self.selectedItems?.count) + + return AnyView( + TabGroupView(model: self, selectionAnimation: self.selectionAnimation, parentStyle: parentStyle, styleKey: styleKey) + ) + } + } +} + +#if DEBUG +struct TabGroup_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + Group { + let items = [ + Text("item 1").wrappedViewModel, + Text("item 2").wrappedViewModel, + Text("item 3").wrappedViewModel + ] + let selected = [ + Text("item 1").themeColor(foreground: .textTertiary).wrappedViewModel, + Text("item 2").themeColor(foreground: .textTertiary).wrappedViewModel, + Text("item 3").themeColor(foreground: .textTertiary).wrappedViewModel + ] + + TabGroupModel(items: items, selectedItems: selected, currentSelection: 1) + .createView() + .environmentObject(themeSettings) + .previewLayout(.sizeThatFits) + } + } +} +#endif + diff --git a/PlatformUI/PlatformUI/Components/TabGroup/TabItemViewModel.swift b/PlatformUI/PlatformUI/Components/TabGroup/TabItemViewModel.swift new file mode 100644 index 000000000..734eca4d9 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/TabGroup/TabItemViewModel.swift @@ -0,0 +1,146 @@ +// +// TabItemViewModel.swift +// PlatformUI +// +// Created by John Huang on 1/11/23. +// + +import SwiftUI +import Utilities + +public class TabItemViewModel: PlatformViewModel, Equatable { + public static func == (lhs: TabItemViewModel, rhs: TabItemViewModel) -> Bool { + lhs.isSelected == rhs.isSelected && + lhs.value == rhs.value + } + + public enum TabItemContent: Equatable { + public struct PillConfig { + var text: String + var textColor: ThemeColor.SemanticColor + var backgroundColor: ThemeColor.SemanticColor + + public init(text: String, textColor: ThemeColor.SemanticColor, backgroundColor: ThemeColor.SemanticColor) { + self.text = text + self.textColor = textColor + self.backgroundColor = backgroundColor + } + } + + case text(String, EdgeInsets = EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8)) + case textWithPillAccessory(text: String, pillConfig: PillConfig) + case icon(UIImage) + case bar(PlatformViewModel) + + public static func ==(lhs: TabItemContent, rhs: TabItemContent) -> Bool { + switch (lhs, rhs) { + case let (.text(leftText, leftInsets), .text(rightText, rightInsets)): + return leftText == rightText && leftInsets == rightInsets + case let (.icon(leftImage), .icon(rightImage)): + return leftImage.isEqual(rightImage) + default: + return false + } + } + } + @Published public var value: TabItemContent? + @Published public var isSelected: Bool = false + + public init(value: TabItemViewModel.TabItemContent? = nil, isSelected: Bool = false) { + self.value = value + self.isSelected = isSelected + } + + public static var previewValue: TabItemViewModel = { + let vm = TabItemViewModel() + vm.value = .bar(Text("Test String").wrappedViewModel) + vm.isSelected = true + return vm + }() + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self, let value = self.value else { return AnyView(PlatformView.nilView) } + + let styleKey = self.isSelected ? "pill_tab_group_selected_item" : "pill_tab_group_unselected_item" + let templateColor: ThemeColor.SemanticColor = self.isSelected ? .textPrimary: .textTertiary + let textFontSize = ThemeFont.FontSize.small + let borderWidth: CGFloat = 1 + switch value { + case .text(let value, let edgeInsets): + return Text(value) + .themeFont(fontSize: textFontSize) + .padding(edgeInsets) + .themeStyle(styleKey: styleKey, parentStyle: style) + .borderAndClip(style: .capsule, borderColor: .layer6, lineWidth: borderWidth) + .wrappedInAnyView() + case .textWithPillAccessory(let text, let pillConfig): + return HStack(alignment: .center, spacing: 4) { + Text(text) + .themeFont(fontSize: textFontSize) + .themeColor(foreground: .textSecondary) + Text(pillConfig.text) + .themeFont(fontSize: .smaller) + .padding(.horizontal, 5) + .padding(.vertical, 2) + .themeColor(foreground: pillConfig.textColor) + .themeColor(background: pillConfig.backgroundColor) + .clipShape(.rect(cornerRadius: 6)) + } + .padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8)) + .themeStyle(styleKey: styleKey, parentStyle: style) + .borderAndClip(style: .capsule, borderColor: .layer6, lineWidth: borderWidth) + .wrappedInAnyView() + case .icon(let image): + let height = ThemeSettings.shared.themeConfig.themeFont.uiFont(of: .base, fontSize: textFontSize)?.lineHeight ?? 14 + return PlatformIconViewModel(type: .uiImage(image: image), + size: CGSize(width: height, height: height), + templateColor: templateColor) + .createView(parentStyle: parentStyle) + .padding(.vertical, 6) + .padding(.horizontal, 8) + .themeStyle(styleKey: styleKey, parentStyle: style) + .borderAndClip(style: .capsule, borderColor: .layer6, lineWidth: borderWidth) + .wrappedInAnyView() + case .bar(let value): + let content = VStack { + value.createView(parentStyle: style) + Rectangle() + .fill(self.isSelected ? ThemeColor.SemanticColor.colorPurple.color : .clear) + .frame(height: 2) + .frame(maxWidth: .infinity) + } + .frame(maxWidth: .infinity) + return AnyView(content) + + } + } + } +} + +#if DEBUG +struct TabItem_Previews_Dark: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { +// ThemeSettings.applyDarkTheme() +// ThemeSettings.applyStyles() + return TabItemViewModel.previewValue + .createView() + .previewLayout(.sizeThatFits) + } +} + +struct TabItem_Previews_Light: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { +// ThemeSettings.applyLightTheme() +// ThemeSettings.applyStyles() + return TabItemViewModel.previewValue + .createView() + .previewLayout(.sizeThatFits) + } +} + +#endif diff --git a/PlatformUI/PlatformUI/Components/Utility/SwipeActionsViewModifier.swift b/PlatformUI/PlatformUI/Components/Utility/SwipeActionsViewModifier.swift new file mode 100644 index 000000000..7c164166b --- /dev/null +++ b/PlatformUI/PlatformUI/Components/Utility/SwipeActionsViewModifier.swift @@ -0,0 +1,389 @@ +// +// SwipeActionsViewModifier.swift +// PlatformUI +// +// Created by Michael Maguire on 9/18/23. +// + +import SwiftUI + +public enum CellSwipeAccessoryPosition: Int { + case left, right, both +} + +public enum CellSwipeAccessoryVisibility: String { + case showRight, showLeft, showNone +} + +public struct CellSwipeAccessory { + public let accessoryView: AnyView? + public let action: (() -> Void)? + public static let appearAnimation = Animation.easeOut(duration: 0.5) + + public init( + accessoryView: AnyView?, + action: (() -> Void)? = nil + ) { + self.accessoryView = accessoryView + self.action = action + } +} + +struct SwipeActionsModifier: ViewModifier { + @State var cellSwipeAccessoryPosition: CellSwipeAccessoryPosition + let leftCellSwipeAccessory: CellSwipeAccessory? + let rightCellSwipeAccessory: CellSwipeAccessory? + + @State var shouldResetStatusOnAppear = true + + @State var accessoryVisibility: CellSwipeAccessoryVisibility = .showNone + + @State var offset: CGFloat = 0.0 + + @State var frameWidth: CGFloat = .greatestFiniteMagnitude + @State var leftOffset: CGFloat = .leastNormalMagnitude + @State var rightOffset: CGFloat = .greatestFiniteMagnitude + @State var spaceWidth: CGFloat = 0 + + let cellID = UUID() + + @State var currentCellID: UUID? = nil + @State var resetNotice = NotificationCenter.default.publisher(for: .cellSwipeAccessoryVisibilityReset) + + let cellSwipeAccessoryWidth: CGFloat = 60 + + init( + leftCellSwipeAccessory: CellSwipeAccessory?, + rightCellSwipeAccessory: CellSwipeAccessory? + ) { + let cellSwipeAccessoryPosition: CellSwipeAccessoryPosition + if leftCellSwipeAccessory != nil && rightCellSwipeAccessory == nil { + cellSwipeAccessoryPosition = .left + } else if leftCellSwipeAccessory == nil && rightCellSwipeAccessory != nil { + cellSwipeAccessoryPosition = .right + } else { + cellSwipeAccessoryPosition = .both + } + _cellSwipeAccessoryPosition = State(wrappedValue: cellSwipeAccessoryPosition) + self.leftCellSwipeAccessory = leftCellSwipeAccessory + self.rightCellSwipeAccessory = rightCellSwipeAccessory + } + + func accessoryView(accessory: CellSwipeAccessory, position: CellSwipeAccessoryPosition) -> some View { + return Rectangle() + .fill(Color.clear) + .overlay( + ZStack(alignment: position == .left ? .trailing : .leading) { + Color.clear + accessory.accessoryView + .contentShape(Rectangle()) + .frame(width: cellSwipeAccessoryWidth) + } + ) + .contentShape(Rectangle()) + .onTapGesture { + accessory.action?() + resetStatus() + } + } + + @ViewBuilder func loadAccessory(_ accessory: CellSwipeAccessory, position: CellSwipeAccessoryPosition, frame: CGRect) + -> some View + { + accessoryView(accessory: accessory, position: position) + .offset( + x: cellOffset( + position: position, + width: frame.width, + accessory: accessory + ) + ) + } + + func cellOffset( + position: CellSwipeAccessoryPosition, + width: CGFloat, + accessory: CellSwipeAccessory + ) -> CGFloat { + + if frameWidth == .greatestFiniteMagnitude { + DispatchQueue.main.async { + frameWidth = width + } + } + + if position == .left { + return -width + offset + } + else { + return width + offset + } + + } + + func resetAccessoryOffset(position: CellSwipeAccessoryPosition, accessory: CellSwipeAccessory?) { + + if position == .left { + withAnimation(CellSwipeAccessory.appearAnimation) { + leftOffset = -frameWidth + } + } else { + withAnimation(CellSwipeAccessory.appearAnimation) { + rightOffset = frameWidth + } + } + return + } + + func body(content: Content) -> some View { + + return ZStack(alignment: .topLeading) { + Color.clear.zIndex(0) + ZStack { + + GeometryReader { proxy in + ZStack { + if let accessory = leftCellSwipeAccessory { + loadAccessory(accessory, position: .left, frame: proxy.frame(in: .local)) + } + } + }.zIndex(1) + GeometryReader { proxy in + ZStack { + if let accessory = rightCellSwipeAccessory { + loadAccessory(accessory, position: .right, frame: proxy.frame(in: .local)) + } + } + }.zIndex(2) + + ZStack(alignment: .center) { + Color.clear + content + .environment(\.cellSwipeAccessoryVisibility, accessoryVisibility) + } + .zIndex(3) + .highPriorityGesture( + TapGesture(count: 1), + including: currentCellID == nil ? .subviews : .none + ) + .contentShape(Rectangle()) + .onTapGesture( + count: currentCellID != nil ? 1 : 4, + perform: { + resetStatus() + dismissNotification() + } + ) + .offset(x: offset) + } + } + .contentShape(Rectangle()) + .gesture(getGesture()) + .onAppear { + self.set(accessoryVisibility: accessoryVisibility) + switch accessoryVisibility { + case .showLeft: + offset = cellSwipeAccessoryWidth + case .showRight: + offset = cellSwipeAccessoryWidth + case .showNone: + break + } + DispatchQueue.main.asyncAfter(deadline: .now()) { + if shouldResetStatusOnAppear { + resetStatus() + } + } + } + .clipShape(Rectangle()) + .onReceive(resetNotice) { notice in + if cellID != notice.object as? UUID { + resetStatus() + currentCellID = notice.object as? UUID ?? nil + } + + } + .listRowInsets(EdgeInsets()) + + } + + func set(accessoryVisibility: CellSwipeAccessoryVisibility) { + self.accessoryVisibility = accessoryVisibility + } + + func resetStatus() { + accessoryVisibility = .showNone + withAnimation(.easeInOut) { + offset = 0 + leftOffset = -frameWidth + rightOffset = frameWidth + spaceWidth = 0 + } + currentCellID = nil + shouldResetStatusOnAppear = false + } + + func dismissNotification() { + NotificationCenter.default.post(name: .cellSwipeAccessoryVisibilityReset, object: nil) + } + + func getGesture() -> _EndedGesture<_ChangedGesture> { + return DragGesture(minimumDistance: 0) + .onChanged { value in + var dragWidth = value.translation.width + + self.shouldResetStatusOnAppear = false + + if currentCellID != cellID { + currentCellID = cellID + NotificationCenter.default.post(Notification(name: .cellSwipeAccessoryVisibilityReset, object: cellID)) + } + + switch accessoryVisibility { + + case .showNone: + if cellSwipeAccessoryPosition == .left { dragWidth = max(0, dragWidth) } + if cellSwipeAccessoryPosition == .right { dragWidth = min(0, dragWidth) } + + if dragWidth > cellSwipeAccessoryWidth { + dragWidth = cellSwipeAccessoryWidth + dragWidth / 10 + } + + if dragWidth < -cellSwipeAccessoryWidth { + dragWidth = -cellSwipeAccessoryWidth + dragWidth / 10 + } + + withAnimation(.easeInOut) { + offset = dragWidth + } + resetAccessoryOffset(position: .left, accessory: leftCellSwipeAccessory) + resetAccessoryOffset(position: .right, accessory: leftCellSwipeAccessory) + + case .showLeft: + if dragWidth < 0 { + withAnimation(.easeInOut) { + offset = cellSwipeAccessoryWidth + max(dragWidth, -cellSwipeAccessoryWidth) + resetAccessoryOffset(position: .left, accessory: leftCellSwipeAccessory) + resetAccessoryOffset(position: .right, accessory: rightCellSwipeAccessory) + } + } else { + withAnimation(.easeInOut) { + offset = cellSwipeAccessoryWidth + dragWidth / 10 + resetAccessoryOffset(position: .left, accessory: leftCellSwipeAccessory) + resetAccessoryOffset(position: .right, accessory: rightCellSwipeAccessory) + } + } + case .showRight: + if dragWidth > 0 { + withAnimation(.easeInOut) { + offset = -cellSwipeAccessoryWidth + min(dragWidth, cellSwipeAccessoryWidth) + resetAccessoryOffset(position: .left, accessory: leftCellSwipeAccessory) + resetAccessoryOffset(position: .right, accessory: rightCellSwipeAccessory) + } + } else { + withAnimation(.easeInOut) { + offset = -cellSwipeAccessoryWidth + dragWidth / 10 + resetAccessoryOffset(position: .left, accessory: leftCellSwipeAccessory) + resetAccessoryOffset(position: .right, accessory: rightCellSwipeAccessory) + } + } + } + } + .onEnded { value in + if currentCellID != cellID { + currentCellID = cellID + NotificationCenter.default.post(Notification(name: .cellSwipeAccessoryVisibilityReset, object: cellID)) + } + let dragWidth = value.translation.width + + let swipeCommitDistance: CGFloat = 30 + + switch accessoryVisibility { + case .showNone: + if abs(dragWidth) < swipeCommitDistance { + resetStatus() + return + } + + if (cellSwipeAccessoryPosition == .left || cellSwipeAccessoryPosition == .both) && dragWidth >= swipeCommitDistance { + withAnimation(CellSwipeAccessory.appearAnimation) { + offset = cellSwipeAccessoryWidth + resetAccessoryOffset(position: .left, accessory: leftCellSwipeAccessory) + resetAccessoryOffset(position: .right, accessory: rightCellSwipeAccessory) + set(accessoryVisibility: .showLeft) + } + return + } + + if (cellSwipeAccessoryPosition == .right || cellSwipeAccessoryPosition == .both) && dragWidth <= -swipeCommitDistance { + withAnimation(CellSwipeAccessory.appearAnimation) { + offset = -cellSwipeAccessoryWidth + resetAccessoryOffset(position: .left, accessory: leftCellSwipeAccessory) + resetAccessoryOffset(position: .right, accessory: rightCellSwipeAccessory) + set(accessoryVisibility: .showRight) + } + return + } + + case .showLeft: + if dragWidth > -swipeCommitDistance { + withAnimation(CellSwipeAccessory.appearAnimation) { + offset = cellSwipeAccessoryWidth + resetAccessoryOffset(position: .left, accessory: leftCellSwipeAccessory) + resetAccessoryOffset(position: .right, accessory: rightCellSwipeAccessory) + set(accessoryVisibility: .showRight) + } + } else { + resetStatus() + } + case .showRight: + if dragWidth < swipeCommitDistance { + withAnimation(CellSwipeAccessory.appearAnimation) { + offset = -cellSwipeAccessoryWidth + resetAccessoryOffset(position: .left, accessory: leftCellSwipeAccessory) + resetAccessoryOffset(position: .right, accessory: rightCellSwipeAccessory) + set(accessoryVisibility: .showRight) + } + } else { + resetStatus() + } + } + } + } +} + +extension View { + /// Similar to SwiftUI's `swipeActions`, this `swipeAction` modifier enables swipe actions for views which are not contained in a SwiftUI `List` + /// - Parameters: + /// - leftCellSwipeAccessory: the specifications for the left swipe accessory + /// - rightCellSwipeAccessory: the specifications for the left swipe accessory + @ViewBuilder public func swipeActions( + leftCellSwipeAccessory: CellSwipeAccessory?, + rightCellSwipeAccessory: CellSwipeAccessory? + ) -> some View { + self.modifier( + SwipeActionsModifier( + leftCellSwipeAccessory: leftCellSwipeAccessory, + rightCellSwipeAccessory: rightCellSwipeAccessory + ) + ) + + } +} + +private extension Notification.Name { + static let cellSwipeAccessoryVisibilityReset = Notification.Name(UUID().uuidString) +} + +public struct CellSwipeAccessoryVisibilityKey: EnvironmentKey { + public static var defaultValue: CellSwipeAccessoryVisibility = .showNone +} + +extension EnvironmentValues { + public var cellSwipeAccessoryVisibility: CellSwipeAccessoryVisibility { + get { self[CellSwipeAccessoryVisibilityKey.self] } + set { + self[CellSwipeAccessoryVisibilityKey.self] = newValue + } + } +} diff --git a/PlatformUI/PlatformUI/Components/WebView/PlatformWebView.swift b/PlatformUI/PlatformUI/Components/WebView/PlatformWebView.swift new file mode 100644 index 000000000..9be3de379 --- /dev/null +++ b/PlatformUI/PlatformUI/Components/WebView/PlatformWebView.swift @@ -0,0 +1,145 @@ +// +// PlatformWebView.swift +// PlatformUI +// +// Created by Rui Huang on 2/7/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI +import WebKit +import Utilities + +public class PlatformWebViewModel: PlatformViewModel { + @Published public var url: URL? { + didSet { + webViewDelegate.url = url + } + } + + public var pageLoaded: (() -> ())? { + didSet { + webViewDelegate.pageLoaded = pageLoaded + } + } + + public var canGoBack: Bool { + webView.canGoBack + } + + public var canGoForward: Bool { + webView.canGoForward + } + + public func goBack(){ + webView.goBack() + } + + public func goForward(){ + webView.goForward() + } + + private let webView = WKWebView() + private let webViewDelegate = WebViewDelegate() + + public static var previewValue: PlatformWebViewModel = { + let vm = PlatformWebViewModel() + vm.url = URL(string: "http://google.com") + return vm + }() + + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + self.webView.addObserver(self.webViewDelegate, forKeyPath: "URL", options: .new, context: nil) + self.webView.navigationDelegate = self.webViewDelegate + return AnyView( + Group { + if let url = self.url { + let request = URLRequest(url: url) + WebView(webView: self.webView, request: request) + } else { + PlatformView.nilView + } + } + ) + } + } + +} + +private class WebViewDelegate: NSObject, WKNavigationDelegate { + var url: URL? + + init(pageLoaded: (() -> ())? = nil) { + self.pageLoaded = pageLoaded + } + + var pageLoaded: (() -> ())? + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + DispatchQueue.main.async { [weak self] in + self?.pageLoaded?() + } + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if navigationAction.request.mainDocumentURL?.path == url?.path { + decisionHandler(.allow) + } else { + if let url = navigationAction.request.mainDocumentURL, URLHandler.shared?.canOpenURL(url) ?? false { + URLHandler.shared?.open(url, completionHandler: nil) + } + decisionHandler(.cancel) + } + } + + // Observe URL change + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if let key = change?[NSKeyValueChangeKey.newKey] { + // print("observeValue \(key)") // url value + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + self?.pageLoaded?() + } + } + } +} + +private struct WebView: UIViewRepresentable { + let request: URLRequest + var webView: WKWebView? + + init(webView: WKWebView?, request: URLRequest) { + self.webView = webView + self.request = request + } + + func makeUIView(context: Context) -> WKWebView { + let webView = webView ?? WKWebView() + webView.isOpaque = false; + webView.backgroundColor = .clear + return webView + } + + func updateUIView(_ uiView: WKWebView, context: Context) { + uiView.load(request) + } +} + +#if DEBUG +struct PlatformWebView_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + Group { + PlatformWebViewModel.previewValue + .createView() + .environmentObject(themeSettings) + .previewLayout(.sizeThatFits) + } + } +} +#endif + diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/SampleStyle.json b/PlatformUI/PlatformUI/DesignSystem/Theme/SampleStyle.json new file mode 100644 index 000000000..fad67b28f --- /dev/null +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/SampleStyle.json @@ -0,0 +1,52 @@ +{ + "default-style": { + "_fontSize": "largest", + "_fontType": "base", + "_layerColor": "transparent", + "_textColor": "text_secondary" + }, + "title-style": { + "_fontSize": "larger", + "_fontType": "base", + "_layerColor": "lowest", + "_textColor": "text_secondary" + }, + "subtitle-style": { + "_fontSize": "small", + "_fontType": "base", + "_textColor": "text_tertiary" + }, + + "table-cell-title-style": { + "_fontSize": "larger", + "_fontType": "base", + "_textColor": "text_secondary" + }, + "table-cell-subtitle-style": { + "_fontSize": "small", + "_fontType": "base", + "_textColor": "text_tertiary" + }, + "signed-plus": { + "_textColor": "color_green" + }, + "signed-minus": { + "_textColor": "color_red" + }, + "button-primary": { + "_layerColor": "buttonBackground", + "_textColor": "color_purple" + }, + "button-secondary": { + "_layerColor": "layer_6", + "_textColor": "text_primary" + }, + "button-disabled": { + "_layerColor": "layer_2", + "_textColor": "text_tertiary" + }, + "button-destructive": { + "_layerColor": "layer_3", + "_textColor": "color_red" + } +} diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/SampleTheme.json b/PlatformUI/PlatformUI/DesignSystem/Theme/SampleTheme.json new file mode 100644 index 000000000..ac88962b1 --- /dev/null +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/SampleTheme.json @@ -0,0 +1,85 @@ +{ + "id": "sample", + "themeColor": { + "layer": { + "lowest": "#000000", + "lower": "#18181B", + "low": "#0C0C0D", + "medium": "#18181B", + "high": "#252528", + "higher": "#313135", + "highest": "#3D3D42", + "buttonBackground": "#7774FF" + }, + "text": { + "dim": "#888891", + "medium": "#CACACE", + "bright": "#FAFAFA", + "purple": "#6966FF", + "yellow": "#FFCC48", + "green": "#3ED9A4", + "red": "#E45555", + "white": "#FFFFFF" + }, + "color": { + "text_primary": "#FAFAFA", + "text_secondary": "#CACACE", + "text_tertiary": "#888891", + + "layer_0": "#000000", + "layer_1": "#18181B", + "layer_2": "#0C0C0D", + "layer_3": "#18181B", + "layer_4": "#252528", + "layer_5": "#313135", + "layer_6": "#3D3D42", + "layer_7": "#494950", + + "color_purple": "#7774FF", + "color_green": "#1AFFB9", + "color_yellow": "#FFCC48", + "color_red": "#FF5C5C", + "color_black": "#000000", + "color_white": "#FFFFFF", + "color_faded_green": "#1AFFB929", + "color_faded_red": "#FF5C5C29", + "color_faded_yellow": "#FFCC4829", + + "border_default": "#313135", + "border_destructive": "#FF616133", + "border_button": "#FFFFFF33", + + "gradient_gray_start": "#313135", + "gradient_gray_end": "#18181B", + "gradient_green_start": "#17332C", + "gradient_green_end": "#313135", + "gradient_red_start": "#331A1A", + "gradient_red_end": "#313135" + } + }, + "themeFont": { + "size": { + "largest": "25", + "larger": "22", + "large": "18", + "medium": "16", + "small": "14.5", + "smaller": "13", + "smallest": "11" + }, + "type": { + "minus": { + "name": "Satoshi-Regular" + }, + "base": { + "name": "Satoshi-Medium" + }, + "plus": { + "name": "Satoshi-Bold" + }, + "number": { + "name": ".AppleSystemUIFontMonospaced-Medium" + } + } + } +} diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/Samples/SampleStyleLabel.swift b/PlatformUI/PlatformUI/DesignSystem/Theme/Samples/SampleStyleLabel.swift new file mode 100644 index 000000000..950a4e618 --- /dev/null +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/Samples/SampleStyleLabel.swift @@ -0,0 +1,59 @@ +// +// SampleStyleLabel.swift +// PlatformUI +// +// Created by Rui Huang on 8/23/22. +// + +import SwiftUI + +struct SampleStyleLabel: View, PlatformUIViewProtocol { + @ObservedObject var themeSettings = ThemeSettings.shared + + var parentStyle: ThemeStyle = ThemeStyle.defaultStyle + + var styleKey: String? = "title-style" + + var body: some View { + HStack { + Text("Title") + .themeStyle(style: style) + .environmentObject(themeSettings) + SampleSubtitleStyleLabel(parentStyle: style) + } + } +} + +struct SampleSubtitleStyleLabel: View, PlatformUIViewProtocol { + @ObservedObject var themeSettings = ThemeSettings.shared + + var parentStyle: ThemeStyle = ThemeStyle.defaultStyle + var styleKey: String? = "subtitle-style" + + var body: some View { + Text("Subtitle") + .themeStyle(style: style) + .environmentObject(themeSettings) + } +} + + +#if DEBUG +struct SampleStyleLabel_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ZStack { + VStack { + Spacer() + SampleStyleLabel() + Spacer() + } + } + .edgesIgnoringSafeArea(.bottom) + .edgesIgnoringSafeArea(.top) + .environmentObject(themeSettings) + } +} +#endif + diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/Samples/SampleThemeInput.swift b/PlatformUI/PlatformUI/DesignSystem/Theme/Samples/SampleThemeInput.swift new file mode 100644 index 000000000..79b2e7563 --- /dev/null +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/Samples/SampleThemeInput.swift @@ -0,0 +1,56 @@ +// +// SampleThemeInput.swift +// PlatformUI +// +// Created by Rui Huang on 8/9/22. +// + +import SwiftUI + +struct SampleThemeInput: View { + init(value: Binding, + prompt: String? = nil, + fontType: ThemeFont.FontType = .number, + fontSize: ThemeFont.FontSize = .larger) { + self.value = value + self.prompt = prompt + self.fontType = fontType + self.fontSize = fontSize + } + + let value: Binding + let prompt: String? + let fontType: ThemeFont.FontType + let fontSize: ThemeFont.FontSize + + var body: some View { + return TextField(prompt ?? "", text: value) + .themeColor(foreground: .textSecondary) + .themeColor(background: .layer1) + .themeFont(fontType: fontType, fontSize: fontSize) + } +} + +#if DEBUG +struct SampleThemeInput_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings() + @State static var textValue = "input value" + @State static var textValueEmpty = "" + static var previews: some View { + ZStack { + themeSettings.themeConfig.themeColor.color(of: .layer0) + VStack { + Spacer() + Group { + SampleThemeInput(value: $textValue) + SampleThemeInput(value: $textValueEmpty, prompt: "Enter Text") + }.padding() + Spacer() + } + } + .edgesIgnoringSafeArea(.bottom) + .edgesIgnoringSafeArea(.top) + .environmentObject(themeSettings) + } +} +#endif diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/Samples/SampleThemeLabel.swift b/PlatformUI/PlatformUI/DesignSystem/Theme/Samples/SampleThemeLabel.swift new file mode 100644 index 000000000..f5d3c2cc4 --- /dev/null +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/Samples/SampleThemeLabel.swift @@ -0,0 +1,69 @@ +// +// SampleThemeLabel.swift +// PlatformUI +// +// Created by Rui Huang on 8/9/22. +// + +import SwiftUI + +struct SampleThemeLabel: View { + init(text: String, + textColor: ThemeColor.SemanticColor = .textTertiary, + fontType: ThemeFont.FontType = .base, + fontSize: ThemeFont.FontSize = .largest) { + self.text = text + self.textColor = textColor + self.fontType = fontType + self.fontSize = fontSize + } + + let text: String + let textColor: ThemeColor.SemanticColor + let fontType: ThemeFont.FontType + let fontSize: ThemeFont.FontSize + + var body: some View { + Text(text) + .themeColor(foreground: textColor) + .themeFont(fontType: fontType, fontSize: fontSize) + + } +} + +#if DEBUG +struct SampleThemeLabel_Previews: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings() + + static var previews: some View { + ZStack { + themeSettings.themeConfig.themeColor.color(of: .layer0) + VStack { + Spacer() + Group { + SampleThemeLabel(text: "labelText", fontType: .minus) + SampleThemeLabel(text: "labelText", fontType: .base) + SampleThemeLabel(text: "labelText", fontType: .plus) + SampleThemeLabel(text: "labelText", fontType: .number) + } + Spacer() + Group { + SampleThemeLabel(text: "labelText", fontSize: .largest) + SampleThemeLabel(text: "labelText", fontSize: .larger) + SampleThemeLabel(text: "labelText", fontSize: .large) + } + Spacer() + Group { + SampleThemeLabel(text: "labelText", textColor: .textTertiary) + SampleThemeLabel(text: "labelText", textColor: .textSecondary) + SampleThemeLabel(text: "labelText", textColor: .textPrimary) + } + Spacer() + } + } + .edgesIgnoringSafeArea(.bottom) + .edgesIgnoringSafeArea(.top) + .environmentObject(themeSettings) + } +} +#endif diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeColorCache.swift b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeColorCache.swift new file mode 100644 index 000000000..2309b67e6 --- /dev/null +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeColorCache.swift @@ -0,0 +1,28 @@ +// +// ThemeColorCache.swift +// PlatformUI +// +// Created by Rui Huang on 10/4/23. +// + +import Foundation +import SwiftUI + +final class ThemeColorCache { + static let shared = ThemeColorCache() + + private var cache = [ThemeColor.SemanticColor: Color]() + + func get(_ key: ThemeColor.SemanticColor) -> Color? { + return cache[key] + } + + func set(_ key: ThemeColor.SemanticColor, color: Color) { + cache[key] = color + } + + func clear() { + cache = [:] + } +} + diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeConfig.swift b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeConfig.swift new file mode 100644 index 000000000..2f0fdb34c --- /dev/null +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeConfig.swift @@ -0,0 +1,496 @@ +// +// ThemeConfig.swift +// PlatformUI +// +// Created by Rui Huang on 8/8/22. +// + +import Foundation +import UIKit +import Utilities +import SwiftUI + +public final class ThemeSettings: ObservableObject, SingletonProtocol { + public static let shared = ThemeSettings() + + @Published public var themeConfig: ThemeConfig = .sampleThemeConfig { + didSet { + if themeConfig != oldValue { + ThemeColorCache.shared.clear() + ThemeFontCache.shared.clear() + } + } + } + @Published public var styleConfig: StyleConfig = .sampleStyleConfig + + public static var respondsToSystemTheme = true +} + +// MARK: - ThemeConfig + +public struct ThemeConfig: Codable, Equatable { + public let id: String + public let themeColor: ThemeColor + public let themeFont: ThemeFont +} + +public extension ThemeConfig { + static let sampleThemeConfig = load(configJson: "SampleTheme.json")! + + static func load(configJson: String, bundle: Bundle? = nil) -> ThemeConfig? { + _load(configJson: configJson, bundle: bundle) + } +} + +// MARK: - StyleConfig + +public struct StyleConfig: Codable { + public let styles: [String: ThemeStyle] +} + +public extension StyleConfig { + static let sampleStyleConfig = load(configJson: "SampleStyle.json") ?? StyleConfig(styles: [:]) + + static func load(configJson: String, bundle: Bundle? = nil) -> StyleConfig? { + let styles: [String: ThemeStyle]? = _load(configJson: configJson, bundle: bundle) + if let styles = styles { + return StyleConfig(styles: styles) + } else { + return nil + } + } +} + +public struct ThemeStyle: Codable { + init(_fontSize: String? = nil, _fontType: String? = nil, _layerColor: String? = nil, _textColor: String? = nil) { + self._fontSize = _fontSize + self._fontType = _fontType + self._layerColor = _layerColor + self._textColor = _textColor + } + + let _fontSize, _fontType, _layerColor, _textColor: String? +} + +public extension ThemeStyle { + static let defaultStyle: ThemeStyle = ThemeSettings.shared.styleConfig.styles["default-style"]! + + var fontSize: ThemeFont.FontSize? { + guard let _fontSize = _fontSize else { + return nil + } + + return ThemeFont.FontSize(rawValue: _fontSize) + } + + var fontType: ThemeFont.FontType? { + guard let _fontType = _fontType else { + return nil + } + + return ThemeFont.FontType(rawValue: _fontType) + } + + var layerColor: ThemeColor.SemanticColor? { + guard let _layerColor = _layerColor else { + return nil + } + + return ThemeColor.SemanticColor(rawValue: _layerColor) + } + + var textColor: ThemeColor.SemanticColor? { + guard let _textColor = _textColor else { + return nil + } + + return ThemeColor.SemanticColor(rawValue: _textColor) + } + + func merge(from newStyle: Self) -> Self { + let _fontSize = newStyle._fontSize ?? _fontSize + let _fontType = newStyle._fontType ?? _fontType + let _layerColor = newStyle._layerColor ?? _layerColor + let _textColor = newStyle._textColor ?? _textColor + return Self.init(_fontSize: _fontSize, _fontType: _fontType, _layerColor: _layerColor, _textColor: _textColor) + } + + func themeColor(foreground: ThemeColor.SemanticColor) -> Self { + let newStyle = ThemeStyle(_textColor: foreground.rawValue) + return merge(from: newStyle) + } + + func themeColor(background: ThemeColor.SemanticColor) -> Self { + let newStyle = ThemeStyle(_layerColor: background.rawValue) + return merge(from: newStyle) + } + + func themeFont(fontType: ThemeFont.FontType? = nil, fontSize: ThemeFont.FontSize = .medium) -> Self { + let fontType = fontType ?? .base + let newStyle = ThemeStyle(_fontSize: fontSize.rawValue, _fontType: fontType.rawValue) + return merge(from: newStyle) + } +} + + +// MARK: - Color + +public struct ThemeColor: Codable, Equatable { + let color : [String: String] + + public enum SemanticColor: Hashable { + case transparent + + case textPrimary + case textSecondary + case textTertiary + + case layer0 + case layer1 + case layer2 + case layer3 + case layer4 + case layer5 + case layer6 + case layer7 + + case borderDefault + case borderDestructive + case borderButton + + case colorPurple + case colorYellow + case colorGreen + case colorRed + case colorWhite + case colorBlack + case colorFadedPurple + case colorFadedGreen + case colorFadedRed + case colorFadedYellow + + case custom(rgb: String, alpha: Double?) + + init(rawValue: String) { + switch rawValue { + case "transparent": self = .transparent + + case "color_purple": self = .colorPurple + case "color_yellow": self = .colorYellow + case "color_green": self = .colorGreen + case "color_red": self = .colorRed + case "color_white": self = .colorWhite + case "color_black": self = .colorBlack + case "color_faded_purple": self = .colorFadedPurple + case "color_faded_green": self = .colorFadedGreen + case "color_faded_red": self = .colorFadedRed + case "color_faded_yellow": self = .colorFadedYellow + + case "layer_0": self = .layer0 + case "layer_1": self = .layer1 + case "layer_2": self = .layer2 + case "layer_3": self = .layer3 + case "layer_4": self = .layer4 + case "layer_5": self = .layer5 + case "layer_6": self = .layer6 + case "layer_7": self = .layer7 + + case "text_primary": self = .textPrimary + case "text_secondary": self = .textSecondary + case "text_tertiary": self = .textTertiary + + case "border_default": self = .borderDefault + case "border_destructive": self = .borderDestructive + case "border_button": self = .borderButton + default: + if let custom = parseCustomRawValue(rawValue: rawValue) { + self = .custom(rgb: custom.rgb, alpha: custom.alpha) + } else { + assertionFailure("Unable to parse color \(rawValue)") + self = .transparent + } + } + } + + public var rawValue: String { + switch self { + case .transparent: return "transparent" + + case .layer0: return "layer_0" + case .layer1: return "layer_1" + case .layer2: return "layer_2" + case .layer3: return "layer_3" + case .layer4: return "layer_4" + case .layer5: return "layer_5" + case .layer6: return "layer_6" + case .layer7: return "layer_7" + + case .textPrimary: return "text_primary" + case .textSecondary: return "text_secondary" + case .textTertiary: return "text_tertiary" + + case .borderDefault: return "border_default" + case .borderDestructive: return "border_destructive" + case .borderButton: return "border_button" + + case .colorPurple: return "color_purple" + case .colorYellow: return "color_yellow" + case .colorGreen: return "color_green" + case .colorRed: return "color_red" + case .colorWhite: return "color_white" + case .colorBlack: return "color_black" + case .colorFadedPurple: return "color_faded_purple" + case .colorFadedGreen: return "color_faded_green" + case .colorFadedRed: return "color_faded_red" + case .colorFadedYellow: return "color_faded_yellow" + case .custom(rgb: let rgb, let alpha): return "\(rgb),\(String(describing: alpha))" + } + } + } +} + +public extension ThemeColor { + private func uiColor(of semanticColor: SemanticColor) -> UIColor { + switch semanticColor { + case .transparent: + return UIColor.clear + case .custom(rgb: let rgb, alpha: let alpha): + return UIColor(hex: rgb)?.withAlphaComponent(alpha ?? 1) ?? .clear + default: + assert(UIColor(hex: color[semanticColor.rawValue]) != nil, "color does not exist: \(semanticColor.rawValue)") + return UIColor(hex: color[semanticColor.rawValue]) ?? .clear + } + } + + func color(of layerColor: SemanticColor) -> Color { + if let color = ThemeColorCache.shared.get(layerColor) { + return color + } + let uiColor = uiColor(of: layerColor) + let color = Color(uiColor: uiColor) + ThemeColorCache.shared.set(layerColor, color: color) + return color + } +} + +// MARK: - Font + +public struct ThemeFont: Codable, Equatable { + let size: [String: String] + let type: [String: FontTypeDetail] + + public enum FontSize: Hashable { + case largest, larger, large, medium, small, smaller, smallest + case custom(size: Float) + + init(rawValue: String) { + switch rawValue { + case "largest": self = .largest + case "larger": self = .larger + case "large": self = .large + case "medium": self = .medium + case "small": self = .small + case "smaller": self = .smaller + case "smallest": self = .smallest + + default: + if let size = Float(rawValue), size > 0 { + self = .custom(size: size) + } else { + assertionFailure("Unable to parse font size \(rawValue)") + self = .medium + } + } + } + + var rawValue: String { + switch self { + case .largest: return "largest" + case .larger: return "larger" + case .large: return "large" + case .medium: return "medium" + case .small: return "small" + case .smaller: return "smaller" + case .smallest: return "smallest" + case .custom(size: let size): return String(size) + } + } + } + + public enum FontType: Hashable { + case minus, base, plus, number + case custom(name: String) + + init(rawValue: String) { + switch rawValue { + case "minus": self = .minus + case "base": self = .base + case "plus": self = .plus + case "number": self = .number + default: self = .custom(name: rawValue) + } + } + + var rawValue: String { + switch self { + case .minus: return "minus" + case .base: return "base" + case .plus: return "plus" + case .number: return "number" + case .custom(name: let name): return name + } + } + + var defaultWeight: UIFont.Weight { + switch self { + case .minus: + return .regular + case .base: + return .medium + case .plus: + return .bold + case .number: + return .medium + case .custom(let name): + preconditionFailure("unrecognized font type \(name)") + return .regular + } + } + + var defaultFontName: String { + switch self { + case .minus: + return UIFont.systemFont(ofSize: 0).fontName + case .base: + return UIFont.systemFont(ofSize: 0).fontName + case .plus: + return UIFont.boldSystemFont(ofSize: 0).fontName + case .number: + return UIFont.monospacedSystemFont(ofSize: 0, weight: defaultWeight).fontName + case .custom(let name): + preconditionFailure("unrecognized font type \(name)") + return UIFont.systemFont(ofSize: 0).fontName + } + } + } +} + +public struct FontTypeDetail: Codable, Equatable { + let name: String? +} + +public extension ThemeFont { + + func uiFont(of fontType: FontType, fontSize: FontSize) -> UIFont? { + let sizeValue: Float + switch fontSize { + case .custom(size: let size): + sizeValue = size + default: + if let sizeString = size[fontSize.rawValue], let size = Float(sizeString) { + sizeValue = size + } else { + // assertionFailure("fontSize not found \(size)") + return nil + } + } + + if let fontName = type[fontType.rawValue]?.name, fontName.length > 0 { + return loadFont(name: fontName, + size: sizeValue) + } + switch fontType { + case .custom: + return nil + case .number: + return UIFont.monospacedSystemFont(ofSize: CGFloat(sizeValue), weight: fontType.defaultWeight) + case .plus: + return UIFont.boldSystemFont(ofSize: CGFloat(sizeValue)).withWeight(fontType.defaultWeight) + case .base: + return UIFont.systemFont(ofSize: CGFloat(sizeValue)).withWeight(fontType.defaultWeight) + case .minus: + return UIFont.systemFont(ofSize: CGFloat(sizeValue)).withWeight(fontType.defaultWeight) + } + } + + private func loadFont(name: String, size: Float) -> UIFont? { + let font = UIFont(name: name, size: CGFloat(size)) + guard let font = font else { + assertionFailure("Font not found: \(name) \(size)") + return nil + } + return font + } + + func font(of fontType: FontType, fontSize: FontSize) -> Font? { + if let cached = ThemeFontCache.shared.get(fontType: fontType, fontSize: fontSize) { + return cached + } + if let uiFont = uiFont(of: fontType, fontSize: fontSize) { + let font = Font(uiFont as CTFont) + ThemeFontCache.shared.set(fontType: fontType, fontSize: fontSize, font: font) + return font + } + return nil + } +} + +public extension ThemeColor.SemanticColor { + var uiColor: UIColor { + ThemeSettings.shared.themeConfig.themeColor.uiColor(of: self) + } + var color: Color { + ThemeSettings.shared.themeConfig.themeColor.color(of: self) + } +} + + +// Private + +private func _load(configJson: String, bundle: Bundle? = nil) -> T? { + let bundle = bundle ?? Bundle(for: PlatformUIBundleClass.self) + if let url = bundle.url(forResource: configJson, withExtension: "") { + do { + let data = try Data(contentsOf: url) + let obj = try? JSONDecoder().decode(T.self, from: data) + return obj + } catch { + print("error:\(error)") + } + } + return nil +} + +private func parseCustomRawValue(rawValue: String) -> (rgb: String, alpha: Double?)? { + if rawValue.starts(with: "#") { + let split = rawValue.split(separator: ",") + var alpha: Double? = 1.0 + if split.count > 1 { + let value = String(split[1]) + alpha = Parser().asNumber(value)?.doubleValue + } + if split.count > 0, + UIColor(hex: String(split[0])) != nil { + return (rgb: String(split[0]), alpha: alpha) + } + } + return nil +} + + +extension UIFont { + func withWeight(_ weight: UIFont.Weight) -> UIFont { + var attributes = fontDescriptor.fontAttributes + var traits = (attributes[.traits] as? [UIFontDescriptor.TraitKey: Any]) ?? [:] + + traits[.weight] = weight + + attributes[.name] = nil + attributes[.traits] = traits + attributes[.family] = familyName + + let descriptor = UIFontDescriptor(fontAttributes: attributes) + + return UIFont(descriptor: descriptor, size: pointSize) + } +} diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeFontCache.swift b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeFontCache.swift new file mode 100644 index 000000000..fe8e7702a --- /dev/null +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeFontCache.swift @@ -0,0 +1,34 @@ +// +// ThemeFontCache.swift +// PlatformUI +// +// Created by Rui Huang on 10/4/23. +// + +import Foundation +import SwiftUI + +final class ThemeFontCache { + static let shared = ThemeFontCache() + + private var cache = [Key: Font]() + + func get(fontType: ThemeFont.FontType, fontSize: ThemeFont.FontSize) -> Font? { + let key = Key(type: fontType, size: fontSize) + return cache[key] + } + + func set(fontType: ThemeFont.FontType, fontSize: ThemeFont.FontSize, font: Font) { + let key = Key(type: fontType, size: fontSize) + cache[key] = font + } + + func clear() { + cache = [:] + } +} + +private struct Key: Hashable { + let type: ThemeFont.FontType + let size: ThemeFont.FontSize +} diff --git a/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift new file mode 100644 index 000000000..91a02aa4b --- /dev/null +++ b/PlatformUI/PlatformUI/DesignSystem/Theme/ThemeViewModifiers.swift @@ -0,0 +1,716 @@ +// +// ThemeViewModifiers.swift +// PlatformUI +// +// Created by Rui Huang on 8/10/22. +// + +import SwiftUI +import SDWebImageSwiftUI +import Utilities + +// MARK: Color + +public extension View { + func themeColor(foreground: ThemeColor.SemanticColor) -> some View { + modifier(TextColorModifier(textColor: foreground)) + } + + func themeColor(background: ThemeColor.SemanticColor) -> some View { + modifier(BackgroundColorModifier(layerColor: background)) + } + + func themeGradient(background: ThemeColor.SemanticColor, + gradientColor: Color, + intensityLayerColor: Double = 0.95, + intensityGradientColor: Double = 0.05) -> some View { + modifier(GradientColorModifier(layerColor: background, + gradientColor: gradientColor, + intensityLayerColor: intensityLayerColor, + intensityGradientColor: intensityGradientColor)) + } +} + +public extension Text { + func themeColor(foreground: ThemeColor.SemanticColor) -> Text { + self.foregroundColor(foreground.color) + } +} + +private struct TextColorModifier: ViewModifier { + @EnvironmentObject var themeSettings: ThemeSettings + + let textColor: ThemeColor.SemanticColor + + func body(content: Content) -> some View { + content + .foregroundColor(themeSettings.themeConfig.themeColor.color(of: textColor)) + } +} + +private struct BackgroundColorModifier: ViewModifier { + @EnvironmentObject var themeSettings: ThemeSettings + + let layerColor: ThemeColor.SemanticColor + + func body(content: Content) -> some View { + content + .background(themeSettings.themeConfig.themeColor.color(of: layerColor)) + } +} + +private struct GradientColorModifier: ViewModifier { + @EnvironmentObject var themeSettings: ThemeSettings + + let layerColor: ThemeColor.SemanticColor + let gradientColor: Color + let intensityLayerColor: Double + let intensityGradientColor: Double + + fileprivate init(layerColor: ThemeColor.SemanticColor, gradientColor: Color, intensityLayerColor: Double = 0.95, intensityGradientColor: Double = 0.05) { + self.layerColor = layerColor + self.gradientColor = gradientColor + self.intensityLayerColor = intensityLayerColor + self.intensityGradientColor = intensityGradientColor + } + + func body(content: Content) -> some View { + let layerColor = themeSettings.themeConfig.themeColor.color(of: layerColor) + let blendedColor = Color(UIColor.blend(color1: UIColor(layerColor), intensity1: intensityLayerColor, color2: UIColor(gradientColor), intensity2: intensityGradientColor)) + + let gradient = LinearGradient( + gradient: Gradient(colors: [ + layerColor, + blendedColor]), + startPoint: .leading, endPoint: .trailing) + + content + .background(gradient) + } +} + +public extension Image { + /// iniitalizes an image that supports app themes + /// - Parameters: + /// - themedImageBaseName: the base image name. e.g. if the app supports 3 themes and the corresponding themed image names are "circle_light" "circle_dark" and "circle_classic_dark" then your base name is "circle" + /// - bundle: the bundle + init(themedImageBaseName: String, bundle: Bundle, themeSettings: ThemeSettings = ThemeSettings.shared) { + self.init(themedImageBaseName + "_" + "\(themeSettings.themeConfig.id)", bundle: bundle) + } + + func templateColor(_ foreground: ThemeColor.SemanticColor?) -> some View { + if let foreground = foreground { + return AnyView(self.renderingMode(.template).themeColor(foreground: foreground)) + } + return AnyView(self) + } +} + +public extension WebImage { + func templateColor(_ foreground: ThemeColor.SemanticColor?) -> some View { + if let foreground = foreground { + return AnyView(self.renderingMode(.template).themeColor(foreground: foreground)) + } + return AnyView(self) + } +} + +// MARK: Font + +public extension View { + func themeFont(fontType: ThemeFont.FontType? = nil, fontSize: ThemeFont.FontSize = .medium) -> some View { + let fontType = fontType ?? .base + return modifier(ThemeFontModifier(fontType: fontType, fontSize: fontSize)) + } +} + +private struct ThemeFontModifier: ViewModifier { + @EnvironmentObject var themeSettings: ThemeSettings + + let fontType: ThemeFont.FontType + let fontSize: ThemeFont.FontSize + + func body(content: Content) -> some View { + content + .font(themeSettings.themeConfig.themeFont.font(of: fontType, fontSize: fontSize)) + } +} + +public extension Text { + func themeFont(fontType: ThemeFont.FontType = .base, fontSize: ThemeFont.FontSize = .medium) -> Text { + return self.font(ThemeSettings.shared.themeConfig.themeFont.font(of: fontType, fontSize: fontSize)) + } +} + +// MARK: Style + +public extension View { + func themeStyle(style: ThemeStyle) -> some View { + modifier(StyleModifier(style: style)) + } + + func themeStyle(styleKey: String, parentStyle: ThemeStyle) -> some View { + modifier(StyleKeyModifier(styleKey: styleKey, parentStyle: parentStyle)) + } +} + +private struct StyleModifier: ViewModifier { + @EnvironmentObject var themeSettings: ThemeSettings + + let style: ThemeStyle + + func body(content: Content) -> some View { + if let fontType = style.fontType, let fontSize = style.fontSize, let textColor = style.textColor, let layerColor = style.layerColor { + content + .themeFont(fontType: fontType, fontSize: fontSize) + .themeColor(foreground: textColor) + .themeColor(background: layerColor) + } else { + // assertionFailure("StyleModifier: style not complete") + content + } + } +} + +private struct StyleKeyModifier: ViewModifier { + @EnvironmentObject var themeSettings: ThemeSettings + + let styleKey: String + let parentStyle: ThemeStyle + + func body(content: Content) -> some View { + if let style = themeSettings.styleConfig.styles[styleKey] { + content + .themeStyle(style: parentStyle.merge(from: style)) + } else { + content + } + } +} + +// MARK: Sheet + +public enum MakeSheetStyle { + case fullScreen, fitSize + /// note this style behaves significantly different. It takes content and rounds the corners, adds insets and safe area ignore. + /// only use on views presented with presentOverFullSreen presentation style + /// these sheets are not drag-to-dismiss + case forPresentedOverCurrentScreen +} + +public extension View { + func makeSheet(sheetStyle: MakeSheetStyle = .fullScreen) -> some View { + modifier(SheetViewModifier(sheetStyle: sheetStyle)) + } +} + +private struct SheetViewModifier: ViewModifier { + let topPadding: CGFloat = 18 + let sheetStyle: MakeSheetStyle + + @EnvironmentObject var themeSettings: ThemeSettings + + func body(content: Content) -> some View { + let dragIndicator = Rectangle() + .themeColor(background: .layer1) + .frame(width: 36, height: 4) + .clipShape(Capsule()) + .padding(.top, topPadding) + + switch sheetStyle { + case .fullScreen: + return AnyView( + ZStack(alignment: .top) { + content + .cornerRadius(36, corners: [.topLeft, .topRight]) + VStack { + dragIndicator + Spacer() + } + } + .environmentObject(themeSettings) + ) + case .fitSize: + return AnyView( + ZStack(alignment: .bottom) { + VStack(spacing: 0) { + Spacer() + ZStack(alignment: .top) { + content + .cornerRadius(36, corners: [.topLeft, .topRight]) + VStack { + dragIndicator + } + } + } + .environmentObject(themeSettings) + } + ) + + case .forPresentedOverCurrentScreen: + return ZStack(alignment: .bottom) { + ThemeColor.SemanticColor.layer0.color + .opacity(0.8) + content + .padding(.top, 24) + .padding(.bottom, max((content.safeAreaInsets?.bottom ?? 0), 16)) + .padding(.horizontal, 24) + .themeColor(background: .layer3) + .cornerRadius(36, corners: [.topLeft, .topRight]) + } + .ignoresSafeArea(edges: [.all]) + .wrappedInAnyView() + } + } +} +// MARK: Make any view a button + +public extension View { + func makeButton(style: ThemeStyle = ThemeStyle.defaultStyle, action: @escaping (() -> Void)) -> some View { + modifier(ButtonViewModifier(style: style, action: action)) + } +} + +private struct ButtonViewModifier: ViewModifier { + let style: ThemeStyle + let action: (() -> Void) + + @EnvironmentObject var themeSettings: ThemeSettings + + func body(content: Content) -> some View { + PlatformButtonViewModel(content: content.wrappedViewModel, type: .iconType) { + action() + } + .createView(parentStyle: style) + .environmentObject(themeSettings) + } +} + +// MARK: CornerRadius + +public extension View { + func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { + clipShape(RoundedCorner(radius: radius, corners: corners) ) + } +} + +private struct RoundedCorner: Shape { + var radius: CGFloat = .infinity + var corners: UIRectCorner = .allCorners + + func path(in rect: CGRect) -> Path { + let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) + return Path(path.cgPath) + } +} + +// MARK: Border + +public extension View { + func border(borderWidth: CGFloat = 1, cornerRadius: CGFloat = 0, borderColor: Color? = ThemeColor.SemanticColor.layer5.color) -> some View { + modifier(BorderModifier(cornerRadius: cornerRadius, borderWidth: borderWidth, borderColor: borderColor)) + } + + func borderAndClip(style: ClipStyle, borderColor: ThemeColor.SemanticColor, lineWidth: CGFloat = 1) -> some View { + modifier(BorderAndClipModifier(style: style, borderColor: borderColor, lineWidth: lineWidth)) + } +} + +/// The clip shape/style +public enum ClipStyle { + /// A rectangular shape with rounded corners with specified corner radius, aligned inside the frame of the view containing it. + case cornerRadius(CGFloat) + /// A capsule shape is equivalent to a rounded rectangle where the corner radius is chosen as half the length of the rectangle’s smallest edge. + case capsule + case circle +} + +private struct BorderAndClipModifier: ViewModifier { + let style: ClipStyle + let borderColor: ThemeColor.SemanticColor + let lineWidth: CGFloat + + func body(content: Content) -> some View { + switch style { + case .circle: + content + .clipShape(Circle()) + .overlay(Circle() + .strokeBorder(borderColor.color, lineWidth: lineWidth)) + + case .cornerRadius(let cornerRadius): + content + .clipShape(.rect(cornerRadius: cornerRadius)) + .overlay(RoundedRectangle(cornerRadius: cornerRadius) + .strokeBorder(borderColor.color, lineWidth: lineWidth)) + + case .capsule: + content + .clipShape(Capsule()) + .overlay(Capsule() + .strokeBorder(borderColor.color, lineWidth: lineWidth)) + } + } +} + + +private struct BorderModifier: ViewModifier { + var cornerRadius: CGFloat = .infinity + var borderWidth: CGFloat = 1 + var borderColor: Color? = ThemeColor.SemanticColor.layer5.color + + @EnvironmentObject var themeSettings: ThemeSettings + + func body(content: Content) -> some View { + content + .clipShape(.rect(cornerRadius: cornerRadius)) + .overlay( + RoundedRectangle(cornerRadius: cornerRadius) + .stroke(borderColor ?? .clear, lineWidth: borderWidth) + ) + .padding(borderWidth) + .environmentObject(themeSettings) + } +} + + +// MARK: List + +public extension View { + func animateHeight(height: CGFloat) -> some View { + modifier(AnimatingViewHeight(height: height)) + } +} + +private struct AnimatingViewHeight: AnimatableModifier { + var height: CGFloat = 0 + + var animatableData: CGFloat { + get { height } + set { height = newValue } + } + + func body(content: Content) -> some View { + content.frame(height: height) + } +} + +// MARK: LeftAligned + +public extension View { + func leftAligned() -> some View { + modifier(LeftAlignedModifier()) + } +} + +private struct LeftAlignedModifier: ViewModifier { + func body(content: Content) -> some View { + HStack(spacing: 0) { + content + Spacer(minLength: 0) + } + } +} + +// MARK: RightAligned + +public extension View { + func rightAligned() -> some View { + modifier(RightAlignedModifier()) + } +} + +private struct RightAlignedModifier: ViewModifier { + func body(content: Content) -> some View { + HStack { + Spacer() + content + } + } +} + +// MARK: TopAligned + +public extension View { + func topAligned() -> some View { + modifier(TopAlignedModifier()) + } +} + +private struct TopAlignedModifier: ViewModifier { + func body(content: Content) -> some View { + VStack(spacing: 0) { + content + Spacer(minLength: 0) + } + } +} + +// MARK: BottomAligned + +public extension View { + func bottomAligned() -> some View { + modifier(BottomAlignedModifier()) + } +} + +private struct BottomAlignedModifier: ViewModifier { + func body(content: Content) -> some View { + VStack(spacing: 0) { + Spacer(minLength: 0) + content + } + } +} + +// MARK: CenterAligned + +public extension View { + func centerAligned() -> some View { + modifier(CenterAlignedModifier()) + } +} + +private struct CenterAlignedModifier: ViewModifier { + func body(content: Content) -> some View { + // forcing minLength to 0 does make a difference, default behavior is black box behavior which seems to vary + HStack(spacing: 0) { + Spacer(minLength: 0) + VStack(spacing: 0) { + Spacer(minLength: 0) + content + Spacer(minLength: 0) + } + Spacer(minLength: 0) + } + } +} + +// MARK: NavigationView + +public extension View { + func navigationViewEmbedded(backgroundColor: Color?) -> some View { + modifier(NavigationViewEmbeddedModifier(backgroundColor: backgroundColor)) + } +} + +private struct NavigationViewEmbeddedModifier: ViewModifier { + let backgroundColor: Color? + + func body(content: Content) -> some View { + NavigationView { + if let backgroundColor = backgroundColor { + ZStack { + backgroundColor.edgesIgnoringSafeArea(.all) + content + } + .navigationBarHidden(true) + } else { + content + .navigationBarHidden(true) + } + } + } +} + +// MARK: Circle background + +public extension View { + func circleBackground(size: CGSize = CGSize(width: 48, height: 48), color: ThemeColor.SemanticColor = .layer6) -> some View { + modifier(CircleBackgroundModifier(size: size, color: color)) + } +} + +private struct CircleBackgroundModifier: ViewModifier { + let size: CGSize + let color: ThemeColor.SemanticColor + + func body(content: Content) -> some View { + ZStack { + content + } + .frame(width: size.width, height: size.height) + .themeColor(background: color) + .clipShape(Circle()) + } +} + +public extension View { + func flipped(_ axis: Axis = .horizontal, anchor: UnitPoint = .center) -> some View { + switch axis { + case .horizontal: + return scaleEffect(CGSize(width: -1, height: 1), anchor: anchor) + case .vertical: + return scaleEffect(CGSize(width: 1, height: -1), anchor: anchor) + } + } +} + +// MARK: AttributedString + +public extension AttributedString { + /// Applies a font to the attributed string. + /// - Parameters: + /// - foreground: the font to apply + /// - range: the range to modify, `nil` if the entire string should be modified + func themeFont(fontType: ThemeFont.FontType = .base, fontSize: ThemeFont.FontSize = .medium, to range: Range? = nil) -> Self { + var string = self + if let range = range { + string[range].font = ThemeSettings.shared.themeConfig.themeFont.font(of: fontType, fontSize: fontSize) + } else { + string.font = ThemeSettings.shared.themeConfig.themeFont.font(of: fontType, fontSize: fontSize) + } + return string + } + + /// Applies a foreground color to the attributed string. + /// - Parameters: + /// - foreground: the color to apply + /// - range: the range to modify, `nil` if the entire string should be modified + func themeColor(foreground: ThemeColor.SemanticColor, to range: Range? = nil) -> Self { + var string = self + if let range = range { + string[range].foregroundColor = ThemeSettings.shared.themeConfig.themeColor.color(of: foreground) + } else { + string.foregroundColor = ThemeSettings.shared.themeConfig.themeColor.color(of: foreground) + } + return string + } + + func dottedUnderline(foreground: ThemeColor.SemanticColor, for range: Range? = nil) -> Self { + var string = self + let range = range ?? string.startIndex.. some View { + if let keyboardToolbarContent = keyboardToolbarContent { + return modifier(KeyboardAccessoryModifier(keyboardToolbarContent: keyboardToolbarContent, background: background)) + } + + let text = Text(DataLocalizer.localize(path: "APP.GENERAL.DONE")) + .themeColor(foreground: .colorWhite) + .themeFont(fontSize: .small) + let keyboardToolbarContent = AnyView( + PlatformButtonViewModel(content: text.wrappedViewModel, + type: .pill) { + PlatformView.hideKeyboard() + } + .createView(parentStyle: parentStyle) + ) + return modifier(KeyboardAccessoryModifier(keyboardToolbarContent: keyboardToolbarContent, background: background)) + } +} + +// MARK: Bullet list + +public extension View { + func bulletItem() -> some View { + modifier(BulletItemModifier()) + } +} + +private struct BulletItemModifier: ViewModifier { + func body(content: Content) -> some View { + HStack(alignment: .top) { + Text("\u{2022}") + content + } + } +} + +private struct KeyboardAccessoryModifier: ViewModifier { + let keyboardToolbarContent: AnyView + let background: ThemeColor.SemanticColor + + func body(content: Content) -> some View { + ZStack { + + background.color + .edgesIgnoringSafeArea(.all) + + content + .themeColor(background: background) + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + Spacer() + keyboardToolbarContent + } + } + } + } +} + +public extension PlatformView { + static func hideKeyboard() { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } +} + +private struct TruncateWithoutEllipses: ViewModifier { + func body(content: Content) -> some View { + ZStack(alignment: .leading) { + content.hidden().layoutPriority(1) + content.fixedSize(horizontal: true, vertical: false) + } + .clipped() + } +} + +public enum ExtendedTruncationMode { + case noEllipsis +} + +public extension View { + func truncationMode(_ mode: ExtendedTruncationMode) -> some View { + switch mode { + case .noEllipsis: + return self.modifier(TruncateWithoutEllipses()) + } + } +} + +public extension View { + func wrappedInAnyView() -> AnyView { + AnyView(self) + } +} + +// MARK: ScrollView + +public extension View { + func disableBounces() -> some View { + modifier(DisableBouncesModifier()) + } +} + +struct DisableBouncesModifier: ViewModifier { + func body(content: Content) -> some View { + content + .onAppear { + UIScrollView.appearance().bounces = false + } + .onDisappear { + UIScrollView.appearance().bounces = true + } + } +} + +// MARK: Conditional + +public extension View { + @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { + if condition { + transform(self) + } else { + self + } + } +} diff --git a/PlatformUI/PlatformUI/Media.xcassets/Contents.json b/PlatformUI/PlatformUI/Media.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/PlatformUI/PlatformUI/Media.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlatformUI/PlatformUI/Media.xcassets/combo_box_tick.imageset/Contents.json b/PlatformUI/PlatformUI/Media.xcassets/combo_box_tick.imageset/Contents.json new file mode 100644 index 000000000..ecc01b199 --- /dev/null +++ b/PlatformUI/PlatformUI/Media.xcassets/combo_box_tick.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Vector 209.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/PlatformUI/PlatformUI/Media.xcassets/combo_box_tick.imageset/Vector 209.svg b/PlatformUI/PlatformUI/Media.xcassets/combo_box_tick.imageset/Vector 209.svg new file mode 100644 index 000000000..2d1bfe017 --- /dev/null +++ b/PlatformUI/PlatformUI/Media.xcassets/combo_box_tick.imageset/Vector 209.svg @@ -0,0 +1,3 @@ + + + diff --git a/PlatformUI/PlatformUI/PlatformListViewModel.swift b/PlatformUI/PlatformUI/PlatformListViewModel.swift new file mode 100644 index 000000000..43529ff1d --- /dev/null +++ b/PlatformUI/PlatformUI/PlatformListViewModel.swift @@ -0,0 +1,111 @@ +// +// PlatformListViewModel.swift +// PlatformUI +// +// Created by Rui Huang on 3/10/23. +// + +import SwiftUI +import Utilities +import Combine + +open class PlatformListViewModel: PlatformViewModeling { + private let firstListItemTopSeparator: Bool + private let lastListItemBottomSeparator: Bool + private let intraItemSeparator: Bool + + public var items: [PlatformViewModel] = [] { + didSet { + contentChanged?() + } + } + + + public var width: CGFloat? { + didSet { + if width != oldValue { + contentChanged?() + } + } + } + + open var header: PlatformViewModel? { nil } + open var footer: PlatformViewModel? { nil } + open var placeholder: PlatformViewModel? { nil } + + // contentChanged is required because the list view model returns a ForEach struct + // which does not observe the content change. Caller should supply a contentChanged block + // that manually triggers the parent view model's objectWillChange.send() + + public var contentChanged: (() -> Void)? + + public init(items: [PlatformViewModel] = [], + intraItemSeparator: Bool = true, + firstListItemTopSeparator: Bool = false, + lastListItemBottomSeparator: Bool = false, + contentChanged: (() -> Void)? = nil) { + self.items = items + self.intraItemSeparator = intraItemSeparator + self.firstListItemTopSeparator = firstListItemTopSeparator + self.lastListItemBottomSeparator = lastListItemBottomSeparator + self.contentChanged = contentChanged + } + + open func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> AnyView { + + let itemsOrPlaceholder = items.count > 0 ? items : [placeholder ?? .init(bodyBuilder: nil)] + let list: [PlatformViewModel] + if let header, let footer { + list = [header] + itemsOrPlaceholder + [footer] + } else if let header { + list = [header] + itemsOrPlaceholder + } else if let footer { + list = itemsOrPlaceholder + [footer] + } else { + list = itemsOrPlaceholder + } + + return AnyView( + VStack(spacing: intraItemSeparator ? 0 : 10) { + ForEach(list, id: \.id) { [weak self] item in + Group { + let cell = + Group { + // render the item if it is a header or a footer and the index is first or last + // or if items is empty (and placeholder is being displayed) + if (item === list.first && self?.header != nil) || (item === list.last && self?.footer != nil) || self?.items.isEmpty != false { + item.createView(parentStyle: parentStyle) + } else { + VStack(alignment: .leading, spacing: 0) { + if self?.intraItemSeparator == true { + let shouldDisplayTopSeparator = self?.intraItemSeparator == true && (self?.firstListItemTopSeparator == true && item === list.first) + let shouldDisplayBottomSeparator = self?.intraItemSeparator == true && (item !== list.last || self?.lastListItemBottomSeparator == true) + if shouldDisplayTopSeparator { + DividerModel().createView(parentStyle: parentStyle) + } + + Spacer() + item.createView(parentStyle: parentStyle) + Spacer() + + if shouldDisplayBottomSeparator { + DividerModel().createView(parentStyle: parentStyle) + } + } else { + item.createView(parentStyle: parentStyle) + } + } + } + } + + if let width = self?.width { + cell.frame(width: width) + } else { + cell + } + } + } + } + ) + } +} diff --git a/PlatformUI/PlatformUI/PlatformUI.docc/PlatformUI.md b/PlatformUI/PlatformUI/PlatformUI.docc/PlatformUI.md new file mode 100755 index 000000000..de8d0d890 --- /dev/null +++ b/PlatformUI/PlatformUI/PlatformUI.docc/PlatformUI.md @@ -0,0 +1,13 @@ +# ``PlatformUI`` + +Summary + +## Overview + +Text + +## Topics + +### Group + +- ``Symbol`` \ No newline at end of file diff --git a/PlatformUI/PlatformUI/PlatformUI.h b/PlatformUI/PlatformUI/PlatformUI.h new file mode 100644 index 000000000..89f6c0a6c --- /dev/null +++ b/PlatformUI/PlatformUI/PlatformUI.h @@ -0,0 +1,18 @@ +// +// PlatformUI.h +// PlatformUI +// +// Created by Rui Huang on 8/8/22. +// + +#import + +//! Project version number for PlatformUI. +FOUNDATION_EXPORT double PlatformUIVersionNumber; + +//! Project version string for PlatformUI. +FOUNDATION_EXPORT const unsigned char PlatformUIVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/PlatformUI/PlatformUI/PlatformUI.swift b/PlatformUI/PlatformUI/PlatformUI.swift new file mode 100644 index 000000000..827b29dc8 --- /dev/null +++ b/PlatformUI/PlatformUI/PlatformUI.swift @@ -0,0 +1,46 @@ +// +// PlatformUI.swift +// PlatformUI +// +// Created by Rui Huang on 8/22/22. +// + +import SwiftUI + +public protocol PlatformUIViewProtocol { + var themeSettings: ThemeSettings { get } + var parentStyle: ThemeStyle { get } + var styleKey: String? { get } +} + +public extension PlatformUIViewProtocol { + + var style: ThemeStyle { + guard let styleKey = styleKey else { + return parentStyle + } + + if let itemStyle = themeSettings.styleConfig.styles[styleKey] { + return parentStyle.merge(from: itemStyle) + } + + assertionFailure("StyleKey not found: \(styleKey)") + return parentStyle + } +} + +public enum PlatformUISign { + case plus + case minus + case none + + public init(value: Double?) { + if value ?? 0 > 0 { + self = .plus + } else if value ?? 0 < 0 { + self = .minus + } else { + self = .none + } + } +} diff --git a/PlatformUI/PlatformUI/PlatformUIBundleClass.swift b/PlatformUI/PlatformUI/PlatformUIBundleClass.swift new file mode 100644 index 000000000..20c33b879 --- /dev/null +++ b/PlatformUI/PlatformUI/PlatformUIBundleClass.swift @@ -0,0 +1,12 @@ +// +// PlatformUIBundleClass.swift +// PlatformUI +// +// Created by Rui Huang on 8/9/22. +// + +import Foundation + +class PlatformUIBundleClass { + +} diff --git a/PlatformUI/PlatformUI/PlatformViewModel+Ext.swift b/PlatformUI/PlatformUI/PlatformViewModel+Ext.swift new file mode 100644 index 000000000..5fdd20a1e --- /dev/null +++ b/PlatformUI/PlatformUI/PlatformViewModel+Ext.swift @@ -0,0 +1,38 @@ +// +// PlatformViewModel+Ext.swift +// PlatformUI +// +// Created by Rui Huang on 8/3/23. +// + +import Foundation +import UIKit +import SwiftUI + +public extension PlatformViewModel { + var safeAreaInsets: UIEdgeInsets? { + keyWindow?.safeAreaInsets + } + + var keyWindow: UIWindow? { + UIApplication + .shared + .connectedScenes + .compactMap { ($0 as? UIWindowScene)?.keyWindow } + .last + } +} + +public extension View { + var safeAreaInsets: UIEdgeInsets? { + keyWindow?.safeAreaInsets + } + + var keyWindow: UIWindow? { + UIApplication + .shared + .connectedScenes + .compactMap { ($0 as? UIWindowScene)?.keyWindow } + .last + } +} diff --git a/PlatformUI/PlatformUI/PlatformViewModel.swift b/PlatformUI/PlatformUI/PlatformViewModel.swift new file mode 100644 index 000000000..7984bcdb9 --- /dev/null +++ b/PlatformUI/PlatformUI/PlatformViewModel.swift @@ -0,0 +1,84 @@ +// +// PlatformViewModel.swift +// PlatformUI +// +// Created by Rui Huang on 9/1/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI +import Utilities +import Combine + +public protocol PlatformViewModeling: ObservableObject, Identifiable { + associatedtype Content: View + + func createView(parentStyle: ThemeStyle, styleKey: String?) -> Content +} + +open class PlatformViewModel: PlatformViewModeling { + private let bodyBuilder: ((_ style: ThemeStyle) -> AnyView)? + + public init(bodyBuilder: ((_ style: ThemeStyle) -> AnyView)? = nil) { + self.bodyBuilder = bodyBuilder + } + + open func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + return PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + if let bodyBuilder = self?.bodyBuilder { + return AnyView(bodyBuilder(style)) + } else { + return AnyView(Text("")) + } + } + } + + public func updateView(){ + self.objectWillChange.send() + } +} + +public struct PlatformView: View, PlatformUIViewProtocol { + public static let nilView: Text? = nil + public static let nilViewModel = PlatformViewModel() { _ in AnyView(nilView) } + public static let emptyView = Text("") + + private var bodyBuilder: ((_ style: ThemeStyle) -> AnyView)? + + public init(viewModel: PlatformViewModel = PlatformViewModel(), + parentStyle: ThemeStyle = ThemeStyle.defaultStyle, + styleKey: String? = nil, + bodyBuilder: @escaping ((_ style: ThemeStyle) -> AnyView)) { + self.viewModel = viewModel + self.parentStyle = parentStyle + self.styleKey = styleKey + self.bodyBuilder = bodyBuilder + } + + @ObservedObject public var viewModel: PlatformViewModel + + // System-wide theme settings. Do not change this. + @ObservedObject public var themeSettings = ThemeSettings.shared + + // Pass this to child views + public var parentStyle: ThemeStyle = ThemeStyle.defaultStyle + + // Update this to provide custom style key if necessary + public var styleKey: String? + + public var body: some View { + Group { + bodyBuilder?(style) + } + .themeStyle(style: style) + .environmentObject(themeSettings) + } +} + +public extension View { + var wrappedViewModel: PlatformViewModel { + PlatformViewModel() { _ in + AnyView(self) + } + } +} diff --git a/PlatformUI/PlatformUITests/PlatformUITests.swift b/PlatformUI/PlatformUITests/PlatformUITests.swift new file mode 100644 index 000000000..1400a2921 --- /dev/null +++ b/PlatformUI/PlatformUITests/PlatformUITests.swift @@ -0,0 +1,18 @@ +// +// PlatformUITests.swift +// PlatformUITests +// +// Created by Rui Huang on 8/8/22. +// + +import XCTest +@testable import PlatformUI + +class PlatformUITests: XCTestCase { + + func testFont() throws { + let themeConfig = ThemeConfig.sampleThemeConfig + let font = themeConfig.themeFont.font(of: .text, fontSize: .largest) + XCTAssert(font != nil) + } +} diff --git a/PlatformUIJedio/PlatformUIJedio.xcodeproj/project.pbxproj b/PlatformUIJedio/PlatformUIJedio.xcodeproj/project.pbxproj new file mode 100644 index 000000000..2990518cb --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio.xcodeproj/project.pbxproj @@ -0,0 +1,1333 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 027E1EDC29C914AC0098666F /* SettingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027E1EDB29C914AC0098666F /* SettingHeaderView.swift */; }; + 027E1EDE29C925FD0098666F /* SettingOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027E1EDD29C925FD0098666F /* SettingOptionView.swift */; }; + 027E1EE029C95CBD0098666F /* FieldSettingsViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027E1EDF29C95CBD0098666F /* FieldSettingsViewPresenter.swift */; }; + 027E1EFA29CA296B0098666F /* SettingsViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027E1EF929CA296B0098666F /* SettingsViewPresenter.swift */; }; + 027E1EFE29CA4FB10098666F /* FieldOutputTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027E1EFD29CA4FB10098666F /* FieldOutputTextView.swift */; }; + 027E1F0029CA4FE30098666F /* FieldOutputBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027E1EFF29CA4FE30098666F /* FieldOutputBaseView.swift */; }; + 02D1CD9629C8D274009ADF9A /* PlatformUIJedio.docc in Sources */ = {isa = PBXBuildFile; fileRef = 02D1CD9529C8D274009ADF9A /* PlatformUIJedio.docc */; }; + 02D1CD9C29C8D274009ADF9A /* PlatformUIJedio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02D1CD9129C8D274009ADF9A /* PlatformUIJedio.framework */; }; + 02D1CDA129C8D274009ADF9A /* PlatformUIJedioTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D1CDA029C8D274009ADF9A /* PlatformUIJedioTests.swift */; }; + 02D1CDA229C8D274009ADF9A /* PlatformUIJedio.h in Headers */ = {isa = PBXBuildFile; fileRef = 02D1CD9429C8D274009ADF9A /* PlatformUIJedio.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 02D1CE6429C8D2BC009ADF9A /* PlatformUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02D1CE4A29C8D294009ADF9A /* PlatformUI.framework */; }; + 02D1CE6529C8D2C8009ADF9A /* JedioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02D1CE5629C8D299009ADF9A /* JedioKit.framework */; platformFilter = ios; }; + 02D1CE6829C8D34B009ADF9A /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D1CE6729C8D34B009ADF9A /* SettingsView.swift */; }; + 02D1CE6D29C8D3E4009ADF9A /* BaseSettingsViewPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D1CE6C29C8D3E4009ADF9A /* BaseSettingsViewPresenter.swift */; }; + 02D1CEC429C8D47B009ADF9A /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02D1CEA129C8D441009ADF9A /* RoutingKit.framework */; platformFilter = ios; }; + 02D1CEC529C8D4B1009ADF9A /* PlatformParticles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02D1CE8B29C8D437009ADF9A /* PlatformParticles.framework */; }; + 02D1CEC629C8D4B6009ADF9A /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02D1CE7729C8D42F009ADF9A /* Utilities.framework */; platformFilter = ios; }; + 02D1CEC729C8D4E6009ADF9A /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02D1CEB329C8D441009ADF9A /* ParticlesKit.framework */; platformFilter = ios; }; + 02D1CED329C8D548009ADF9A /* dydxViews.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02D1CECE29C8D53C009ADF9A /* dydxViews.framework */; }; + 02FAFA7729D4E838001A0903 /* FieldInputSwitchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FAFA7629D4E838001A0903 /* FieldInputSwitchView.swift */; }; + 02FAFA7929D4E8A1001A0903 /* FieldInputBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FAFA7829D4E8A1001A0903 /* FieldInputBaseView.swift */; }; + 02FAFA7B29D5027C001A0903 /* FieldInputSelectionGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FAFA7A29D5027C001A0903 /* FieldInputSelectionGridView.swift */; }; + 02FAFA7D29D51303001A0903 /* FieldInputTextsInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FAFA7C29D51303001A0903 /* FieldInputTextsInputView.swift */; }; + 4F079DA1872A9168D6035DD6 /* Pods_iOS_PlatformUIJedio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4DFCF1B8C8EACF619929C67 /* Pods_iOS_PlatformUIJedio.framework */; }; + DEAF6AF5E6F9D552E4F39981 /* Pods_iOS_PlatformUIJedioTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 375BABF5403724E52C1496B1 /* Pods_iOS_PlatformUIJedioTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 02D1CD9D29C8D274009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CD8829C8D274009ADF9A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 02D1CD9029C8D274009ADF9A; + remoteInfo = PlatformUIJedio; + }; + 02D1CE4929C8D294009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE4429C8D294009ADF9A /* PlatformUI.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 02E2C92B28A1C8A400F7C3BE; + remoteInfo = PlatformUI; + }; + 02D1CE4B29C8D294009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE4429C8D294009ADF9A /* PlatformUI.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 02E2C93528A1C8A400F7C3BE; + remoteInfo = PlatformUITests; + }; + 02D1CE5529C8D299009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE4D29C8D299009ADF9A /* JedioKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB84B21B0F3F500391ADF; + remoteInfo = JedioKit; + }; + 02D1CE5729C8D299009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE4D29C8D299009ADF9A /* JedioKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBA9821BB791800BEF926; + remoteInfo = JedioKitAppleWatch; + }; + 02D1CE5929C8D299009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE4D29C8D299009ADF9A /* JedioKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBAA521BB792600BEF926; + remoteInfo = JedioKitAppleTV; + }; + 02D1CE5B29C8D299009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE4D29C8D299009ADF9A /* JedioKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB85421B0F3F500391ADF; + remoteInfo = JedioKitTests; + }; + 02D1CE5D29C8D299009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE4D29C8D299009ADF9A /* JedioKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBAAD21BB792600BEF926; + remoteInfo = JedioKitAppleTVTests; + }; + 02D1CE5F29C8D2A7009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE4D29C8D299009ADF9A /* JedioKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31ACB84A21B0F3F500391ADF; + remoteInfo = JedioKit; + }; + 02D1CE6129C8D2AB009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE4429C8D294009ADF9A /* PlatformUI.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 02E2C92A28A1C8A400F7C3BE; + remoteInfo = PlatformUI; + }; + 02D1CE7629C8D42F009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE6E29C8D42F009ADF9A /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 02D1CE7829C8D42F009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE6E29C8D42F009ADF9A /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 02D1CE7A29C8D42F009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE6E29C8D42F009ADF9A /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 02D1CE7C29C8D42F009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE6E29C8D42F009ADF9A /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 02D1CE7E29C8D42F009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE6E29C8D42F009ADF9A /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 02D1CE8A29C8D437009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE8029C8D437009ADF9A /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB72921B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 02D1CE8C29C8D437009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE8029C8D437009ADF9A /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB7C621BB592E00BEF926; + remoteInfo = PlatformParticlesAppleWatch; + }; + 02D1CE8E29C8D437009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE8029C8D437009ADF9A /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB4321BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTV; + }; + 02D1CE9029C8D437009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE8029C8D437009ADF9A /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CFF7C121D7D1F200DE7A79; + remoteInfo = MessageParticles; + }; + 02D1CE9229C8D437009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE8029C8D437009ADF9A /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB73221B0EE2D00391ADF; + remoteInfo = PlatformParticlesTests; + }; + 02D1CE9429C8D437009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE8029C8D437009ADF9A /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB4B21BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTVTests; + }; + 02D1CE9629C8D437009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE8029C8D437009ADF9A /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CFF7C921D7D1F300DE7A79; + remoteInfo = MessageParticlesTests; + }; + 02D1CEA029C8D441009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE9829C8D441009ADF9A /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B01216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 02D1CEA229C8D441009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE9829C8D441009ADF9A /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196825B21B7947600AE0F28; + remoteInfo = RoutingKitAppleWatch; + }; + 02D1CEA429C8D441009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE9829C8D441009ADF9A /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22321BA26D700BEF926; + remoteInfo = RoutingKitAppleTV; + }; + 02D1CEA629C8D441009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE9829C8D441009ADF9A /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B0A216BCA10008ABEE9; + remoteInfo = RoutingKitTests; + }; + 02D1CEA829C8D441009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE9829C8D441009ADF9A /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22B21BA26D800BEF926; + remoteInfo = RoutingKitAppleTVTests; + }; + 02D1CEB229C8D441009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CEAA29C8D441009ADF9A /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FDA21B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 02D1CEB429C8D441009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CEAA29C8D441009ADF9A /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 319682B421B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 02D1CEB629C8D441009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CEAA29C8D441009ADF9A /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16121BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 02D1CEB829C8D441009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CEAA29C8D441009ADF9A /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FE321B0E9C4001775BA; + remoteInfo = ParticlesKitTests; + }; + 02D1CEBA29C8D441009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CEAA29C8D441009ADF9A /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16921BA246A00BEF926; + remoteInfo = ParticlesKitAppleTVTests; + }; + 02D1CEBC29C8D44A009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE9829C8D441009ADF9A /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65B00216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 02D1CEBE29C8D457009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE6E29C8D42F009ADF9A /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 02D1CEC029C8D457009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CEAA29C8D441009ADF9A /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 311C0FD921B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 02D1CEC229C8D46A009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CE8029C8D437009ADF9A /* PlatformParticles.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31ACB72821B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 02D1CECD29C8D53C009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CEC829C8D53C009ADF9A /* dydxViews.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 024B79BA28B7F53800F7C386; + remoteInfo = dydxViews; + }; + 02D1CECF29C8D53C009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CEC829C8D53C009ADF9A /* dydxViews.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 024B79C428B7F53800F7C386; + remoteInfo = dydxViewsTests; + }; + 02D1CED129C8D543009ADF9A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02D1CEC829C8D53C009ADF9A /* dydxViews.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 024B79B928B7F53800F7C386; + remoteInfo = dydxViews; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 027E1EDB29C914AC0098666F /* SettingHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingHeaderView.swift; sourceTree = ""; }; + 027E1EDD29C925FD0098666F /* SettingOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingOptionView.swift; sourceTree = ""; }; + 027E1EDF29C95CBD0098666F /* FieldSettingsViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldSettingsViewPresenter.swift; sourceTree = ""; }; + 027E1EF929CA296B0098666F /* SettingsViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewPresenter.swift; sourceTree = ""; }; + 027E1EFD29CA4FB10098666F /* FieldOutputTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldOutputTextView.swift; sourceTree = ""; }; + 027E1EFF29CA4FE30098666F /* FieldOutputBaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldOutputBaseView.swift; sourceTree = ""; }; + 02D1CD9129C8D274009ADF9A /* PlatformUIJedio.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PlatformUIJedio.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 02D1CD9429C8D274009ADF9A /* PlatformUIJedio.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformUIJedio.h; sourceTree = ""; }; + 02D1CD9529C8D274009ADF9A /* PlatformUIJedio.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = PlatformUIJedio.docc; sourceTree = ""; }; + 02D1CD9B29C8D274009ADF9A /* PlatformUIJedioTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PlatformUIJedioTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 02D1CDA029C8D274009ADF9A /* PlatformUIJedioTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformUIJedioTests.swift; sourceTree = ""; }; + 02D1CE4429C8D294009ADF9A /* PlatformUI.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PlatformUI.xcodeproj; path = ../PlatformUI/PlatformUI.xcodeproj; sourceTree = ""; }; + 02D1CE4D29C8D299009ADF9A /* JedioKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = JedioKit.xcodeproj; path = ../JedioKit/JedioKit.xcodeproj; sourceTree = ""; }; + 02D1CE6729C8D34B009ADF9A /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 02D1CE6C29C8D3E4009ADF9A /* BaseSettingsViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseSettingsViewPresenter.swift; sourceTree = ""; }; + 02D1CE6E29C8D42F009ADF9A /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 02D1CE8029C8D437009ADF9A /* PlatformParticles.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PlatformParticles.xcodeproj; path = ../PlatformParticles/PlatformParticles.xcodeproj; sourceTree = ""; }; + 02D1CE9829C8D441009ADF9A /* RoutingKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RoutingKit.xcodeproj; path = ../RoutingKit/RoutingKit.xcodeproj; sourceTree = ""; }; + 02D1CEAA29C8D441009ADF9A /* ParticlesKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParticlesKit.xcodeproj; path = ../ParticlesKit/ParticlesKit.xcodeproj; sourceTree = ""; }; + 02D1CEC829C8D53C009ADF9A /* dydxViews.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = dydxViews.xcodeproj; path = ../dydx/dydxViews/dydxViews.xcodeproj; sourceTree = ""; }; + 02FAFA7629D4E838001A0903 /* FieldInputSwitchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldInputSwitchView.swift; sourceTree = ""; }; + 02FAFA7829D4E8A1001A0903 /* FieldInputBaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldInputBaseView.swift; sourceTree = ""; }; + 02FAFA7A29D5027C001A0903 /* FieldInputSelectionGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldInputSelectionGridView.swift; sourceTree = ""; }; + 02FAFA7C29D51303001A0903 /* FieldInputTextsInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldInputTextsInputView.swift; sourceTree = ""; }; + 2DC89F0DDD206C12D9901366 /* Pods-iOS-PlatformUIJedio.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformUIJedio.release.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformUIJedio/Pods-iOS-PlatformUIJedio.release.xcconfig"; sourceTree = ""; }; + 36D266CC260D5D029E776CFA /* Pods-iOS-PlatformUIJedioTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformUIJedioTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformUIJedioTests/Pods-iOS-PlatformUIJedioTests.release.xcconfig"; sourceTree = ""; }; + 375BABF5403724E52C1496B1 /* Pods_iOS_PlatformUIJedioTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_PlatformUIJedioTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9DD1B818407628F1B79C936E /* Pods-iOS-PlatformUIJedioTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformUIJedioTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformUIJedioTests/Pods-iOS-PlatformUIJedioTests.debug.xcconfig"; sourceTree = ""; }; + A4DFCF1B8C8EACF619929C67 /* Pods_iOS_PlatformUIJedio.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_PlatformUIJedio.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BC76FBF717207B1934DB6F30 /* Pods-iOS-PlatformUIJedio.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-PlatformUIJedio.debug.xcconfig"; path = "Target Support Files/Pods-iOS-PlatformUIJedio/Pods-iOS-PlatformUIJedio.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 02D1CD8E29C8D274009ADF9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 02D1CED329C8D548009ADF9A /* dydxViews.framework in Frameworks */, + 02D1CEC729C8D4E6009ADF9A /* ParticlesKit.framework in Frameworks */, + 02D1CEC629C8D4B6009ADF9A /* Utilities.framework in Frameworks */, + 02D1CEC529C8D4B1009ADF9A /* PlatformParticles.framework in Frameworks */, + 02D1CEC429C8D47B009ADF9A /* RoutingKit.framework in Frameworks */, + 02D1CE6529C8D2C8009ADF9A /* JedioKit.framework in Frameworks */, + 02D1CE6429C8D2BC009ADF9A /* PlatformUI.framework in Frameworks */, + 4F079DA1872A9168D6035DD6 /* Pods_iOS_PlatformUIJedio.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 02D1CD9829C8D274009ADF9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 02D1CD9C29C8D274009ADF9A /* PlatformUIJedio.framework in Frameworks */, + DEAF6AF5E6F9D552E4F39981 /* Pods_iOS_PlatformUIJedioTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 027E1EC229C914950098666F /* Components */ = { + isa = PBXGroup; + children = ( + 02FAFA5D29D4E81B001A0903 /* Input */, + 027E1EFC29CA2FC40098666F /* Output */, + 027E1EDB29C914AC0098666F /* SettingHeaderView.swift */, + 027E1EDD29C925FD0098666F /* SettingOptionView.swift */, + ); + path = Components; + sourceTree = ""; + }; + 027E1EFC29CA2FC40098666F /* Output */ = { + isa = PBXGroup; + children = ( + 027E1EFF29CA4FE30098666F /* FieldOutputBaseView.swift */, + 027E1EFD29CA4FB10098666F /* FieldOutputTextView.swift */, + ); + path = Output; + sourceTree = ""; + }; + 02D1CD8729C8D274009ADF9A = { + isa = PBXGroup; + children = ( + 02D1CE6629C8D31E009ADF9A /* Dependencies */, + 02D1CD9329C8D274009ADF9A /* PlatformUIJedio */, + 02D1CD9F29C8D274009ADF9A /* PlatformUIJedioTests */, + 02D1CD9229C8D274009ADF9A /* Products */, + 02D1CE6329C8D2BC009ADF9A /* Frameworks */, + DD8E53F4A13656F1E39D187D /* Pods */, + ); + sourceTree = ""; + }; + 02D1CD9229C8D274009ADF9A /* Products */ = { + isa = PBXGroup; + children = ( + 02D1CD9129C8D274009ADF9A /* PlatformUIJedio.framework */, + 02D1CD9B29C8D274009ADF9A /* PlatformUIJedioTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 02D1CD9329C8D274009ADF9A /* PlatformUIJedio */ = { + isa = PBXGroup; + children = ( + 02D1CE6929C8D35B009ADF9A /* Views */, + 02D1CE6A29C8D360009ADF9A /* Presenters */, + 02D1CD9429C8D274009ADF9A /* PlatformUIJedio.h */, + 02D1CD9529C8D274009ADF9A /* PlatformUIJedio.docc */, + ); + path = PlatformUIJedio; + sourceTree = ""; + }; + 02D1CD9F29C8D274009ADF9A /* PlatformUIJedioTests */ = { + isa = PBXGroup; + children = ( + 02D1CDA029C8D274009ADF9A /* PlatformUIJedioTests.swift */, + ); + path = PlatformUIJedioTests; + sourceTree = ""; + }; + 02D1CE4529C8D294009ADF9A /* Products */ = { + isa = PBXGroup; + children = ( + 02D1CE4A29C8D294009ADF9A /* PlatformUI.framework */, + 02D1CE4C29C8D294009ADF9A /* PlatformUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 02D1CE4E29C8D299009ADF9A /* Products */ = { + isa = PBXGroup; + children = ( + 02D1CE5629C8D299009ADF9A /* JedioKit.framework */, + 02D1CE5829C8D299009ADF9A /* JedioKit.framework */, + 02D1CE5A29C8D299009ADF9A /* JedioKit.framework */, + 02D1CE5C29C8D299009ADF9A /* JedioKitTests.xctest */, + 02D1CE5E29C8D299009ADF9A /* JedioKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 02D1CE6329C8D2BC009ADF9A /* Frameworks */ = { + isa = PBXGroup; + children = ( + A4DFCF1B8C8EACF619929C67 /* Pods_iOS_PlatformUIJedio.framework */, + 375BABF5403724E52C1496B1 /* Pods_iOS_PlatformUIJedioTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 02D1CE6629C8D31E009ADF9A /* Dependencies */ = { + isa = PBXGroup; + children = ( + 02D1CEC829C8D53C009ADF9A /* dydxViews.xcodeproj */, + 02D1CE9829C8D441009ADF9A /* RoutingKit.xcodeproj */, + 02D1CEAA29C8D441009ADF9A /* ParticlesKit.xcodeproj */, + 02D1CE8029C8D437009ADF9A /* PlatformParticles.xcodeproj */, + 02D1CE6E29C8D42F009ADF9A /* Utilities.xcodeproj */, + 02D1CE4D29C8D299009ADF9A /* JedioKit.xcodeproj */, + 02D1CE4429C8D294009ADF9A /* PlatformUI.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 02D1CE6929C8D35B009ADF9A /* Views */ = { + isa = PBXGroup; + children = ( + 027E1EC229C914950098666F /* Components */, + 02D1CE6729C8D34B009ADF9A /* SettingsView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 02D1CE6A29C8D360009ADF9A /* Presenters */ = { + isa = PBXGroup; + children = ( + 02D1CE6C29C8D3E4009ADF9A /* BaseSettingsViewPresenter.swift */, + 027E1EF929CA296B0098666F /* SettingsViewPresenter.swift */, + 027E1EDF29C95CBD0098666F /* FieldSettingsViewPresenter.swift */, + ); + path = Presenters; + sourceTree = ""; + }; + 02D1CE6F29C8D42F009ADF9A /* Products */ = { + isa = PBXGroup; + children = ( + 02D1CE7729C8D42F009ADF9A /* Utilities.framework */, + 02D1CE7929C8D42F009ADF9A /* Utilities.framework */, + 02D1CE7B29C8D42F009ADF9A /* Utilities.framework */, + 02D1CE7D29C8D42F009ADF9A /* UtilitiesTests.xctest */, + 02D1CE7F29C8D42F009ADF9A /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 02D1CE8129C8D437009ADF9A /* Products */ = { + isa = PBXGroup; + children = ( + 02D1CE8B29C8D437009ADF9A /* PlatformParticles.framework */, + 02D1CE8D29C8D437009ADF9A /* PlatformParticles.framework */, + 02D1CE8F29C8D437009ADF9A /* PlatformParticles.framework */, + 02D1CE9129C8D437009ADF9A /* MessageParticles.framework */, + 02D1CE9329C8D437009ADF9A /* PlatformParticlesTests.xctest */, + 02D1CE9529C8D437009ADF9A /* PlatformParticlesAppleTVTests.xctest */, + 02D1CE9729C8D437009ADF9A /* MessageParticlesTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 02D1CE9929C8D441009ADF9A /* Products */ = { + isa = PBXGroup; + children = ( + 02D1CEA129C8D441009ADF9A /* RoutingKit.framework */, + 02D1CEA329C8D441009ADF9A /* RoutingKit.framework */, + 02D1CEA529C8D441009ADF9A /* RoutingKit.framework */, + 02D1CEA729C8D441009ADF9A /* RoutingKitTests.xctest */, + 02D1CEA929C8D441009ADF9A /* RoutingKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 02D1CEAB29C8D441009ADF9A /* Products */ = { + isa = PBXGroup; + children = ( + 02D1CEB329C8D441009ADF9A /* ParticlesKit.framework */, + 02D1CEB529C8D441009ADF9A /* ParticlesKit.framework */, + 02D1CEB729C8D441009ADF9A /* ParticlesKit.framework */, + 02D1CEB929C8D441009ADF9A /* ParticlesKitTests.xctest */, + 02D1CEBB29C8D441009ADF9A /* ParticlesKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 02D1CEC929C8D53C009ADF9A /* Products */ = { + isa = PBXGroup; + children = ( + 02D1CECE29C8D53C009ADF9A /* dydxViews.framework */, + 02D1CED029C8D53C009ADF9A /* dydxViewsTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 02FAFA5D29D4E81B001A0903 /* Input */ = { + isa = PBXGroup; + children = ( + 02FAFA7829D4E8A1001A0903 /* FieldInputBaseView.swift */, + 02FAFA7629D4E838001A0903 /* FieldInputSwitchView.swift */, + 02FAFA7A29D5027C001A0903 /* FieldInputSelectionGridView.swift */, + 02FAFA7C29D51303001A0903 /* FieldInputTextsInputView.swift */, + ); + path = Input; + sourceTree = ""; + }; + DD8E53F4A13656F1E39D187D /* Pods */ = { + isa = PBXGroup; + children = ( + BC76FBF717207B1934DB6F30 /* Pods-iOS-PlatformUIJedio.debug.xcconfig */, + 2DC89F0DDD206C12D9901366 /* Pods-iOS-PlatformUIJedio.release.xcconfig */, + 9DD1B818407628F1B79C936E /* Pods-iOS-PlatformUIJedioTests.debug.xcconfig */, + 36D266CC260D5D029E776CFA /* Pods-iOS-PlatformUIJedioTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 02D1CD8C29C8D274009ADF9A /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 02D1CDA229C8D274009ADF9A /* PlatformUIJedio.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 02D1CD9029C8D274009ADF9A /* PlatformUIJedio */ = { + isa = PBXNativeTarget; + buildConfigurationList = 02D1CDA529C8D274009ADF9A /* Build configuration list for PBXNativeTarget "PlatformUIJedio" */; + buildPhases = ( + 4872C31DE1E391C3C3E81012 /* [CP] Check Pods Manifest.lock */, + 02D1CD8C29C8D274009ADF9A /* Headers */, + 02D1CD8D29C8D274009ADF9A /* Sources */, + 02D1CD8E29C8D274009ADF9A /* Frameworks */, + 02D1CD8F29C8D274009ADF9A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 02D1CED229C8D543009ADF9A /* PBXTargetDependency */, + 02D1CEC329C8D46A009ADF9A /* PBXTargetDependency */, + 02D1CEBF29C8D457009ADF9A /* PBXTargetDependency */, + 02D1CEC129C8D457009ADF9A /* PBXTargetDependency */, + 02D1CEBD29C8D44A009ADF9A /* PBXTargetDependency */, + 02D1CE6229C8D2AB009ADF9A /* PBXTargetDependency */, + 02D1CE6029C8D2A7009ADF9A /* PBXTargetDependency */, + ); + name = PlatformUIJedio; + productName = PlatformUIJedio; + productReference = 02D1CD9129C8D274009ADF9A /* PlatformUIJedio.framework */; + productType = "com.apple.product-type.framework"; + }; + 02D1CD9A29C8D274009ADF9A /* PlatformUIJedioTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 02D1CDA829C8D274009ADF9A /* Build configuration list for PBXNativeTarget "PlatformUIJedioTests" */; + buildPhases = ( + 0A3362D6700B54B02C480932 /* [CP] Check Pods Manifest.lock */, + 02D1CD9729C8D274009ADF9A /* Sources */, + 02D1CD9829C8D274009ADF9A /* Frameworks */, + 02D1CD9929C8D274009ADF9A /* Resources */, + 74423CE5696BC0BBC36C46AA /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 02D1CD9E29C8D274009ADF9A /* PBXTargetDependency */, + ); + name = PlatformUIJedioTests; + productName = PlatformUIJedioTests; + productReference = 02D1CD9B29C8D274009ADF9A /* PlatformUIJedioTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 02D1CD8829C8D274009ADF9A /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1420; + LastUpgradeCheck = 1420; + TargetAttributes = { + 02D1CD9029C8D274009ADF9A = { + CreatedOnToolsVersion = 14.2; + }; + 02D1CD9A29C8D274009ADF9A = { + CreatedOnToolsVersion = 14.2; + }; + }; + }; + buildConfigurationList = 02D1CD8B29C8D274009ADF9A /* Build configuration list for PBXProject "PlatformUIJedio" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 02D1CD8729C8D274009ADF9A; + productRefGroup = 02D1CD9229C8D274009ADF9A /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 02D1CEC929C8D53C009ADF9A /* Products */; + ProjectRef = 02D1CEC829C8D53C009ADF9A /* dydxViews.xcodeproj */; + }, + { + ProductGroup = 02D1CE4E29C8D299009ADF9A /* Products */; + ProjectRef = 02D1CE4D29C8D299009ADF9A /* JedioKit.xcodeproj */; + }, + { + ProductGroup = 02D1CEAB29C8D441009ADF9A /* Products */; + ProjectRef = 02D1CEAA29C8D441009ADF9A /* ParticlesKit.xcodeproj */; + }, + { + ProductGroup = 02D1CE8129C8D437009ADF9A /* Products */; + ProjectRef = 02D1CE8029C8D437009ADF9A /* PlatformParticles.xcodeproj */; + }, + { + ProductGroup = 02D1CE4529C8D294009ADF9A /* Products */; + ProjectRef = 02D1CE4429C8D294009ADF9A /* PlatformUI.xcodeproj */; + }, + { + ProductGroup = 02D1CE9929C8D441009ADF9A /* Products */; + ProjectRef = 02D1CE9829C8D441009ADF9A /* RoutingKit.xcodeproj */; + }, + { + ProductGroup = 02D1CE6F29C8D42F009ADF9A /* Products */; + ProjectRef = 02D1CE6E29C8D42F009ADF9A /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 02D1CD9029C8D274009ADF9A /* PlatformUIJedio */, + 02D1CD9A29C8D274009ADF9A /* PlatformUIJedioTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 02D1CE4A29C8D294009ADF9A /* PlatformUI.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformUI.framework; + remoteRef = 02D1CE4929C8D294009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE4C29C8D294009ADF9A /* PlatformUITests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformUITests.xctest; + remoteRef = 02D1CE4B29C8D294009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE5629C8D299009ADF9A /* JedioKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = JedioKit.framework; + remoteRef = 02D1CE5529C8D299009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE5829C8D299009ADF9A /* JedioKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = JedioKit.framework; + remoteRef = 02D1CE5729C8D299009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE5A29C8D299009ADF9A /* JedioKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = JedioKit.framework; + remoteRef = 02D1CE5929C8D299009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE5C29C8D299009ADF9A /* JedioKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = JedioKitTests.xctest; + remoteRef = 02D1CE5B29C8D299009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE5E29C8D299009ADF9A /* JedioKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = JedioKitAppleTVTests.xctest; + remoteRef = 02D1CE5D29C8D299009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE7729C8D42F009ADF9A /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 02D1CE7629C8D42F009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE7929C8D42F009ADF9A /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 02D1CE7829C8D42F009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE7B29C8D42F009ADF9A /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 02D1CE7A29C8D42F009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE7D29C8D42F009ADF9A /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 02D1CE7C29C8D42F009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE7F29C8D42F009ADF9A /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 02D1CE7E29C8D42F009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE8B29C8D437009ADF9A /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 02D1CE8A29C8D437009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE8D29C8D437009ADF9A /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 02D1CE8C29C8D437009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE8F29C8D437009ADF9A /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 02D1CE8E29C8D437009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE9129C8D437009ADF9A /* MessageParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MessageParticles.framework; + remoteRef = 02D1CE9029C8D437009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE9329C8D437009ADF9A /* PlatformParticlesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformParticlesTests.xctest; + remoteRef = 02D1CE9229C8D437009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE9529C8D437009ADF9A /* PlatformParticlesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformParticlesAppleTVTests.xctest; + remoteRef = 02D1CE9429C8D437009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CE9729C8D437009ADF9A /* MessageParticlesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = MessageParticlesTests.xctest; + remoteRef = 02D1CE9629C8D437009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CEA129C8D441009ADF9A /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 02D1CEA029C8D441009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CEA329C8D441009ADF9A /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 02D1CEA229C8D441009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CEA529C8D441009ADF9A /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 02D1CEA429C8D441009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CEA729C8D441009ADF9A /* RoutingKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitTests.xctest; + remoteRef = 02D1CEA629C8D441009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CEA929C8D441009ADF9A /* RoutingKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitAppleTVTests.xctest; + remoteRef = 02D1CEA829C8D441009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CEB329C8D441009ADF9A /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 02D1CEB229C8D441009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CEB529C8D441009ADF9A /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 02D1CEB429C8D441009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CEB729C8D441009ADF9A /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 02D1CEB629C8D441009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CEB929C8D441009ADF9A /* ParticlesKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitTests.xctest; + remoteRef = 02D1CEB829C8D441009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CEBB29C8D441009ADF9A /* ParticlesKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitAppleTVTests.xctest; + remoteRef = 02D1CEBA29C8D441009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CECE29C8D53C009ADF9A /* dydxViews.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = dydxViews.framework; + remoteRef = 02D1CECD29C8D53C009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D1CED029C8D53C009ADF9A /* dydxViewsTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = dydxViewsTests.xctest; + remoteRef = 02D1CECF29C8D53C009ADF9A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 02D1CD8F29C8D274009ADF9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 02D1CD9929C8D274009ADF9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0A3362D6700B54B02C480932 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-PlatformUIJedioTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 4872C31DE1E391C3C3E81012 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-PlatformUIJedio-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 74423CE5696BC0BBC36C46AA /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-PlatformUIJedioTests/Pods-iOS-PlatformUIJedioTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-PlatformUIJedioTests/Pods-iOS-PlatformUIJedioTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-PlatformUIJedioTests/Pods-iOS-PlatformUIJedioTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 02D1CD8D29C8D274009ADF9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 02FAFA7729D4E838001A0903 /* FieldInputSwitchView.swift in Sources */, + 02FAFA7B29D5027C001A0903 /* FieldInputSelectionGridView.swift in Sources */, + 027E1EDC29C914AC0098666F /* SettingHeaderView.swift in Sources */, + 027E1EFE29CA4FB10098666F /* FieldOutputTextView.swift in Sources */, + 02D1CD9629C8D274009ADF9A /* PlatformUIJedio.docc in Sources */, + 027E1EFA29CA296B0098666F /* SettingsViewPresenter.swift in Sources */, + 02D1CE6829C8D34B009ADF9A /* SettingsView.swift in Sources */, + 02FAFA7D29D51303001A0903 /* FieldInputTextsInputView.swift in Sources */, + 02FAFA7929D4E8A1001A0903 /* FieldInputBaseView.swift in Sources */, + 027E1F0029CA4FE30098666F /* FieldOutputBaseView.swift in Sources */, + 02D1CE6D29C8D3E4009ADF9A /* BaseSettingsViewPresenter.swift in Sources */, + 027E1EE029C95CBD0098666F /* FieldSettingsViewPresenter.swift in Sources */, + 027E1EDE29C925FD0098666F /* SettingOptionView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 02D1CD9729C8D274009ADF9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 02D1CDA129C8D274009ADF9A /* PlatformUIJedioTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 02D1CD9E29C8D274009ADF9A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 02D1CD9029C8D274009ADF9A /* PlatformUIJedio */; + targetProxy = 02D1CD9D29C8D274009ADF9A /* PBXContainerItemProxy */; + }; + 02D1CE6029C8D2A7009ADF9A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = JedioKit; + targetProxy = 02D1CE5F29C8D2A7009ADF9A /* PBXContainerItemProxy */; + }; + 02D1CE6229C8D2AB009ADF9A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PlatformUI; + targetProxy = 02D1CE6129C8D2AB009ADF9A /* PBXContainerItemProxy */; + }; + 02D1CEBD29C8D44A009ADF9A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKit; + targetProxy = 02D1CEBC29C8D44A009ADF9A /* PBXContainerItemProxy */; + }; + 02D1CEBF29C8D457009ADF9A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 02D1CEBE29C8D457009ADF9A /* PBXContainerItemProxy */; + }; + 02D1CEC129C8D457009ADF9A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKit; + targetProxy = 02D1CEC029C8D457009ADF9A /* PBXContainerItemProxy */; + }; + 02D1CEC329C8D46A009ADF9A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PlatformParticles; + targetProxy = 02D1CEC229C8D46A009ADF9A /* PBXContainerItemProxy */; + }; + 02D1CED229C8D543009ADF9A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = dydxViews; + targetProxy = 02D1CED129C8D543009ADF9A /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 02D1CDA329C8D274009ADF9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 02D1CDA429C8D274009ADF9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 02D1CDA629C8D274009ADF9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BC76FBF717207B1934DB6F30 /* Pods-iOS-PlatformUIJedio.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 75C6UARB5H; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.PlatformUIJedio; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 02D1CDA729C8D274009ADF9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2DC89F0DDD206C12D9901366 /* Pods-iOS-PlatformUIJedio.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 75C6UARB5H; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.PlatformUIJedio; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 02D1CDA929C8D274009ADF9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9DD1B818407628F1B79C936E /* Pods-iOS-PlatformUIJedioTests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 75C6UARB5H; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.PlatformUIJedioTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 02D1CDAA29C8D274009ADF9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 36D266CC260D5D029E776CFA /* Pods-iOS-PlatformUIJedioTests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 75C6UARB5H; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exchange.dydx.PlatformUIJedioTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 02D1CD8B29C8D274009ADF9A /* Build configuration list for PBXProject "PlatformUIJedio" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 02D1CDA329C8D274009ADF9A /* Debug */, + 02D1CDA429C8D274009ADF9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 02D1CDA529C8D274009ADF9A /* Build configuration list for PBXNativeTarget "PlatformUIJedio" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 02D1CDA629C8D274009ADF9A /* Debug */, + 02D1CDA729C8D274009ADF9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 02D1CDA829C8D274009ADF9A /* Build configuration list for PBXNativeTarget "PlatformUIJedioTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 02D1CDA929C8D274009ADF9A /* Debug */, + 02D1CDAA29C8D274009ADF9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 02D1CD8829C8D274009ADF9A /* Project object */; +} diff --git a/PlatformUIJedio/PlatformUIJedio.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/PlatformUIJedio/PlatformUIJedio.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/PlatformUIJedio/PlatformUIJedio.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PlatformUIJedio/PlatformUIJedio.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/PlatformUIJedio/PlatformUIJedio.xcodeproj/xcshareddata/xcschemes/PlatformUIJedio.xcscheme b/PlatformUIJedio/PlatformUIJedio.xcodeproj/xcshareddata/xcschemes/PlatformUIJedio.xcscheme new file mode 100644 index 000000000..88fe0bb83 --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio.xcodeproj/xcshareddata/xcschemes/PlatformUIJedio.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlatformUIJedio/PlatformUIJedio/PlatformUIJedio.docc/PlatformUIJedio.md b/PlatformUIJedio/PlatformUIJedio/PlatformUIJedio.docc/PlatformUIJedio.md new file mode 100755 index 000000000..9989f2b6c --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/PlatformUIJedio.docc/PlatformUIJedio.md @@ -0,0 +1,13 @@ +# ``PlatformUIJedio`` + +Summary + +## Overview + +Text + +## Topics + +### Group + +- ``Symbol`` \ No newline at end of file diff --git a/PlatformUIJedio/PlatformUIJedio/PlatformUIJedio.h b/PlatformUIJedio/PlatformUIJedio/PlatformUIJedio.h new file mode 100644 index 000000000..5ce175ad0 --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/PlatformUIJedio.h @@ -0,0 +1,18 @@ +// +// PlatformUIJedio.h +// PlatformUIJedio +// +// Created by Rui Huang on 3/20/23. +// + +#import + +//! Project version number for PlatformUIJedio. +FOUNDATION_EXPORT double PlatformUIJedioVersionNumber; + +//! Project version string for PlatformUIJedio. +FOUNDATION_EXPORT const unsigned char PlatformUIJedioVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/PlatformUIJedio/PlatformUIJedio/Presenters/BaseSettingsViewPresenter.swift b/PlatformUIJedio/PlatformUIJedio/Presenters/BaseSettingsViewPresenter.swift new file mode 100644 index 000000000..88a07083f --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/Presenters/BaseSettingsViewPresenter.swift @@ -0,0 +1,54 @@ +// +// BaseSettingsViewPresenter.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/20/23. +// + +import Utilities +import dydxViews +import PlatformParticles +import RoutingKit +import ParticlesKit +import PlatformUI +import JedioKit + +public class SettingsViewController: HostingViewController { + public var requestPath = "" + + override public func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { + if request?.path == requestPath { + return true + } + return false + } +} + +public protocol SettingsViewPresenterProtocol: HostedViewPresenterProtocol { + var viewModel: SettingsViewModel? { get } +} + +open class BaseSettingsViewPresenter: HostedViewPresenter, SettingsViewPresenterProtocol { + let fieldsEntity: FieldsEntityInteractor + + var fieldLists: [FieldListInteractor]? { + fieldsEntity.list?.list as? [FieldListInteractor] + } + + public static func newFieldsEntity(forDefinitionFile definitionFile: String) -> FieldsEntityInteractor { + let fieldsLoader = FieldLoader() + fieldsLoader.definitionFile = definitionFile + + let fieldsEntity = FieldsEntityInteractor() + fieldsEntity.fieldLoader = fieldsLoader + fieldsEntity.list = ListInteractor() + fieldsEntity.entity = DictionaryEntity() + return fieldsEntity + } + + public init(definitionFile: String) { + self.fieldsEntity = Self.newFieldsEntity(forDefinitionFile: definitionFile) + super.init() + viewModel = SettingsViewModel() + } +} diff --git a/PlatformUIJedio/PlatformUIJedio/Presenters/FieldSettingsViewPresenter.swift b/PlatformUIJedio/PlatformUIJedio/Presenters/FieldSettingsViewPresenter.swift new file mode 100644 index 000000000..0155b804b --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/Presenters/FieldSettingsViewPresenter.swift @@ -0,0 +1,87 @@ +// +// FieldSettingsViewPresenter.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/20/23. +// + +import Utilities +import dydxViews +import PlatformParticles +import RoutingKit +import ParticlesKit +import PlatformUI +import JedioKit + +open class FieldSettingsViewPresenter: BaseSettingsViewPresenter { + private let fieldName: String + private let keyValueStore: KeyValueStoreProtocol? + + private var fieldInput: FieldInput? + + public init(definitionFile: String, fieldName: String, keyValueStore: KeyValueStoreProtocol?) { + self.fieldName = fieldName + self.keyValueStore = keyValueStore + super.init(definitionFile: definitionFile) + + fieldInput = findInput(fieldName: fieldName) + loadOptions() + } + + private func loadOptions() { + let listModel = PlatformListViewModel(firstListItemTopSeparator: true, lastListItemBottomSeparator: true) + listModel.width = UIScreen.main.bounds.width - 16 + let options: [[String: Any]]? = fieldInput?.options + listModel.items = (options ?? []).compactMap { option in + guard let text = textForOption(option: option), + let value = valueForOption(option: option) else { + return nil + } + + let optionViewModel = SettingOptionViewModel() + optionViewModel.text = DataLocalizer.localize(path: text) + optionViewModel.isSelected = parser.asString(keyValueStore?.value(forKey: fieldName)) == value + optionViewModel.onTapAction = { [weak self] in + guard let self = self else { + return + } + if let currentValue = self.keyValueStore?.value(forKey: self.fieldName) as? String, + let newValue = option["value"] as? String, + currentValue == newValue { + self.onOptionSelected(option: option, changed: false) + } else { + self.keyValueStore?.setValue(option["value"], forKey: self.fieldName) + optionViewModel.isSelected = true + self.onOptionSelected(option: option, changed: true) + self.loadOptions() + } + } + return optionViewModel + } + viewModel?.sections = [SettingsViewModel.SectionViewModel(items: listModel)] + } + + open func onOptionSelected(option: [String: Any], changed: Bool) { + } + + open func textForOption(option: [String: Any]) -> String? { + parser.asString(option["text"]) + } + + open func valueForOption(option: [String: Any]) -> String? { + parser.asString(option["value"]) + } + + private func findInput(fieldName: String) -> FieldInput? { + for fieldList in fieldLists ?? [] { + for object2 in fieldList.list ?? [] { + if let input = object2 as? FieldInput { + if input.field?.definition(for: "field")?["field"] as? String == fieldName { + return input + } + } + } + } + return nil + } +} diff --git a/PlatformUIJedio/PlatformUIJedio/Presenters/SettingsViewPresenter.swift b/PlatformUIJedio/PlatformUIJedio/Presenters/SettingsViewPresenter.swift new file mode 100644 index 000000000..8fb3c68a2 --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/Presenters/SettingsViewPresenter.swift @@ -0,0 +1,160 @@ +// +// SettingsViewPresenter.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/21/23. +// + +import Utilities +import dydxViews +import PlatformParticles +import RoutingKit +import ParticlesKit +import PlatformUI +import JedioKit + +open class SettingsViewPresenter: BaseSettingsViewPresenter { + private let keyValueStore: KeyValueStoreProtocol? + private let appScheme: String? + + public init(definitionFile: String, keyValueStore: KeyValueStoreProtocol?, appScheme: String?) { + self.keyValueStore = keyValueStore + self.appScheme = appScheme + super.init(definitionFile: definitionFile) + } + + override open func start() { + super.start() + + loadSettings() + } + + private func loadSettings() { + var sections = [SettingsViewModel.SectionViewModel]() + for fieldList in fieldLists ?? [] { + sections.append(createSection(group: fieldList)) + } + viewModel?.sections = sections + } + + private func createSection(group: FieldListInteractor) -> SettingsViewModel.SectionViewModel { + let listViewModel = PlatformListViewModel(firstListItemTopSeparator: true, lastListItemBottomSeparator: true) + listViewModel.width = UIScreen.main.bounds.width - 16 + for item in group.list ?? [] { + if let output = item as? FieldOutput, + let viewModel = createOutput(output: output) { + listViewModel.items.append(viewModel) + } else if let input = item as? FieldInput, + let viewModel = createInput(input: input) { + listViewModel.items.append(viewModel) + } + } + return SettingsViewModel.SectionViewModel(title: group.title, items: listViewModel) + } + + private func createInput(input: FieldInput) -> PlatformViewModel? { + guard let fieldInputDefinition = input.fieldInput else { + return nil + } + + if let fieldName = input.fieldName, + let value = keyValueStore?.value(forKey: fieldName) { + input.value = value + } + + let valueChanged: ((Any?) -> Void)? = { [weak self] value in + input.value = value + if let fieldName = input.fieldName { + self?.keyValueStore?.setValue(value, forKey: fieldName) + } + self?.loadSettings() + } + + if let xib = fieldInputDefinition.xib { + // TODO: ... + } else if fieldInputDefinition.link != nil { + // TODO: ... + } else { + let hasOptions = fieldInputDefinition.options != nil + switch fieldInputDefinition.fieldType { + case .text: + if hasOptions { + return FieldInputSelectionGridViewModel(input: input, valueChanged: valueChanged) + } else { + return FieldInputTextsInputViewModel(input: input, valueChanged: valueChanged) + } + + // case .int: + // if hasOptions { + // return "field_input_grid_int" + // } else if fieldInput.min != nil && fieldInput.max != nil { + // return "field_input_slider_int" + // } else { + // return "field_input_textfield_int" + // } + // + // case .float: + // if fieldInput.min != nil && fieldInput.max != nil { + // return "field_input_slider_float" + // } else { + // return "field_input_textfield_float" + // } + // + // case .percent: + // return "field_input_slider_percent" + // + // case .strings: + // return "field_input_grid_strings" + + case .bool: + return FieldInputSwitchViewModel(input: input, valueChanged: valueChanged) + + // case .image: + // #if _iOS + // return "field_button_image" + // #else + // return "field_blank" + // #endif + // + // case .images: + // #if _iOS + // return "field_input_grid_images" + // #else + // return "field_blank" + // #endif + // + // case .signature: + // #if _iOS + // return "field_input_button_signature" + // #else + // return "field_blank" + // #endif + + default: + // assertionFailure("Not implemented") + break + } + } + return nil + } + + private func createOutput(output: FieldOutput) -> PlatformViewModel? { + if let xib = output.field?.xib { + // TODO: ... + } else { + return createOutputItem(output: output) + } + return nil + } + + open func createOutputItem(output: FieldOutput) -> FieldOutputTextViewModel { + let textViewModel = FieldOutputTextViewModel(output: output) + if let appScheme = appScheme, let link = output.link?.replacingOccurrences(of: "{APP_SCHEME}", with: appScheme) { + let routingRequest = RoutingRequest(url: link) + textViewModel.onTapAction = { + Router.shared?.navigate(to: routingRequest, animated: true, completion: nil) + } + } + return textViewModel + } +} diff --git a/PlatformUIJedio/PlatformUIJedio/Views/Components/Input/FieldInputBaseView.swift b/PlatformUIJedio/PlatformUIJedio/Views/Components/Input/FieldInputBaseView.swift new file mode 100644 index 000000000..df5beabd5 --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/Views/Components/Input/FieldInputBaseView.swift @@ -0,0 +1,37 @@ +// +// FieldInputBaseView.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/29/23. +// + +import SwiftUI +import PlatformUI +import Utilities +import JedioKit + +open class FieldInputBaseViewModel: PlatformViewModel { + @Published public var title: String? + @Published public var subtitle: String? + @Published public var text: String? + @Published public var image: String? + @Published public var valueChanged: ((Any?) -> Void)? + + @Published public var input: FieldInputProtocol? + + public init() {} + + public init(input: FieldInputProtocol, valueChanged: ((Any?) -> Void)?) { + self.input = input + self.valueChanged = valueChanged + if let title = input.title { + self.title = DataLocalizer.localize(path: title) + } + if let subtitle = input.subtitle { + self.subtitle = DataLocalizer.localize(path: subtitle) + } + if let text = input.text { + self.text = DataLocalizer.localize(path: text) + } + } +} diff --git a/PlatformUIJedio/PlatformUIJedio/Views/Components/Input/FieldInputSelectionGridView.swift b/PlatformUIJedio/PlatformUIJedio/Views/Components/Input/FieldInputSelectionGridView.swift new file mode 100644 index 000000000..44613cf15 --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/Views/Components/Input/FieldInputSelectionGridView.swift @@ -0,0 +1,134 @@ +// +// FieldInputSelectionGridView.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/29/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI +import PlatformUI +import Utilities + +public class FieldInputSelectionGridViewModel: FieldInputBaseViewModel { + private struct Option: Hashable { + var text: String + var value: String + var selected: Bool + } + + private var options: [Option] { + input?.fieldInput?.options?.compactMap { (dict: [String : Any]) in + if let text = dict["text"] as? String, + let value = dict["value"] as? String { + if selectedValue == value || (selectedValue == nil && value == "") { + return Option(text: text, value: value, selected: true) + } else { + return Option(text: text, value: value, selected: false) + } + } else { + return nil + } + } ?? [] + } + + private var optionValues: [String] { + input?.fieldInput?.options?.compactMap { (dict: [String : Any]) in + dict["value"] as? String + } ?? [] + } + + private var optionTexts: [(String, Bool)] { + input?.fieldInput?.options?.compactMap { (dict: [String : Any]) in + if let text = dict["text"] as? String, let value = dict["value"] as? String { + if selectedValue == value || (selectedValue == nil && value == "") { + return (text, true) + } else { + return (text, false) + } + } else { + return nil + } + } ?? [] + } + + private var selectedValue: String? { + input?.value as? String + } + + public static var previewValue: FieldInputSelectionGridViewModel { + let vm = FieldInputSelectionGridViewModel() + return vm + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + let columns = [GridItem(), GridItem()] + return AnyView( + VStack(alignment: .leading) { + Text(self.title ?? "") + .themeFont(fontSize: .medium) + LazyVGrid(columns: columns, spacing: 16) { + ForEach(self.options, id: \.self) { option in + let color = option.selected ? ThemeColor.SemanticColor.layer5 : ThemeColor.SemanticColor.layer3 + ZStack { + Text(option.text) + .themeFont(fontSize: .medium) + .padding() + .frame(maxWidth: .infinity) + .themeColor(background: color) + + if option.selected { + PlatformIconViewModel(type: .system(name: "checkmark"), size: CGSize(width: 8, height: 8)) + .createView(parentStyle: style) + .topAligned() + .rightAligned() + .padding(8) + } + } + .onTapGesture { + if !option.selected { + self.input?.value = option.value + self.valueChanged?(option.value) + self.objectWillChange.send() + } + } + } + } + } + .padding() + ) + } + } +} + +#if DEBUG +struct FieldInputSelectionGridView_Previews_Dark: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyDarkTheme() + ThemeSettings.applyStyles() + return FieldInputSelectionGridViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} + +struct FieldInputSelectionGridView_Previews_Light: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyLightTheme() + ThemeSettings.applyStyles() + return FieldInputSelectionGridViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} +#endif + diff --git a/PlatformUIJedio/PlatformUIJedio/Views/Components/Input/FieldInputSwitchView.swift b/PlatformUIJedio/PlatformUIJedio/Views/Components/Input/FieldInputSwitchView.swift new file mode 100644 index 000000000..4f034d2fe --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/Views/Components/Input/FieldInputSwitchView.swift @@ -0,0 +1,82 @@ +// +// FieldInputSwitchView.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/29/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI +import PlatformUI +import Utilities +import JedioKit + +public class FieldInputSwitchViewModel: FieldInputBaseViewModel { + public static var previewValue: FieldInputSwitchViewModel { + let vm = FieldInputSwitchViewModel() + vm.title = "title" + vm.subtitle = "subititle" + vm.text = "text" + return vm + } + + public lazy var inputBinding = Binding { + return self.input?.checked ?? false + } set: { newValue in + self.input?.checked = newValue + self.valueChanged?(newValue) + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + return AnyView( + HStack(spacing: 0) { + VStack(alignment: .leading) { + Text(self.title ?? "") + .themeFont(fontSize: .medium) + if let subtitle = self.subtitle { + Text(subtitle) + .themeColor(foreground: .textTertiary) + .themeFont(fontSize: .small) + } + } + .fixedSize() + Spacer() + Toggle("", isOn: self.inputBinding) + } + .padding() + ) + } + } +} + +#if DEBUG +struct FieldInputSwitchView_Previews_Dark: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyDarkTheme() + ThemeSettings.applyStyles() + return FieldInputSwitchViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} + +struct FieldInputSwitchView_Previews_Light: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyLightTheme() + ThemeSettings.applyStyles() + return FieldInputSwitchViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} +#endif + diff --git a/PlatformUIJedio/PlatformUIJedio/Views/Components/Input/FieldInputTextsInputView.swift b/PlatformUIJedio/PlatformUIJedio/Views/Components/Input/FieldInputTextsInputView.swift new file mode 100644 index 000000000..ed921e9bc --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/Views/Components/Input/FieldInputTextsInputView.swift @@ -0,0 +1,91 @@ +// +// FieldInputTextsInputView.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/29/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI +import PlatformUI +import Utilities + +public class FieldInputTextsInputViewModel: FieldInputBaseViewModel { + private lazy var inputBinding = Binding( + get: { + self.input?.value as? String ?? "" + }, + set: { + self.input?.value = $0 + } + ) + + public static var previewValue: FieldInputTextsInputViewModel { + let vm = FieldInputTextsInputViewModel() + return vm + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + return AnyView( + VStack(alignment: .leading) { + HStack { + Text(self.title ?? "") + .themeFont(fontSize: .medium) + Spacer() + if let subtitle = self.subtitle { + Text(subtitle) + .themeColor(foreground: .textTertiary) + .themeFont(fontSize: .small) + } + } + Spacer() + PlatformInputModel(value: self.inputBinding, + currentValue: self.input?.value as? String, + keyboardType: .default, + onEditingChanged: { [weak self] focused in + if (focused == false) { + self?.valueChanged?(self?.input?.value) + } + }) + .createView(parentStyle: style) + .padding(.vertical, 2) + .padding(.horizontal, 8) + .themeColor(background: .layer5) + } + .padding() + ) + } + } +} + +#if DEBUG +struct FieldInputTextsInputView_Previews_Dark: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyDarkTheme() + ThemeSettings.applyStyles() + return FieldInputTextsInputViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} + +struct FieldInputTextsInputView_Previews_Light: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyLightTheme() + ThemeSettings.applyStyles() + return FieldInputTextsInputViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} +#endif + diff --git a/PlatformUIJedio/PlatformUIJedio/Views/Components/Output/FieldOutputBaseView.swift b/PlatformUIJedio/PlatformUIJedio/Views/Components/Output/FieldOutputBaseView.swift new file mode 100644 index 000000000..fc6edac25 --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/Views/Components/Output/FieldOutputBaseView.swift @@ -0,0 +1,41 @@ +// +// FieldOutputBaseViewModel.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/21/23. +// + +import SwiftUI +import PlatformUI +import Utilities +import JedioKit + +open class FieldOutputBaseViewModel: PlatformViewModel { + @Published public var title: String? + @Published public var subtitle: String? + @Published public var text: String? + @Published public var subtext: String? + @Published public var image: String? + @Published public var link: String? + @Published public var onTapAction: (() -> Void)? + + public init() {} + + public init(output: FieldOutputProtocol) { + if let title = output.title { + self.title = DataLocalizer.localize(path: title) + } + if let subtitle = output.subtitle { + self.subtitle = DataLocalizer.localize(path: subtitle) + } + if let text = output.text { + self.text = DataLocalizer.localize(path: text) + } + if let subtext = output.subtext { + self.subtext = DataLocalizer.localize(path: subtext) + } + if let link = output.fieldOutput?.link?["text"] as? String { + self.link = link + } + } +} diff --git a/PlatformUIJedio/PlatformUIJedio/Views/Components/Output/FieldOutputTextView.swift b/PlatformUIJedio/PlatformUIJedio/Views/Components/Output/FieldOutputTextView.swift new file mode 100644 index 000000000..770f04154 --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/Views/Components/Output/FieldOutputTextView.swift @@ -0,0 +1,95 @@ +// +// FieldOutputTextView.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/21/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI +import PlatformUI +import Utilities + +public class FieldOutputTextViewModel: FieldOutputBaseViewModel { + public static var previewValue: FieldOutputTextViewModel { + let vm = FieldOutputTextViewModel() + vm.title = "title" + vm.subtitle = "subititle" + vm.text = "text" + vm.subtext = "subtext" + return vm + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + let main = + VStack(alignment: .leading) { + Text(self.title ?? "") + if let subtitle = self.subtitle { + Text(subtitle) + .themeFont(fontSize: .small) + } + } + .padding(.vertical, 4) + + let trailing = + HStack { + VStack(alignment: .trailing) { + Text(self.text ?? "") + .themeFont(fontSize: .medium) + if let subtext = self.subtext { + Text(subtext) + .themeFont(fontSize: .small) + } + } + if self.link != nil { + PlatformIconViewModel(type: .system(name: "chevron.right"), + size: CGSize(width: 16, height: 16), + templateColor: .textTertiary) + .createView(parentStyle: style) + } + } + .themeColor(foreground: .textTertiary) + + return AnyView( + PlatformTableViewCellViewModel(main: main.wrappedViewModel, + trailing: trailing.wrappedViewModel) + .createView(parentStyle: style) + .onTapGesture { [weak self] in + self?.onTapAction?() + } + ) + } + } +} + +#if DEBUG +struct FieldOutputTextView_Previews_Dark: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyDarkTheme() + ThemeSettings.applyStyles() + return FieldOutputTextViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} + +struct FieldOutputTextView_Previews_Light: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyLightTheme() + ThemeSettings.applyStyles() + return FieldOutputTextViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} +#endif + diff --git a/PlatformUIJedio/PlatformUIJedio/Views/Components/SettingHeaderView.swift b/PlatformUIJedio/PlatformUIJedio/Views/Components/SettingHeaderView.swift new file mode 100644 index 000000000..d7528c1f0 --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/Views/Components/SettingHeaderView.swift @@ -0,0 +1,78 @@ +// +// SettingHeaderView.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/20/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI +import PlatformUI +import Utilities + +public class SettingHeaderViewModel: PlatformViewModel { + @Published public var text: String? + @Published public var dismissAction: (() -> Void)? + + public init() { } + + public static var previewValue: SettingHeaderViewModel { + let vm = SettingHeaderViewModel() + vm.text = "Header" + return vm + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + return AnyView( + HStack(spacing: 16) { + if let dismissAction = self.dismissAction { + let buttonContent = PlatformIconViewModel(type: .system(name: "chevron.left"), size: CGSize(width: 16, height: 16), templateColor: .textTertiary) + PlatformButtonViewModel(content: buttonContent, type: .iconType) { + dismissAction() + } + .createView(parentStyle: style) + .padding([.leading, .vertical], 8) + } + + Text(self.text ?? "") + .themeFont(fontType: .base, fontSize: .largest) + .themeColor(foreground: .textPrimary) + } + .padding(.horizontal, 8) + .padding(.top, 8) + ) + } + } +} + +#if DEBUG +struct SettingHeaderView_Previews_Dark: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyDarkTheme() + ThemeSettings.applyStyles() + return SettingHeaderViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} + +struct SettingHeaderView_Previews_Light: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyLightTheme() + ThemeSettings.applyStyles() + return SettingHeaderViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} +#endif + diff --git a/PlatformUIJedio/PlatformUIJedio/Views/Components/SettingOptionView.swift b/PlatformUIJedio/PlatformUIJedio/Views/Components/SettingOptionView.swift new file mode 100644 index 000000000..143dc09d4 --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/Views/Components/SettingOptionView.swift @@ -0,0 +1,78 @@ +// +// SettingOptionView.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/20/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI +import PlatformUI +import Utilities + +public class SettingOptionViewModel: PlatformViewModel { + @Published public var text: String? + @Published public var isSelected = false + @Published public var onTapAction: (() -> Void)? + + public init() { } + + public static var previewValue: SettingOptionViewModel { + let vm = SettingOptionViewModel() + vm.text = "Test String" + vm.isSelected = true + return vm + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + let main = Text(self.text ?? "") + .padding(.vertical, 4) + let trailing: PlatformViewModel + if self.isSelected { + trailing = PlatformIconViewModel(type: .system(name: "checkmark"), size: CGSize(width: 16, height: 16)) + } else { + trailing = PlatformView.nilViewModel + } + return AnyView( + PlatformTableViewCellViewModel(main: main.wrappedViewModel, + trailing: trailing) + .createView(parentStyle: parentStyle) + .onTapGesture { [weak self] in + self?.onTapAction?() + } + ) + } + } +} + +#if DEBUG +struct SettingOptionView_Previews_Dark: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyDarkTheme() + ThemeSettings.applyStyles() + return SettingOptionViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} + +struct SettingOptionView_Previews_Light: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyLightTheme() + ThemeSettings.applyStyles() + return SettingOptionViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} +#endif + diff --git a/PlatformUIJedio/PlatformUIJedio/Views/SettingsView.swift b/PlatformUIJedio/PlatformUIJedio/Views/SettingsView.swift new file mode 100644 index 000000000..6680bb8ce --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedio/Views/SettingsView.swift @@ -0,0 +1,131 @@ +// +// SettingsView.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/20/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import SwiftUI +import PlatformUI +import Utilities + +public class SettingsViewModel: PlatformViewModel { + public struct SectionViewModel: Identifiable { + public var id: String { + title ?? UUID().uuidString + } + public var title: String? + public var items: PlatformListViewModel? + + public init(title: String? = nil, items: PlatformListViewModel? = nil) { + self.title = title + self.items = items + } + } + + @Published public var headerViewModel: PlatformViewModel? = SettingHeaderViewModel() + @Published public var sections: [SectionViewModel] = [] { + didSet { + sections.forEach { section in + section.items?.contentChanged = { [weak self] in + self?.objectWillChange.send() + } + } + } + } + @Published public var footerViewModel: PlatformViewModel? + + public init(headerViewModel: PlatformViewModel? = nil, sections: [SectionViewModel] = [], footerViewModel: PlatformViewModel? = nil) { + self.headerViewModel = headerViewModel + self.sections = sections + self.footerViewModel = footerViewModel + } + + public static var previewValue: SettingsViewModel { + let vm = SettingsViewModel() + vm.headerViewModel = SettingHeaderViewModel.previewValue + let itemList = PlatformListViewModel() + itemList.items = [ + Text("Item 1").wrappedViewModel, + Text("Item 2").wrappedViewModel, + Text("Item 3").wrappedViewModel + ] + vm.sections = [SectionViewModel(title: "Title", items: itemList)] + return vm + } + + public override func createView(parentStyle: ThemeStyle = ThemeStyle.defaultStyle, styleKey: String? = nil) -> PlatformView { + PlatformView(viewModel: self, parentStyle: parentStyle, styleKey: styleKey) { [weak self] style in + guard let self = self else { return AnyView(PlatformView.nilView) } + + let view = AnyView( + VStack(alignment: .leading, spacing: 30) { + self.headerViewModel?.createView(parentStyle: style) + + ScrollView(showsIndicators: false) { + //note, using a LazyVStack here caused app unresponsiveness, needs investigation + // settings lists are fairly static/finite, so no concerned about using a VStack + VStack { + ForEach(self.sections) { section in + if let sectionTitle = section.title { + let header = Text(sectionTitle) + .leftAligned() + + Section(header: header) { + section.items?.createView(parentStyle: style) + } + } else { + Section { + section.items?.createView(parentStyle: style) + } + } + } + + self.footerViewModel?.createView(parentStyle: style) + } + } + + Spacer() + } + .padding([.leading, .trailing]) + .themeColor(background: .layer2) + .navigationViewEmbedded(backgroundColor: ThemeColor.SemanticColor.layer2.color) + ) + + // make it visible under the tabbar + return AnyView( + view.ignoresSafeArea(edges: [.bottom]) + ) + } + } +} + +#if DEBUG +struct SettingsView_Previews_Dark: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyDarkTheme() + ThemeSettings.applyStyles() + return SettingsViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} + +struct SettingsView_Previews_Light: PreviewProvider { + @StateObject static var themeSettings = ThemeSettings.shared + + static var previews: some View { + ThemeSettings.applyLightTheme() + ThemeSettings.applyStyles() + return SettingsViewModel.previewValue + .createView() + // .edgesIgnoringSafeArea(.bottom) + .previewLayout(.sizeThatFits) + } +} +#endif + diff --git a/PlatformUIJedio/PlatformUIJedioTests/PlatformUIJedioTests.swift b/PlatformUIJedio/PlatformUIJedioTests/PlatformUIJedioTests.swift new file mode 100644 index 000000000..35a906ce0 --- /dev/null +++ b/PlatformUIJedio/PlatformUIJedioTests/PlatformUIJedioTests.swift @@ -0,0 +1,36 @@ +// +// PlatformUIJedioTests.swift +// PlatformUIJedioTests +// +// Created by Rui Huang on 3/20/23. +// + +import XCTest +@testable import PlatformUIJedio + +final class PlatformUIJedioTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/README.md b/README.md index 5ce0ef9f7..cd4da38bd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ -# Background +
+ icon +
+

v4-native-ios

+ + This is the native iOS app for dYdX v4. @@ -26,7 +35,7 @@ Other dependencies are specified by the Cocoapods and Swift Package Manager conf # API Keys & Secrets Unzip the `secrets.zip` from the `iOS Secrets` vault in the dYdX 1Password account. Ask a team member for access. -Add the `secrets/` folder to the native-ios-v4/scripts folder. +Add the `secrets/` folder to the v4-native-ios/scripts folder. > `mv {REPLACE_WITH_PATH_TO_UNZIPPED}/secrets {REPLACE_WITH_REPO}/scripts` @@ -41,17 +50,9 @@ https://apps.apple.com/us/app/transporter/id1450874784?mt=12 # Update Javascript -Javascript code is generated in v4-client. To update - -Get the desired commit from v4-client -Copy from {v4-client}/__native__/__ios__/v4-native-client.js -to {native-ios-v4}/dydx/dydxPresenters/_Feature/ - -To generate v4-native-client.js from the v4-client repo, run +Javascript code is generated from v4-client. Note, this shell script must be executed from the **scripts/** directory. It will attemp to clone `v4-clients` if `v4-clients` does not exist next to where you have checked out `v4-native-ios` -> npm run build - -> npm run webpack +> ./update_client_apis.sh # Update Fonts @@ -83,3 +84,8 @@ Your project should have one or more theme files. For each theme file, you must replace the values at paths `themeFont.type.bold`, `themeFont.type.text`, or `themeFont.type.number` for each custom font you want to use. + +_______ +*By using, recording, referencing, or downloading (i.e., any “action”) any information contained on this page or in any dYdX Trading Inc. ("dYdX") database or documentation, you hereby and thereby agree to the [v4 Terms of Use](https://dydx.exchange/v4-terms) and [Privacy Policy](https://dydx.exchange/privacy) governing such information, and you agree that such action establishes a binding agreement between you and dYdX.* + +*This documentation provides information on how to use dYdX v4 software (”dYdX Chain”). dYdX does not deploy or run v4 software for public use, or operate or control any dYdX Chain infrastructure. dYdX is not responsible for any actions taken by other third parties who use v4 software. dYdX services and products are not available to persons or entities who reside in, are located in, are incorporated in, or have registered offices in the United States or Canada, or Restricted Persons (as defined in the dYdX [Terms of Use](https://dydx.exchange/terms)). The content provided herein does not constitute, and should not be considered, or relied upon as, financial advice, legal advice, tax advice, investment advice or advice of any other nature, and you agree that you are responsible to conduct independent research, perform due diligence and engage a professional advisor prior to taking any financial, tax, legal or investment action related to the foregoing content. The information contained herein, and any use of v4 software, are subject to the [v4 Terms of Use](https://dydx.exchange/v4-terms).* diff --git a/RoutingKit/RoutingKit.xcodeproj/project.pbxproj b/RoutingKit/RoutingKit.xcodeproj/project.pbxproj new file mode 100644 index 000000000..3c66ec06d --- /dev/null +++ b/RoutingKit/RoutingKit.xcodeproj/project.pbxproj @@ -0,0 +1,1175 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 19ED0C3768C72BB040BDF0E6 /* Pods_iOS_RoutingKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D016DAF18F37232259AF612 /* Pods_iOS_RoutingKitTests.framework */; }; + 311D6FA82176F02800655040 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 311D6F502176EF4B00655040 /* Utilities.framework */; }; + 313EB22C21BA26D800BEF926 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB22321BA26D700BEF926 /* RoutingKit.framework */; }; + 313EB23121BA26D800BEF926 /* RoutingKitAppleTVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EB23021BA26D800BEF926 /* RoutingKitAppleTVTests.swift */; }; + 313EB23321BA26D800BEF926 /* RoutingKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 313EB22521BA26D800BEF926 /* RoutingKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 313EB26521BA271400BEF926 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB23E21BA26D800BEF926 /* Utilities.framework */; }; + 31471F8C24BA320200057221 /* BadgingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471F8B24BA320200057221 /* BadgingProtocol.swift */; }; + 31471F8D24BA320200057221 /* BadgingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471F8B24BA320200057221 /* BadgingProtocol.swift */; }; + 31471F8E24BA320200057221 /* BadgingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471F8B24BA320200057221 /* BadgingProtocol.swift */; }; + 314B628D23DCCEBB00139EB3 /* MappedRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628723DCCEBB00139EB3 /* MappedRouter.swift */; }; + 314B628E23DCCEBB00139EB3 /* MappedRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628723DCCEBB00139EB3 /* MappedRouter.swift */; }; + 314B628F23DCCEBB00139EB3 /* MappedRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628723DCCEBB00139EB3 /* MappedRouter.swift */; }; + 314B629123DCCEBC00139EB3 /* RouterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628823DCCEBB00139EB3 /* RouterProtocol.swift */; }; + 314B629223DCCEBC00139EB3 /* RouterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628823DCCEBB00139EB3 /* RouterProtocol.swift */; }; + 314B629323DCCEBC00139EB3 /* RouterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628823DCCEBB00139EB3 /* RouterProtocol.swift */; }; + 314B629523DCCEBC00139EB3 /* RoutingHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628923DCCEBB00139EB3 /* RoutingHistory.swift */; }; + 314B629623DCCEBC00139EB3 /* RoutingHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628923DCCEBB00139EB3 /* RoutingHistory.swift */; }; + 314B629723DCCEBC00139EB3 /* RoutingHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628923DCCEBB00139EB3 /* RoutingHistory.swift */; }; + 314B629923DCCEBC00139EB3 /* RoutingRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628A23DCCEBB00139EB3 /* RoutingRequest.swift */; }; + 314B629A23DCCEBC00139EB3 /* RoutingRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628A23DCCEBB00139EB3 /* RoutingRequest.swift */; }; + 314B629B23DCCEBC00139EB3 /* RoutingRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628A23DCCEBB00139EB3 /* RoutingRequest.swift */; }; + 314B629D23DCCEBC00139EB3 /* RoutingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628C23DCCEBB00139EB3 /* RoutingAction.swift */; }; + 314B629E23DCCEBC00139EB3 /* RoutingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628C23DCCEBB00139EB3 /* RoutingAction.swift */; }; + 314B629F23DCCEBC00139EB3 /* RoutingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B628C23DCCEBB00139EB3 /* RoutingAction.swift */; }; + 3196825F21B7947600AE0F28 /* RoutingKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 3196825D21B7947600AE0F28 /* RoutingKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3196826921B7948900AE0F28 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3196826521B7947600AE0F28 /* Utilities.framework */; }; + 31E65B0B216BCA10008ABEE9 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31E65B01216BCA10008ABEE9 /* RoutingKit.framework */; }; + 31E65B10216BCA10008ABEE9 /* RoutingKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E65B0F216BCA10008ABEE9 /* RoutingKitTests.swift */; }; + 31E65B12216BCA10008ABEE9 /* RoutingKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 31E65B04216BCA10008ABEE9 /* RoutingKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E5BA1B2558054BFA0799D92D /* Pods_iOS_RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ACE60444F1016EE6EB4C7C82 /* Pods_iOS_RoutingKit.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 311D6F4F2176EF4B00655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F4A2176EF4B00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 311D6F512176EF4B00655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F4A2176EF4B00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 311D6F532176EF5400655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F4A2176EF4B00655040 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 313EB22D21BA26D800BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31E65AF8216BCA10008ABEE9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 313EB22221BA26D700BEF926; + remoteInfo = RoutingKitAppleTV; + }; + 313EB23D21BA26D800BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F4A2176EF4B00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 313EB24121BA26D800BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F4A2176EF4B00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 313EB26321BA270B00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F4A2176EF4B00655040 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313A536421B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 3196826421B7947600AE0F28 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F4A2176EF4B00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 3196826721B7948000AE0F28 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F4A2176EF4B00655040 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 3196823621B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 31E65B0C216BCA10008ABEE9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31E65AF8216BCA10008ABEE9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31E65B00216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 01FD8358AB075E5F21E52A66 /* Pods-iOS-RoutingKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-RoutingKit.debug.xcconfig"; path = "Target Support Files/Pods-iOS-RoutingKit/Pods-iOS-RoutingKit.debug.xcconfig"; sourceTree = ""; }; + 289CD7AB972AAFCD780017C4 /* Pods-iOS-RoutingKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-RoutingKitTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-RoutingKitTests/Pods-iOS-RoutingKitTests.release.xcconfig"; sourceTree = ""; }; + 311D6F4A2176EF4B00655040 /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 313EB22321BA26D700BEF926 /* RoutingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RoutingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EB22521BA26D800BEF926 /* RoutingKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoutingKit.h; sourceTree = ""; }; + 313EB22621BA26D800BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EB22B21BA26D800BEF926 /* RoutingKitAppleTVTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RoutingKitAppleTVTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EB23021BA26D800BEF926 /* RoutingKitAppleTVTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingKitAppleTVTests.swift; sourceTree = ""; }; + 313EB23221BA26D800BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31471F8B24BA320200057221 /* BadgingProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgingProtocol.swift; sourceTree = ""; }; + 314B628723DCCEBB00139EB3 /* MappedRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappedRouter.swift; sourceTree = ""; }; + 314B628823DCCEBB00139EB3 /* RouterProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouterProtocol.swift; sourceTree = ""; }; + 314B628923DCCEBB00139EB3 /* RoutingHistory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutingHistory.swift; sourceTree = ""; }; + 314B628A23DCCEBB00139EB3 /* RoutingRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutingRequest.swift; sourceTree = ""; }; + 314B628C23DCCEBB00139EB3 /* RoutingAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutingAction.swift; sourceTree = ""; }; + 3196825B21B7947600AE0F28 /* RoutingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RoutingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3196825D21B7947600AE0F28 /* RoutingKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoutingKit.h; sourceTree = ""; }; + 3196825E21B7947600AE0F28 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31CEE9D72170FB6D00DC61DA /* Utilities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Utilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31E65B01216BCA10008ABEE9 /* RoutingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RoutingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31E65B04216BCA10008ABEE9 /* RoutingKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoutingKit.h; sourceTree = ""; }; + 31E65B05216BCA10008ABEE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31E65B0A216BCA10008ABEE9 /* RoutingKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RoutingKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 31E65B0F216BCA10008ABEE9 /* RoutingKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingKitTests.swift; sourceTree = ""; }; + 31E65B11216BCA10008ABEE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 36609126661F079A0DAEE5AB /* Pods-iOS-RoutingKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-RoutingKitTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-RoutingKitTests/Pods-iOS-RoutingKitTests.debug.xcconfig"; sourceTree = ""; }; + 6D016DAF18F37232259AF612 /* Pods_iOS_RoutingKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_RoutingKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 751F8CCFC4CC9BEED49011F2 /* Pods-iOS-RoutingKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-RoutingKit.release.xcconfig"; path = "Target Support Files/Pods-iOS-RoutingKit/Pods-iOS-RoutingKit.release.xcconfig"; sourceTree = ""; }; + ACE60444F1016EE6EB4C7C82 /* Pods_iOS_RoutingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_RoutingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 313EB22021BA26D700BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB26521BA271400BEF926 /* Utilities.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB22821BA26D800BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB22C21BA26D800BEF926 /* RoutingKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3196825821B7947600AE0F28 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3196826921B7948900AE0F28 /* Utilities.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AFE216BCA10008ABEE9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 311D6FA82176F02800655040 /* Utilities.framework in Frameworks */, + E5BA1B2558054BFA0799D92D /* Pods_iOS_RoutingKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65B07216BCA10008ABEE9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31E65B0B216BCA10008ABEE9 /* RoutingKit.framework in Frameworks */, + 19ED0C3768C72BB040BDF0E6 /* Pods_iOS_RoutingKitTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02684F1E28BD41810007CEFF /* Dependencies */ = { + isa = PBXGroup; + children = ( + 311D6F4A2176EF4B00655040 /* Utilities.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 311D6F4B2176EF4B00655040 /* Products */ = { + isa = PBXGroup; + children = ( + 311D6F502176EF4B00655040 /* Utilities.framework */, + 3196826521B7947600AE0F28 /* Utilities.framework */, + 313EB23E21BA26D800BEF926 /* Utilities.framework */, + 311D6F522176EF4B00655040 /* UtilitiesTests.xctest */, + 313EB24221BA26D800BEF926 /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 313EB22421BA26D800BEF926 /* RoutingKitAppleTV */ = { + isa = PBXGroup; + children = ( + 313EB22521BA26D800BEF926 /* RoutingKit.h */, + 313EB22621BA26D800BEF926 /* Info.plist */, + ); + path = RoutingKitAppleTV; + sourceTree = ""; + }; + 313EB22F21BA26D800BEF926 /* RoutingKitAppleTVTests */ = { + isa = PBXGroup; + children = ( + 313EB23021BA26D800BEF926 /* RoutingKitAppleTVTests.swift */, + 313EB23221BA26D800BEF926 /* Info.plist */, + ); + path = RoutingKitAppleTVTests; + sourceTree = ""; + }; + 31471F8824BA31E900057221 /* _Badging */ = { + isa = PBXGroup; + children = ( + 31471F8B24BA320200057221 /* BadgingProtocol.swift */, + ); + path = _Badging; + sourceTree = ""; + }; + 314B628623DCCEBB00139EB3 /* _Router */ = { + isa = PBXGroup; + children = ( + 314B628723DCCEBB00139EB3 /* MappedRouter.swift */, + 314B628823DCCEBB00139EB3 /* RouterProtocol.swift */, + 314B628923DCCEBB00139EB3 /* RoutingHistory.swift */, + 314B628A23DCCEBB00139EB3 /* RoutingRequest.swift */, + ); + path = _Router; + sourceTree = ""; + }; + 314B628B23DCCEBB00139EB3 /* _Action */ = { + isa = PBXGroup; + children = ( + 314B628C23DCCEBB00139EB3 /* RoutingAction.swift */, + ); + path = _Action; + sourceTree = ""; + }; + 3196825C21B7947600AE0F28 /* RoutingKitAppleWatch */ = { + isa = PBXGroup; + children = ( + 3196825D21B7947600AE0F28 /* RoutingKit.h */, + 3196825E21B7947600AE0F28 /* Info.plist */, + ); + path = RoutingKitAppleWatch; + sourceTree = ""; + }; + 31E65AF7216BCA10008ABEE9 = { + isa = PBXGroup; + children = ( + 02684F1E28BD41810007CEFF /* Dependencies */, + 368D976F606FDCA7601F61E5 /* Frameworks */, + 31E65B02216BCA10008ABEE9 /* Products */, + 31E65B03216BCA10008ABEE9 /* RoutingKit */, + 313EB22421BA26D800BEF926 /* RoutingKitAppleTV */, + 313EB22F21BA26D800BEF926 /* RoutingKitAppleTVTests */, + 3196825C21B7947600AE0F28 /* RoutingKitAppleWatch */, + 31E65B0E216BCA10008ABEE9 /* RoutingKitTests */, + 386DF2AA8AB0261E9E127A2C /* Pods */, + ); + sourceTree = ""; + }; + 31E65B02216BCA10008ABEE9 /* Products */ = { + isa = PBXGroup; + children = ( + 31E65B01216BCA10008ABEE9 /* RoutingKit.framework */, + 31E65B0A216BCA10008ABEE9 /* RoutingKitTests.xctest */, + 3196825B21B7947600AE0F28 /* RoutingKit.framework */, + 313EB22321BA26D700BEF926 /* RoutingKit.framework */, + 313EB22B21BA26D800BEF926 /* RoutingKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31E65B03216BCA10008ABEE9 /* RoutingKit */ = { + isa = PBXGroup; + children = ( + 31E65B04216BCA10008ABEE9 /* RoutingKit.h */, + 31E65B05216BCA10008ABEE9 /* Info.plist */, + 314B628B23DCCEBB00139EB3 /* _Action */, + 31471F8824BA31E900057221 /* _Badging */, + 314B628623DCCEBB00139EB3 /* _Router */, + ); + path = RoutingKit; + sourceTree = ""; + }; + 31E65B0E216BCA10008ABEE9 /* RoutingKitTests */ = { + isa = PBXGroup; + children = ( + 31E65B0F216BCA10008ABEE9 /* RoutingKitTests.swift */, + 31E65B11216BCA10008ABEE9 /* Info.plist */, + ); + path = RoutingKitTests; + sourceTree = ""; + }; + 368D976F606FDCA7601F61E5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 31CEE9D72170FB6D00DC61DA /* Utilities.framework */, + ACE60444F1016EE6EB4C7C82 /* Pods_iOS_RoutingKit.framework */, + 6D016DAF18F37232259AF612 /* Pods_iOS_RoutingKitTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 386DF2AA8AB0261E9E127A2C /* Pods */ = { + isa = PBXGroup; + children = ( + 01FD8358AB075E5F21E52A66 /* Pods-iOS-RoutingKit.debug.xcconfig */, + 751F8CCFC4CC9BEED49011F2 /* Pods-iOS-RoutingKit.release.xcconfig */, + 36609126661F079A0DAEE5AB /* Pods-iOS-RoutingKitTests.debug.xcconfig */, + 289CD7AB972AAFCD780017C4 /* Pods-iOS-RoutingKitTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 313EB21E21BA26D700BEF926 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB23321BA26D800BEF926 /* RoutingKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3196825621B7947600AE0F28 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 3196825F21B7947600AE0F28 /* RoutingKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AFC216BCA10008ABEE9 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 31E65B12216BCA10008ABEE9 /* RoutingKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 313EB22221BA26D700BEF926 /* RoutingKitAppleTV */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EB24521BA26D800BEF926 /* Build configuration list for PBXNativeTarget "RoutingKitAppleTV" */; + buildPhases = ( + 313EB21E21BA26D700BEF926 /* Headers */, + 313EB21F21BA26D700BEF926 /* Sources */, + 313EB22021BA26D700BEF926 /* Frameworks */, + 313EB22121BA26D700BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EB26421BA270B00BEF926 /* PBXTargetDependency */, + ); + name = RoutingKitAppleTV; + productName = RoutingKitAppleTV; + productReference = 313EB22321BA26D700BEF926 /* RoutingKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 313EB22A21BA26D800BEF926 /* RoutingKitAppleTVTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EB24621BA26D800BEF926 /* Build configuration list for PBXNativeTarget "RoutingKitAppleTVTests" */; + buildPhases = ( + 313EB22721BA26D800BEF926 /* Sources */, + 313EB22821BA26D800BEF926 /* Frameworks */, + 313EB22921BA26D800BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EB22E21BA26D800BEF926 /* PBXTargetDependency */, + ); + name = RoutingKitAppleTVTests; + productName = RoutingKitAppleTVTests; + productReference = 313EB22B21BA26D800BEF926 /* RoutingKitAppleTVTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 3196825A21B7947600AE0F28 /* RoutingKitAppleWatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3196826621B7947600AE0F28 /* Build configuration list for PBXNativeTarget "RoutingKitAppleWatch" */; + buildPhases = ( + 3196825621B7947600AE0F28 /* Headers */, + 3196825721B7947600AE0F28 /* Sources */, + 3196825821B7947600AE0F28 /* Frameworks */, + 3196825921B7947600AE0F28 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3196826821B7948000AE0F28 /* PBXTargetDependency */, + ); + name = RoutingKitAppleWatch; + productName = RoutingKitAppleWatch; + productReference = 3196825B21B7947600AE0F28 /* RoutingKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 31E65B00216BCA10008ABEE9 /* RoutingKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31E65B15216BCA10008ABEE9 /* Build configuration list for PBXNativeTarget "RoutingKit" */; + buildPhases = ( + 0BC7F71913D87FDC224160FA /* [CP] Check Pods Manifest.lock */, + 31E65AFC216BCA10008ABEE9 /* Headers */, + 31E65AFD216BCA10008ABEE9 /* Sources */, + 31E65AFE216BCA10008ABEE9 /* Frameworks */, + 31E65AFF216BCA10008ABEE9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 311D6F542176EF5400655040 /* PBXTargetDependency */, + ); + name = RoutingKit; + productName = RoutingKit; + productReference = 31E65B01216BCA10008ABEE9 /* RoutingKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 31E65B09216BCA10008ABEE9 /* RoutingKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31E65B18216BCA10008ABEE9 /* Build configuration list for PBXNativeTarget "RoutingKitTests" */; + buildPhases = ( + 3A0AEEE2A640025077F4EBD6 /* [CP] Check Pods Manifest.lock */, + 31E65B06216BCA10008ABEE9 /* Sources */, + 31E65B07216BCA10008ABEE9 /* Frameworks */, + 31E65B08216BCA10008ABEE9 /* Resources */, + 8C9DA90EC5FE4F04F45340BB /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 31E65B0D216BCA10008ABEE9 /* PBXTargetDependency */, + ); + name = RoutingKitTests; + productName = RoutingKitTests; + productReference = 31E65B0A216BCA10008ABEE9 /* RoutingKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 31E65AF8216BCA10008ABEE9 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "dYdX Trading Inc"; + TargetAttributes = { + 313EB22221BA26D700BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 313EB22A21BA26D800BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 3196825A21B7947600AE0F28 = { + CreatedOnToolsVersion = 10.1; + }; + 31E65B00216BCA10008ABEE9 = { + CreatedOnToolsVersion = 10.0; + LastSwiftMigration = 1020; + }; + 31E65B09216BCA10008ABEE9 = { + CreatedOnToolsVersion = 10.0; + }; + }; + }; + buildConfigurationList = 31E65AFB216BCA10008ABEE9 /* Build configuration list for PBXProject "RoutingKit" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 31E65AF7216BCA10008ABEE9; + productRefGroup = 31E65B02216BCA10008ABEE9 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 311D6F4B2176EF4B00655040 /* Products */; + ProjectRef = 311D6F4A2176EF4B00655040 /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 31E65B00216BCA10008ABEE9 /* RoutingKit */, + 3196825A21B7947600AE0F28 /* RoutingKitAppleWatch */, + 313EB22221BA26D700BEF926 /* RoutingKitAppleTV */, + 31E65B09216BCA10008ABEE9 /* RoutingKitTests */, + 313EB22A21BA26D800BEF926 /* RoutingKitAppleTVTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 311D6F502176EF4B00655040 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 311D6F4F2176EF4B00655040 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 311D6F522176EF4B00655040 /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 311D6F512176EF4B00655040 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB23E21BA26D800BEF926 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 313EB23D21BA26D800BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB24221BA26D800BEF926 /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 313EB24121BA26D800BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 3196826521B7947600AE0F28 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 3196826421B7947600AE0F28 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 313EB22121BA26D700BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB22921BA26D800BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3196825921B7947600AE0F28 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AFF216BCA10008ABEE9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65B08216BCA10008ABEE9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0BC7F71913D87FDC224160FA /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-RoutingKit-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3A0AEEE2A640025077F4EBD6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-RoutingKitTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8C9DA90EC5FE4F04F45340BB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-RoutingKitTests/Pods-iOS-RoutingKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-RoutingKitTests/Pods-iOS-RoutingKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-RoutingKitTests/Pods-iOS-RoutingKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 313EB21F21BA26D700BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B629F23DCCEBC00139EB3 /* RoutingAction.swift in Sources */, + 314B629B23DCCEBC00139EB3 /* RoutingRequest.swift in Sources */, + 314B629723DCCEBC00139EB3 /* RoutingHistory.swift in Sources */, + 314B629323DCCEBC00139EB3 /* RouterProtocol.swift in Sources */, + 31471F8E24BA320200057221 /* BadgingProtocol.swift in Sources */, + 314B628F23DCCEBB00139EB3 /* MappedRouter.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB22721BA26D800BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB23121BA26D800BEF926 /* RoutingKitAppleTVTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3196825721B7947600AE0F28 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B629E23DCCEBC00139EB3 /* RoutingAction.swift in Sources */, + 314B629A23DCCEBC00139EB3 /* RoutingRequest.swift in Sources */, + 314B629623DCCEBC00139EB3 /* RoutingHistory.swift in Sources */, + 314B629223DCCEBC00139EB3 /* RouterProtocol.swift in Sources */, + 31471F8D24BA320200057221 /* BadgingProtocol.swift in Sources */, + 314B628E23DCCEBB00139EB3 /* MappedRouter.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AFD216BCA10008ABEE9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B629D23DCCEBC00139EB3 /* RoutingAction.swift in Sources */, + 314B629923DCCEBC00139EB3 /* RoutingRequest.swift in Sources */, + 314B629523DCCEBC00139EB3 /* RoutingHistory.swift in Sources */, + 314B629123DCCEBC00139EB3 /* RouterProtocol.swift in Sources */, + 31471F8C24BA320200057221 /* BadgingProtocol.swift in Sources */, + 314B628D23DCCEBB00139EB3 /* MappedRouter.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65B06216BCA10008ABEE9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31E65B10216BCA10008ABEE9 /* RoutingKitTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 311D6F542176EF5400655040 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 311D6F532176EF5400655040 /* PBXContainerItemProxy */; + }; + 313EB22E21BA26D800BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 313EB22221BA26D700BEF926 /* RoutingKitAppleTV */; + targetProxy = 313EB22D21BA26D800BEF926 /* PBXContainerItemProxy */; + }; + 313EB26421BA270B00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleTV; + targetProxy = 313EB26321BA270B00BEF926 /* PBXContainerItemProxy */; + }; + 3196826821B7948000AE0F28 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleWatch; + targetProxy = 3196826721B7948000AE0F28 /* PBXContainerItemProxy */; + }; + 31E65B0D216BCA10008ABEE9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31E65B00216BCA10008ABEE9 /* RoutingKit */; + targetProxy = 31E65B0C216BCA10008ABEE9 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 313EB23421BA26D800BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = RoutingKitAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.RoutingKitAppleTV; + PRODUCT_NAME = RoutingKit; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313EB23521BA26D800BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = RoutingKitAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.RoutingKitAppleTV; + PRODUCT_NAME = RoutingKit; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 313EB23621BA26D800BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = RoutingKitAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.RoutingKitAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313EB23721BA26D800BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = RoutingKitAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.RoutingKitAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 3196826021B7947600AE0F28 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = RoutingKitAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.RoutingKitAppleWatch; + PRODUCT_NAME = RoutingKit; + SDKROOT = watchos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Debug; + }; + 3196826121B7947600AE0F28 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = RoutingKitAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.RoutingKitAppleWatch; + PRODUCT_NAME = RoutingKit; + SDKROOT = watchos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Release; + }; + 31E65B13216BCA10008ABEE9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 31E65B14216BCA10008ABEE9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 31E65B16216BCA10008ABEE9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 01FD8358AB075E5F21E52A66 /* Pods-iOS-RoutingKit.debug.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = RoutingKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.RoutingKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31E65B17216BCA10008ABEE9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 751F8CCFC4CC9BEED49011F2 /* Pods-iOS-RoutingKit.release.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = RoutingKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.RoutingKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 31E65B19216BCA10008ABEE9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 36609126661F079A0DAEE5AB /* Pods-iOS-RoutingKitTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = RoutingKitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.RoutingKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31E65B1A216BCA10008ABEE9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 289CD7AB972AAFCD780017C4 /* Pods-iOS-RoutingKitTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = RoutingKitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.RoutingKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 313EB24521BA26D800BEF926 /* Build configuration list for PBXNativeTarget "RoutingKitAppleTV" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EB23421BA26D800BEF926 /* Debug */, + 313EB23521BA26D800BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313EB24621BA26D800BEF926 /* Build configuration list for PBXNativeTarget "RoutingKitAppleTVTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EB23621BA26D800BEF926 /* Debug */, + 313EB23721BA26D800BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3196826621B7947600AE0F28 /* Build configuration list for PBXNativeTarget "RoutingKitAppleWatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3196826021B7947600AE0F28 /* Debug */, + 3196826121B7947600AE0F28 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31E65AFB216BCA10008ABEE9 /* Build configuration list for PBXProject "RoutingKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31E65B13216BCA10008ABEE9 /* Debug */, + 31E65B14216BCA10008ABEE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31E65B15216BCA10008ABEE9 /* Build configuration list for PBXNativeTarget "RoutingKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31E65B16216BCA10008ABEE9 /* Debug */, + 31E65B17216BCA10008ABEE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31E65B18216BCA10008ABEE9 /* Build configuration list for PBXNativeTarget "RoutingKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31E65B19216BCA10008ABEE9 /* Debug */, + 31E65B1A216BCA10008ABEE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 31E65AF8216BCA10008ABEE9 /* Project object */; +} diff --git a/RoutingKit/RoutingKit.xcodeproj/xcshareddata/xcschemes/RoutingKit.xcscheme b/RoutingKit/RoutingKit.xcodeproj/xcshareddata/xcschemes/RoutingKit.xcscheme new file mode 100644 index 000000000..b8a656427 --- /dev/null +++ b/RoutingKit/RoutingKit.xcodeproj/xcshareddata/xcschemes/RoutingKit.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RoutingKit/RoutingKit/Info.plist b/RoutingKit/RoutingKit/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/RoutingKit/RoutingKit/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/RoutingKit/RoutingKit/RoutingKit.h b/RoutingKit/RoutingKit/RoutingKit.h new file mode 100644 index 000000000..aebc20060 --- /dev/null +++ b/RoutingKit/RoutingKit/RoutingKit.h @@ -0,0 +1,17 @@ +// +// RoutingKit.h +// RoutingKit +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for RoutingKit. +FOUNDATION_EXPORT double RoutingKitVersionNumber; + +//! Project version string for RoutingKit. +FOUNDATION_EXPORT const unsigned char RoutingKitVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/RoutingKit/RoutingKit/_Action/RoutingAction.swift b/RoutingKit/RoutingKit/_Action/RoutingAction.swift new file mode 100644 index 000000000..e832c0efe --- /dev/null +++ b/RoutingKit/RoutingKit/_Action/RoutingAction.swift @@ -0,0 +1,26 @@ +// +// RoutingAction.swift +// RoutingKit +// +// Created by Qiang Huang on 8/10/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +open class RoutingAction: NSObject, NavigableProtocol { + open var completion: RoutingCompletionBlock? + + deinit { + complete(successful: false) + } + + @objc open func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) { + completion?(nil, false) + } + + open func complete(successful: Bool) { + completion?(nil, successful) + completion = nil + } +} diff --git a/RoutingKit/RoutingKit/_Badging/BadgingProtocol.swift b/RoutingKit/RoutingKit/_Badging/BadgingProtocol.swift new file mode 100644 index 000000000..f0339528f --- /dev/null +++ b/RoutingKit/RoutingKit/_Badging/BadgingProtocol.swift @@ -0,0 +1,18 @@ +// +// BadgingProtocol.swift +// RoutingKit +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol UrlBadgingProtocol: NSObjectProtocol { + @objc func badge(url: String, value: String?) + @objc func badge(for url: String) -> String? +} + +public class UrlBadgingProvider: NSObject { + public static var shared: UrlBadgingProtocol? +} diff --git a/RoutingKit/RoutingKit/_Router/MappedRouter.swift b/RoutingKit/RoutingKit/_Router/MappedRouter.swift new file mode 100644 index 000000000..1c2189f1e --- /dev/null +++ b/RoutingKit/RoutingKit/_Router/MappedRouter.swift @@ -0,0 +1,427 @@ +// +// MappedRouter.swift +// RoutingKit +// +// Created by Qiang Huang on 10/11/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Combine +import Foundation +import Utilities + +public enum RoutingPresentation: Int { + case root + case show + case detail + case prompt + case callout + case half + case float + case embed + case drawer + /// center-screen popup which dims the background + case popup + case presentOverFullScreen +} + +private struct PathTuple { + let path: String + let params: [String]? +} + +public class RoutingMap: NSObject, ParsingProtocol { + public var destination: String + public var params: [String]? + public var dependencies: [RoutingRequest]? + public var presentation: RoutingPresentation? + + override open var parser: Parser { + return MappedRouter.parserOverwrite ?? super.parser + } + + public init(destination: String, params: [String]?) { + self.destination = destination + self.params = params + super.init() + } + + public func parse(dictionary: [String: Any]) { + dependencies = parseDependencies(dictionary: dictionary) + switch parser.asString(dictionary["presentation"]) { + case "root": + presentation = .root + + case "drawer": + presentation = .drawer + + case "show": + presentation = .show + + case "detail": + presentation = .detail + + case "prompt": + presentation = .prompt + + case "callout": + presentation = .callout + + case "half": + presentation = .half + + case "float": + presentation = .float + + case "embed": + presentation = .embed + + case "popup": + presentation = .popup + + case "present_over_full_screen": + presentation = .presentOverFullScreen + + default: + presentation = nil + } + } + + public func parseDependencies(dictionary: [String: Any]) -> [RoutingRequest]? { + if let dependencies = parser.asArray(dictionary["dependencies"]) { + var requests = [RoutingRequest]() + for dependency in dependencies { + if let data = parser.asDictionary(dependency), let path = parser.asString(data["path"]) { + let params = parser.asDictionary(data["params"]) + let request = RoutingRequest(path: path, params: params) + requests.append(request) + } else if let path = parser.asString(dependency) { + let request = RoutingRequest(path: path) + requests.append(request) + } else { + Console.shared.log("Error: parsing route dependencies") + } + } + return requests + } + return nil + } +} + +open class MappedRouter: NSObject, RouterProtocol, ParsingProtocol, CombineObserving { + public var cancellableMap = [AnyKeyPath: AnyCancellable]() + + public static var parserOverwrite: Parser? + + override open var parser: Parser { + return MappedRouter.parserOverwrite ?? super.parser + } + + public var appState: AppState? { + didSet { + didSetAppState(oldValue: oldValue) + } + } + + private var pendingRequest: RoutingRequest? + private var pendingPresentation: RoutingPresentation? + private var pendingAnimated: Bool = false + private var pendingCompletion: RoutingCompletionBlock? + + public var disabled: Bool = false + public var defaults: [String: String]? + public var aliases: [String: String]? + public var maps: [String: [String: [String: RoutingMap]]]? // ["http":["www.domain.com": ["/": "Home]]] + // public var shared: [String: RoutingMap] + public init(file: String) { + super.init() + let shared = JsonLoader.load(bundles: Bundle.particles, fileName: "routing_shared.json") as? [String: Any] + if let destinations = JsonLoader.load(bundles: Bundle.particles, fileName: file) as? [String: Any] { + parse(dictionary: destinations, shared: shared) + } + } + + public init(jsonString: String) { + super.init() + let shared = JsonLoader.load(bundles: Bundle.particles, fileName: "routing_shared.json") as? [String: Any] + if let data = jsonString.data(using: .utf8), + let destinations = JsonLoader.load(data: data) as? [String: Any] { + parse(dictionary: destinations, shared: shared) + } + } + + public func parse(dictionary: [String: Any], shared: [String: Any]?) { + if let defaultData = dictionary["defaults"] as? [String: String] { + if defaults == nil { + defaults = defaultData + } else { + defaults = defaults?.merging(defaultData, uniquingKeysWith: { (_, value2) -> String in + value2 + }) + } + } + if let aliasesData = dictionary["aliases"] as? [String: String] { + if aliases == nil { + aliases = aliasesData + } else { + aliases = aliases?.merging(aliasesData, uniquingKeysWith: { (_, value2) -> String in + value2 + }) + } + } + if let schemaMaps = parseMaps(dictionary: dictionary["mapping"] as? [String: Any], shared: shared) { + if maps == nil { + maps = schemaMaps + } else { + maps = maps?.merging(schemaMaps, uniquingKeysWith: { (_, value2) -> [String: [String: RoutingMap]] in + value2 + }) + } + } + } + + private func parseMaps(dictionary: [String: Any]?, shared: [String: Any]?) -> [String: [String: [String: RoutingMap]]]? { + if let dictionary = dictionary { + var schemeMaps = [String: [String: [String: RoutingMap]]]() + for (scheme, value) in dictionary { + if let dictionary = value as? [String: Any] { + var hostMaps = [String: [String: RoutingMap]]() + for (host, value) in dictionary { + if let dictionary = merge(host: host, destination: value as? [String: Any], with: shared) { + var maps = [String: RoutingMap]() + for (key, value) in dictionary { + if let dictionary = parser.asDictionary(value), let destination = parser.asString(dictionary["destination"]) { + let pathTuple = parsePath(path: key) + let routing = map(destination: destination, params: pathTuple.params) + routing.parse(dictionary: dictionary) + assert(!maps.keys.contains { $0 == pathTuple.path }, + "collision on paths \(maps.keys.filter {$0 == pathTuple.path}), remove the duplicate route in routing_swiftui.json") + maps[pathTuple.path] = routing + } + } + hostMaps[host] = maps + } + } + schemeMaps[scheme] = hostMaps + } + } + return schemeMaps + } + return nil + } + + private func parsePath(path: String) -> PathTuple { + var components = path.components(separatedBy: "/") + var params = [String]() + while let last = components.last, last.starts(with: ":") { + components.removeLast() + let param = last.substring(fromIndex: 1) + params.insert(param, at: 0) + } + if params.count > 0 { + let path = components.joined(separator: "/") + return PathTuple(path: path, params: params) + } else { + return PathTuple(path: path, params: nil) + } + } + + private func merge(host: String, destination: [String: Any]?, with shared: [String: Any]?) -> [String: Any]? { + if host == "*" { + return destination + } else { + if let destination = destination { + if let shared = shared { + return destination.merging(shared) { (value1, _) -> Any in + value1 + } + } else { + return destination + } + } else { + return shared + } + } + } + + open func map(destination: String, params: [String]?) -> RoutingMap { + return RoutingMap(destination: destination, params: params) + } + + open func didSetAppState(oldValue: AppState?) { + changeObservation(from: oldValue, to: appState, keyPath: #keyPath(AppState.background)) {[weak self] _, _, _, _ in + self?.sendPending() + } + } + + open func sendPending() { + if appState?.background != true, let pendingRequest = pendingRequest { + reallyNavigate(to: pendingRequest, presentation: pendingPresentation, animated: pendingAnimated, completion: pendingCompletion) + self.pendingRequest = nil + pendingPresentation = nil + pendingAnimated = false + pendingCompletion = nil + } + } + + open func navigate(to request: RoutingRequest, presentation: RoutingPresentation?, animated: Bool, completion: RoutingCompletionBlock?) { + if appState?.background != true { + reallyNavigate(to: request, presentation: presentation, animated: animated, completion: completion) + } else { + pendingRequest = request + pendingPresentation = presentation + pendingAnimated = animated + pendingCompletion = completion + } + } + + open func reallyNavigate(to request: RoutingRequest, presentation: RoutingPresentation?, animated: Bool, completion: RoutingCompletionBlock?) { + if let path = request.path { + Console.shared.log("Route to \(path)") + } + if disabled { + completion?(nil, false) + } else { + let transformed = transform(request: request) + if let map = self.map(for: transformed) { + backtrack(request: transformed, animated: animated) { [weak self] data, completed in + if completed { + completion?(nil, true) + } else { + self?.route(dependencies: map, request: transformed, completion: { [weak self] _, successful in + if successful { + self?.navigate(to: map, request: transformed, presentation: presentation ?? transformed.presentation, animated: animated, completion: { /* [weak self] */ data, successful in + completion?(data, successful) + }) + } else { + completion?(nil, false) + } + }) + } + } + } else { + if let url = request.url { + URLHandler.shared?.open(url, completionHandler: { successful in + completion?(nil, successful) + }) + } else { + completion?(nil, false) + } + } + } + } + + open func transform(request: RoutingRequest) -> RoutingRequest { + if let path = request.path { + let transformed = RoutingRequest( + originalUrl: request.originalUrl, + scheme: transform(request.scheme, default: defaults?["scheme"]), + host: transform(request.host, default: defaults?["host"]), + path: transform(path.trim()) ?? "/", + params: request.params) + + transformed.presentation = request.presentation + return transformed + } + return request + } + + open func transform(_ string: String?, default fallback: String? = nil) -> String? { + if let string = string { + return aliases?[string] ?? string + } else { + return fallback + } + } + + open func map(for request: RoutingRequest) -> RoutingMap? { + var scheme: String? = request.scheme ?? defaults?["scheme"] + if let input = scheme, let alias = aliases?[input] { + scheme = alias + } + var host: String? = request.host ?? defaults?["host"] + if let input = host, let alias = aliases?[input] { + host = alias + } + if let scheme = scheme, let host = host { + if let path = request.path { + return map(scheme: scheme, host: host, path: path, for: request) + } else { + return maps?[scheme]?[host]?["*"] ?? maps?[scheme]?["*"]?["*"] + } + } + + return nil + } + + open func map(scheme: String, host: String, path: String, for request: RoutingRequest) -> RoutingMap? { + var map: RoutingMap? + if let scheme = maps?[scheme] { + if let host = scheme[host] { + map = host[path] + if map === nil { + for (key, value) in host { + let pathMap = value + if let pathParams = pathMap.params, path.starts(with: key) { + let params = path.substring(fromIndex: key.length) + let components = params.components(separatedBy: "/").compactMap { component in + component.trim() + } + if components.count <= pathParams.count { + var requestParams = request.params ?? [String: Any]() + for i in 0 ..< pathParams.count { + let param = pathParams[i] + let paramValue = components[i] + requestParams[param] = paramValue + } + request.params = requestParams + request.path = key + map = pathMap + break + } + } + } + } + } + if map === nil { + map = scheme["*"]?["*"] + } + return map + } + return nil + } + + open func backtrack(request: RoutingRequest, animated: Bool, completion: RoutingCompletionBlock?) { + completion?(nil, false) + } + + open func route(dependencies map: RoutingMap, index: Int = 0, request: RoutingRequest, completion: RoutingCompletionBlock?) { + if index < map.dependencies?.count ?? 0, let dependency = map.dependencies?[index], let path = dependency.path { + var params = dependency.params ?? [String: Any]() + if let others = request.params { + params.merge(others) { (_, value2) -> Any in + value2 + } + } + let request = RoutingRequest(originalUrl: request.originalUrl, path: path, params: params) + navigate(to: request, presentation: nil, animated: false) { [weak self] _, successful in + if successful { + self?.route(dependencies: map, index: index + 1, request: request, completion: completion) + } else { + completion?(nil, false) + } + } + } else { + completion?(nil, true) + } + } + + open func navigate(to map: RoutingMap, request: RoutingRequest, presentation: RoutingPresentation?, animated: Bool, completion: RoutingCompletionBlock?) { + completion?(nil, false) + } + + public func navigate(to url: URL?, completion: RoutingCompletionBlock?) { + // Sample URL app://go.to/path... + navigate(to: RoutingRequest(url: url?.absoluteString), presentation: nil, animated: true, completion: completion) + } +} diff --git a/RoutingKit/RoutingKit/_Router/RouterProtocol.swift b/RoutingKit/RoutingKit/_Router/RouterProtocol.swift new file mode 100644 index 000000000..6acf8e77f --- /dev/null +++ b/RoutingKit/RoutingKit/_Router/RouterProtocol.swift @@ -0,0 +1,52 @@ +// +// RouterProtocol.swift +// RoutingKit +// +// Created by Qiang Huang on 10/11/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public typealias RoutingCompletionBlock = (Any?, Bool) -> Void + +@objc public protocol RoutingOriginatorProtocol: NSObjectProtocol { + func routingRequest() -> RoutingRequest? + + @objc optional func identifierParams() -> [String: Any]? +} + +public protocol RouterProtocol: NSObjectProtocol { + var disabled: Bool { get set } + func navigate(to request: RoutingRequest, animated: Bool, completion: RoutingCompletionBlock?) + func navigate(to request: RoutingRequest, presentation: RoutingPresentation?, animated: Bool, completion: RoutingCompletionBlock?) + func navigate(to originator: RoutingOriginatorProtocol, animated: Bool, completion: RoutingCompletionBlock?) + func navigate(to originator: RoutingOriginatorProtocol, presentation: RoutingPresentation?, animated: Bool, completion: RoutingCompletionBlock?) + func navigate(to url: URL?, completion: RoutingCompletionBlock?) +} + +public extension RouterProtocol { + func navigate(to request: RoutingRequest, animated: Bool, completion: RoutingCompletionBlock?) { + navigate(to: request, presentation: nil, animated: animated, completion: completion) + } + + func navigate(to originator: RoutingOriginatorProtocol, animated: Bool, completion: RoutingCompletionBlock?) { + navigate(to: originator, presentation: nil, animated: animated, completion: completion) + } + + func navigate(to originator: RoutingOriginatorProtocol, presentation: RoutingPresentation?, animated: Bool, completion: RoutingCompletionBlock?) { + if let routingRequest = originator.routingRequest() { + navigate(to: routingRequest, presentation: presentation, animated: animated, completion: completion) + } + } +} + +public class Router { + public static var shared: RouterProtocol? +} + +@objc public protocol NavigableProtocol: NSObjectProtocol { + @objc func navigate(to request: RoutingRequest?, animated: Bool, completion: RoutingCompletionBlock?) + + @objc optional var history: RoutingRequest? { get } +} diff --git a/RoutingKit/RoutingKit/_Router/RoutingHistory.swift b/RoutingKit/RoutingKit/_Router/RoutingHistory.swift new file mode 100644 index 000000000..db73b61e3 --- /dev/null +++ b/RoutingKit/RoutingKit/_Router/RoutingHistory.swift @@ -0,0 +1,147 @@ +// +// RoutingHistory.swift +// RoutingKit +// +// Created by Qiang Huang on 11/24/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation +import Utilities + +public class RoutingEvent: NSObject { + public var request: RoutingRequest? + public weak var destination: NavigableProtocol? +} + +public final class RoutingHistory: NSObject, SingletonProtocol { + public static var shared: RoutingHistory = { + RoutingHistory() + }() + + public var events: [RoutingEvent] = [RoutingEvent]() + public var debouncer: Debouncer = Debouncer() + + public var persistTag: String { + return "\(String(describing: type(of: self))).persist" + } + + public var persistDataFile: String? { + return FolderService.shared?.documents()?.stringByAppendingPathComponent(path: "\(persistTag).data.json") + } + + public func record(destination: NavigableProtocol) { + if !makeLast(destination: destination) { + if let history = destination.history { + if history != nil { + let event = RoutingEvent() + event.destination = destination + event.request = history + events.append(event) + save() + } + } + } + } + + public func modifyEvent(destination: NavigableProtocol, with request: RoutingRequest) { + if let event = events.first(where: { (event: RoutingEvent) -> Bool in + event.destination === destination + }) { + event.request = request + save() + } + } + + public func makeLast(destination: NavigableProtocol) -> Bool { + if let index = events.firstIndex(where: { (event: RoutingEvent) -> Bool in + event.destination === destination + }) { + if let history = destination.history { + if history != nil { + events[index].request = history + } + } + events = Array(events.prefix(through: index)) + save() + return true + } + return false + } + + public func remove(destination: NavigableProtocol) { + if let index = events.firstIndex(where: { (event: RoutingEvent) -> Bool in + event.destination === destination + }) { + events = Array(events.prefix(upTo: index)) + save() + } + } + + public func save() { + if let handler = debouncer.debounce() { + handler.run({ [weak self] in + self?.write() + }, delay: 0.5) + } + } + + public func write() { + DispatchQueue.global().async { [weak self] in + if let self = self, let persistDataFile = self.persistDataFile { + let paths = self.events.compactMap({ (event) -> String? in + if let path = event.request?.path { + if let params = event.request?.params { + let lines = params.map({ (arg0) -> String in + let (key, value) = arg0 + return "\(key)=\(value)" + }) + return "\(path)?\(lines.joined(separator: "&"))" + } else { + return path + } + } + return nil + }) + do { + let json = try JSONSerialization.data(withJSONObject: paths, options: .prettyPrinted) + try json.write(to: URL(fileURLWithPath: persistDataFile)) + } catch { + } + } + } + } + + public func history() -> [RoutingRequest]? { + if let persistDataFile = self.persistDataFile, let lines = JsonLoader.load(file: persistDataFile) as? [String] { + return lines.compactMap({ (line) -> RoutingRequest? in + let pathAndParams = line.components(separatedBy: "?") + switch pathAndParams.count { + case 1: + return RoutingRequest(path: line) + + case 2: + if let path = pathAndParams.first, let paramsString = pathAndParams.last { + let params = paramsString.components(separatedBy: "&") + var requestParams: [String: Any] = [:] + for param in params { + let keyAndValues = param.components(separatedBy: "=") + if keyAndValues.count == 2 { + if let key = keyAndValues.first, let value = keyAndValues.last { + requestParams[key] = value + } + } + } + return RoutingRequest(path: path, params: requestParams) + } else { + return nil + } + + default: + return nil + } + }) + } + return nil + } +} diff --git a/RoutingKit/RoutingKit/_Router/RoutingRequest.swift b/RoutingKit/RoutingKit/_Router/RoutingRequest.swift new file mode 100644 index 000000000..e8f1999e0 --- /dev/null +++ b/RoutingKit/RoutingKit/_Router/RoutingRequest.swift @@ -0,0 +1,65 @@ +// +// RoutingRequest.swift +// RoutingKit +// +// Created by Qiang Huang on 10/11/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +@objc public class RoutingRequest: NSObject { + public let originalUrl: String? + public var scheme: String? + public var host: String? + public var path: String? + public var params: [String: Any]? + + public var presentation: RoutingPresentation? + + public var url: URL? { + if let originalUrl = originalUrl, let url = URL(string: originalUrl) { + return url + } + var urlComponents = URLComponents() + urlComponents.scheme = scheme + urlComponents.host = host + urlComponents.path = path ?? "/" + if let params = params, params.count > 0 { + urlComponents.queryItems = params.compactMap({ (arg0) -> URLQueryItem? in + let (key, value) = arg0 + return URLQueryItem(name: key, value: parser.asString(value)) + }) + } + return urlComponents.url + } + + /// use this initializer to piecewise create a routing request + public init(originalUrl: String? = nil, scheme: String? = nil, host: String? = nil, path: String, params: [String: Any]? = nil) { + self.originalUrl = originalUrl + self.scheme = scheme + self.host = host + self.path = path + self.params = params + super.init() + } + + /// use this initializer to initialize a router request given a url + public init(url: String?) { + originalUrl = url + // Swift does not handle "#" symbol mid-path so we need to manually clean. + // e.g. https://v4.testnet.dydx.exchange/#/trade/AVAX-USD would parse the path as "/" + // We hold onto original url in case we need to recover the # + if let url = url, let urlComponents = URLComponents(string: url.replacingOccurrences(of: "/#/", with: "/")) { + scheme = urlComponents.scheme + host = urlComponents.host?.trim() + path = urlComponents.path.trim() + params = urlComponents.params + } + super.init() + } + + public func modify(path: String) -> RoutingRequest? { + return RoutingRequest(scheme: scheme, host: host, path: path, params: params) + } +} diff --git a/RoutingKit/RoutingKitAppleTV/Info.plist b/RoutingKit/RoutingKitAppleTV/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/RoutingKit/RoutingKitAppleTV/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/RoutingKit/RoutingKitAppleTV/RoutingKit.h b/RoutingKit/RoutingKitAppleTV/RoutingKit.h new file mode 100644 index 000000000..6a8a71478 --- /dev/null +++ b/RoutingKit/RoutingKitAppleTV/RoutingKit.h @@ -0,0 +1,19 @@ +// +// RoutingKitAppleTV.h +// RoutingKitAppleTV +// +// Created by Qiang Huang on 12/6/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for RoutingKitAppleTV. +FOUNDATION_EXPORT double RoutingKitAppleTVVersionNumber; + +//! Project version string for RoutingKitAppleTV. +FOUNDATION_EXPORT const unsigned char RoutingKitAppleTVVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/RoutingKit/RoutingKitAppleTVTests/Info.plist b/RoutingKit/RoutingKitAppleTVTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/RoutingKit/RoutingKitAppleTVTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/RoutingKit/RoutingKitAppleTVTests/RoutingKitAppleTVTests.swift b/RoutingKit/RoutingKitAppleTVTests/RoutingKitAppleTVTests.swift new file mode 100644 index 000000000..7a6e1dafc --- /dev/null +++ b/RoutingKit/RoutingKitAppleTVTests/RoutingKitAppleTVTests.swift @@ -0,0 +1,32 @@ +// +// RoutingKitAppleTVTests.swift +// RoutingKitAppleTVTests +// +// Created by Qiang Huang on 12/6/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +@testable import RoutingKit +import XCTest + +class RoutingKitAppleTVTests: XCTestCase { + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. + } + } +} diff --git a/RoutingKit/RoutingKitAppleWatch/Info.plist b/RoutingKit/RoutingKitAppleWatch/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/RoutingKit/RoutingKitAppleWatch/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/RoutingKit/RoutingKitAppleWatch/RoutingKit.h b/RoutingKit/RoutingKitAppleWatch/RoutingKit.h new file mode 100644 index 000000000..e52378fd7 --- /dev/null +++ b/RoutingKit/RoutingKitAppleWatch/RoutingKit.h @@ -0,0 +1,19 @@ +// +// RoutingKitAppleWatch.h +// RoutingKitAppleWatch +// +// Created by Qiang Huang on 12/4/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for RoutingKitAppleWatch. +FOUNDATION_EXPORT double RoutingKitAppleWatchVersionNumber; + +//! Project version string for RoutingKitAppleWatch. +FOUNDATION_EXPORT const unsigned char RoutingKitAppleWatchVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/RoutingKit/RoutingKitTests/Info.plist b/RoutingKit/RoutingKitTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/RoutingKit/RoutingKitTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/RoutingKit/RoutingKitTests/RoutingKitTests.swift b/RoutingKit/RoutingKitTests/RoutingKitTests.swift new file mode 100644 index 000000000..12d19d0b7 --- /dev/null +++ b/RoutingKit/RoutingKitTests/RoutingKitTests.swift @@ -0,0 +1,34 @@ +// +// RoutingKitTests.swift +// RoutingKitTests +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import XCTest +@testable import RoutingKit + +class RoutingKitTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Shared/CommonAppDelegate.swift b/Shared/CommonAppDelegate.swift new file mode 100644 index 000000000..a87706171 --- /dev/null +++ b/Shared/CommonAppDelegate.swift @@ -0,0 +1,205 @@ +// +// CommonAppDelegate.swift +// Trace +// +// Created by Qiang Huang on 9/15/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import ParticlesKit +import PlatformParticles +import PlatformRouting +import RoutingKit +import UIAppToolkits +import UIToolkits +import Utilities +import WebParticles + +import AmplitudeInjections +import AppsFlyerStaticInjections +import FirebaseStaticInjections +import dydxStateManager +import dydxViews +import dydxAnalytics +import StatsigInjections + +open class CommonAppDelegate: ParticlesAppDelegate { + open var notificationTag: String { + return "firebase" + } + + private lazy var firebaseNotification: FirebaseNotificationHandler = { + return FirebaseNotificationHandler(tag: notificationTag) + }() + + private let notificationHandlerDelegate = dydxNotificationHandlerDelegate() + + override open func inject(completion: @escaping () -> Void) { + super.inject { [weak self] in + self?.injectUX(completion: completion) + } + } + + override open func compositeTracking() -> CompositeTracking { + return dydxCompositeTracking() + } + + override open func injectFeatures(completion: @escaping () -> Void) { + Console.shared.log("injectFeatures") + // these three injections need to happen before app start + injectStatsigApiKey() + injectFirebase() + injectRating() + injectAmplitude() + let compositeFeatureFlags = CompositeFeatureFlagsProvider() + switch Installation.source { + case .debug, .testFlight: + compositeFeatureFlags.local = FeatureFlagsStore.shared + case .appStore, .jailBroken: + break + } + compositeFeatureFlags.remote = StatsigFeatureFlagsProvider.shared + FeatureService.shared = compositeFeatureFlags + FeatureService.shared?.activate { /* [weak self] in */ + Injection.shared?.injectFeatured(completion: completion) + } + } + + override open func injectAuth() { + } + + open func injectUX(completion: @escaping () -> Void) { + injectGraphingAnchor() + injectErrorInfo() + injectAttribution() + injectLocalNotifications() + injectAppearances() + injectWebview() + + completion() + } + + open func injectGraphingAnchor() { + GraphingAnchor.shared = StandardGraphingAnchor() + } + + open func useProductionFirebase() -> Bool { + switch Installation.source { + case .debug, .testFlight, .jailBroken: return false + case .appStore: return true + } + } + + open func injectFirebase() { + Console.shared.log("injectFirebase") + if useProductionFirebase() { + FirebaseRunner.optionsFile = "GoogleService-Info" + } else { + FirebaseRunner.optionsFile = "GoogleService-Info-Staging" + } + _ = FirebaseRunner.shared + if FirebaseRunner.shared.enabled { + add(tracking: FirebaseTracking()) + add(errorLogging: CrashlyticsErrorLogging()) + } + } + + open func injectAmplitude() { + Console.shared.log("injectAmplitude") + let apiKey: String? + switch Installation.source { + case .jailBroken, .debug, .testFlight: + apiKey = CredientialConfig.shared.credential(for: "amplitudeStagingApiKey") + case .appStore: + apiKey = CredientialConfig.shared.credential(for: "amplitudeApiKey") + } + if let apiKey = apiKey, apiKey.isNotEmpty { + add(tracking: dydxAmplitudeTracking(apiKey)) + } + } + + open func injectStatsigApiKey() { + Console.shared.log("injectStatsig") + let environment: StatsigFeatureFlagsProvider.Environment + switch Installation.source { + case .debug, .testFlight: + environment = .development + case .appStore, .jailBroken: + environment = .production + } + guard let apiKey = CredientialConfig.shared.credential(for: "statsigApiKey") else { + assertionFailure("Statsig API key is missing") + return + } + StatsigFeatureFlagsProvider.shared = StatsigFeatureFlagsProvider(apiKey: apiKey, userId: dydxCompositeTracking.getStableId(), environment: environment) + } + + open func injectAttribution() { + Console.shared.log("injectAttribution") + if let devKey = CredientialConfig.shared.credential(for: "appsFlyerDevKey"), devKey.isNotEmpty, + let appId = CredientialConfig.shared.credential(for: "appsFlyerAppId"), appId.isNotEmpty { + AppsFlyerRunner.shared.devKey = devKey + AppsFlyerRunner.shared.appId = appId + Attributer.shared = AppsFlyerAttributor() + Attributer.shared?.launch() + add(tracking: AppsFlyerTracking()) + } + } + + open func injectLocalNotifications() { + LocalNotificationService.shared = SimpleLocalNotification() + } + + open func injectErrorInfo() { + Console.shared.log("injectErrorInfo") + ErrorInfo.shared = dydxBannerErrorAlert() + } + + open func injectRating() { + Console.shared.log("injectRating") + dydxRatingService.shared = dydxPointsRating() + } + + open func injectAppearances() { + Console.shared.log("injectAppearances") + UINavigationBar.appearance().tintColor = ColorPalette.shared.color(system: "blue") + UINavigationBar.appearance().shadowColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.2) + UINavigationBar.appearance().shadowOffset = CGSize(width: 0.0, height: 3.0) + UINavigationBar.appearance().shadowRadius = 5.0 + UIToolbar.appearance().isTranslucent = false + UITabBar.appearance().tintColor = ColorPalette.shared.color(system: "blue") + UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).backgroundColor = ColorPalette.shared.color(system: "superlight") + } + + open func injectWebview() { + ParticlesWebView.setup(urlString: CredientialConfig.shared.credential(for: "webAppUrl")) + } + + override open func startup(completion: @escaping () -> Void) { + injectNotificationHandler() + injectURLHandler() + super.startup { [weak self] in + self?.injectNotification() + self?.firebaseNotification.delegate = self?.notificationHandlerDelegate + completion() + } + } + + open override func applicationDidBecomeActive(_ application: UIApplication) { + super.applicationDidBecomeActive(application) + Tracking.shared?.log(event: AnalyticsEventV2.AppStart()) + dydxRatingService.shared?.launchedApp() + } + + open func injectNotification() { + NotificationService.shared = firebaseNotification + } + + open func injectNotificationHandler() { + NotificationBridge.shared = firebaseNotification + } + + open func injectURLHandler() { + URLHandler.shared = UIApplication.shared + } +} diff --git a/Shared/UIApplication+URLHandler.swift b/Shared/UIApplication+URLHandler.swift new file mode 100644 index 000000000..e6fa4c398 --- /dev/null +++ b/Shared/UIApplication+URLHandler.swift @@ -0,0 +1,17 @@ +// +// UIApplication+URLHandler.swift +// dydxV4 +// +// Created by Michael Maguire on 8/17/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import UIKit +import Utilities + +extension UIApplication: URLHandlerProtocol { + public func open(_ url: URL, completionHandler completion: ((Bool) -> Void)?) { + Tracking.shared?.log(event: "NavigateExternal", data: nil) + open(url, options: [:], completionHandler: completion) + } +} diff --git a/StatsigInjections/StatsigInjections.xcodeproj/project.pbxproj b/StatsigInjections/StatsigInjections.xcodeproj/project.pbxproj new file mode 100644 index 000000000..7a0137161 --- /dev/null +++ b/StatsigInjections/StatsigInjections.xcodeproj/project.pbxproj @@ -0,0 +1,464 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 2795C8922C53FF8B00155009 /* StatsigInjections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2795C8912C53FF8B00155009 /* StatsigInjections.swift */; }; + 41594E9E5B027E2DD01CE0F6 /* Pods_iOS_StatsigInjections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4EB4100B42990F76B4963A47 /* Pods_iOS_StatsigInjections.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 27E4ECA02C540AE900E4F87C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 27E4EC982C540AE900E4F87C /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 27E4ECA22C540AE900E4F87C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 27E4EC982C540AE900E4F87C /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 27E4ECA42C540AE900E4F87C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 27E4EC982C540AE900E4F87C /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 27E4ECA62C540AE900E4F87C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 27E4EC982C540AE900E4F87C /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 27E4ECA82C540AE900E4F87C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 27E4EC982C540AE900E4F87C /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 27E4ECAA2C540AF500E4F87C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 27E4EC982C540AE900E4F87C /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 2795C88C2C53FF8B00155009 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2795C88E2C53FF8B00155009 /* libStatsigInjections.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libStatsigInjections.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 2795C8912C53FF8B00155009 /* StatsigInjections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsigInjections.swift; sourceTree = ""; }; + 27E4EC982C540AE900E4F87C /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 4EB4100B42990F76B4963A47 /* Pods_iOS_StatsigInjections.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_StatsigInjections.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 77234A743052758A3A3902A8 /* Pods-iOS-StatsigInjections.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-StatsigInjections.debug.xcconfig"; path = "Target Support Files/Pods-iOS-StatsigInjections/Pods-iOS-StatsigInjections.debug.xcconfig"; sourceTree = ""; }; + D0DC92405F45F1BEA1A1791D /* Pods-iOS-StatsigInjections.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-StatsigInjections.release.xcconfig"; path = "Target Support Files/Pods-iOS-StatsigInjections/Pods-iOS-StatsigInjections.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2795C88B2C53FF8B00155009 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 41594E9E5B027E2DD01CE0F6 /* Pods_iOS_StatsigInjections.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2795C8852C53FF8B00155009 = { + isa = PBXGroup; + children = ( + 27E4EC982C540AE900E4F87C /* Utilities.xcodeproj */, + 2795C8902C53FF8B00155009 /* StatsigInjections */, + 2795C88F2C53FF8B00155009 /* Products */, + 5E0B9EAD298663F7C5AD6891 /* Pods */, + EF76442BC01983DDC42F71C3 /* Frameworks */, + ); + sourceTree = ""; + }; + 2795C88F2C53FF8B00155009 /* Products */ = { + isa = PBXGroup; + children = ( + 2795C88E2C53FF8B00155009 /* libStatsigInjections.a */, + ); + name = Products; + sourceTree = ""; + }; + 2795C8902C53FF8B00155009 /* StatsigInjections */ = { + isa = PBXGroup; + children = ( + 2795C8912C53FF8B00155009 /* StatsigInjections.swift */, + ); + path = StatsigInjections; + sourceTree = ""; + }; + 27E4EC992C540AE900E4F87C /* Products */ = { + isa = PBXGroup; + children = ( + 27E4ECA12C540AE900E4F87C /* Utilities.framework */, + 27E4ECA32C540AE900E4F87C /* Utilities.framework */, + 27E4ECA52C540AE900E4F87C /* Utilities.framework */, + 27E4ECA72C540AE900E4F87C /* UtilitiesTests.xctest */, + 27E4ECA92C540AE900E4F87C /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 5E0B9EAD298663F7C5AD6891 /* Pods */ = { + isa = PBXGroup; + children = ( + 77234A743052758A3A3902A8 /* Pods-iOS-StatsigInjections.debug.xcconfig */, + D0DC92405F45F1BEA1A1791D /* Pods-iOS-StatsigInjections.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; + EF76442BC01983DDC42F71C3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4EB4100B42990F76B4963A47 /* Pods_iOS_StatsigInjections.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2795C88D2C53FF8B00155009 /* StatsigInjections */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2795C8952C53FF8B00155009 /* Build configuration list for PBXNativeTarget "StatsigInjections" */; + buildPhases = ( + B89343A163A6D5685D0D5500 /* [CP] Check Pods Manifest.lock */, + 2795C88A2C53FF8B00155009 /* Sources */, + 2795C88B2C53FF8B00155009 /* Frameworks */, + 2795C88C2C53FF8B00155009 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 27E4ECAB2C540AF500E4F87C /* PBXTargetDependency */, + ); + name = StatsigInjections; + productName = StatsigInjections; + productReference = 2795C88E2C53FF8B00155009 /* libStatsigInjections.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 2795C8862C53FF8B00155009 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1540; + TargetAttributes = { + 2795C88D2C53FF8B00155009 = { + CreatedOnToolsVersion = 15.4; + }; + }; + }; + buildConfigurationList = 2795C8892C53FF8B00155009 /* Build configuration list for PBXProject "StatsigInjections" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 2795C8852C53FF8B00155009; + productRefGroup = 2795C88F2C53FF8B00155009 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 27E4EC992C540AE900E4F87C /* Products */; + ProjectRef = 27E4EC982C540AE900E4F87C /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 2795C88D2C53FF8B00155009 /* StatsigInjections */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 27E4ECA12C540AE900E4F87C /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 27E4ECA02C540AE900E4F87C /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 27E4ECA32C540AE900E4F87C /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 27E4ECA22C540AE900E4F87C /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 27E4ECA52C540AE900E4F87C /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 27E4ECA42C540AE900E4F87C /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 27E4ECA72C540AE900E4F87C /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 27E4ECA62C540AE900E4F87C /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 27E4ECA92C540AE900E4F87C /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 27E4ECA82C540AE900E4F87C /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXShellScriptBuildPhase section */ + B89343A163A6D5685D0D5500 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-StatsigInjections-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2795C88A2C53FF8B00155009 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2795C8922C53FF8B00155009 /* StatsigInjections.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 27E4ECAB2C540AF500E4F87C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 27E4ECAA2C540AF500E4F87C /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 2795C8932C53FF8B00155009 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 2795C8942C53FF8B00155009 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 2795C8962C53FF8B00155009 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 77234A743052758A3A3902A8 /* Pods-iOS-StatsigInjections.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 2795C8972C53FF8B00155009 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0DC92405F45F1BEA1A1791D /* Pods-iOS-StatsigInjections.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2795C8892C53FF8B00155009 /* Build configuration list for PBXProject "StatsigInjections" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2795C8932C53FF8B00155009 /* Debug */, + 2795C8942C53FF8B00155009 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2795C8952C53FF8B00155009 /* Build configuration list for PBXNativeTarget "StatsigInjections" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2795C8962C53FF8B00155009 /* Debug */, + 2795C8972C53FF8B00155009 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 2795C8862C53FF8B00155009 /* Project object */; +} diff --git a/StatsigInjections/StatsigInjections.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StatsigInjections/StatsigInjections.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/StatsigInjections/StatsigInjections.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/StatsigInjections/StatsigInjections.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/StatsigInjections/StatsigInjections.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/StatsigInjections/StatsigInjections.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/StatsigInjections/StatsigInjections/StatsigInjections.swift b/StatsigInjections/StatsigInjections/StatsigInjections.swift new file mode 100644 index 000000000..2c9c1b66e --- /dev/null +++ b/StatsigInjections/StatsigInjections/StatsigInjections.swift @@ -0,0 +1,135 @@ +// +// StatsigInjections.swift +// StatsigInjections +// +// Created by Michael Maguire on 7/26/24. +// + +import Statsig +import Utilities +import Combine + +public final class StatsigFeatureFlagsProvider: NSObject, FeatureFlagsProtocol { + public enum InitializationState { + case uninitialized + case initializedRemoteLoading + case initializedRemoteLoaded + } + + private let apiKey: String + private let userId: String + private let environment: StatsigEnvironment + // ensures feature flag values stay constant throughout the app session after they are used the first time, even if they are initialized to null + private var sessionValues = [String: Availabilty]() + + private enum Availabilty { + case available(Bool) + // unavailable is the case for a new feature flag, or a first launch of the app with Statsig + case unavailable + } + + public enum Environment { + case production + case development + + var statsigEnvironemnt: StatsigEnvironment { + switch self { + case .production: + return StatsigEnvironment(tier: .Production) + case .development: + return StatsigEnvironment(tier: .Development) + } + } + } + + public init(apiKey: String, userId: String, environment: Environment) { + self.apiKey = apiKey + self.userId = userId + self.environment = environment.statsigEnvironemnt + } + + static public var shared: StatsigFeatureFlagsProvider? + + public var featureFlags: [String: Any]? + + public func refresh(completion: @escaping () -> Void) { + activate(completion: completion) + } + + public func activate(completion: @escaping () -> Void) { + if Statsig.isInitialized() { + initializationState = .initializedRemoteLoaded + completion() + } else { + Statsig.start(sdkKey: apiKey, user: StatsigUser(userID: userId), options: StatsigOptions( + initTimeout: nil, + disableCurrentVCLogging: true, + environment: environment, + enableAutoValueUpdate: true, + autoValueUpdateIntervalSec: nil, + overrideStableID: nil, + enableCacheByFile: nil, + initializeValues: nil, + disableDiagnostics: nil, + disableHashing: nil, + shutdownOnBackground: nil, + api: nil, + eventLoggingApi: nil, + evaluationCallback: nil, + userValidationCallback: nil, + customCacheKey: nil, + urlSession: nil)) {[weak self] error in + Console.shared.log("Statsig feature flags initialized") + if let error { + Console.shared.log("Statsig feature flags failed to initialize: \(error)") + return + } + self?.initializationState = .initializedRemoteLoaded + // intentionally not calling completion here since we do not want ff init to be blocking startup + // this may change if we need FF pre-launch +// completion() + } + initializationState = .initializedRemoteLoading + } + completion() + } + + @Published private(set) public var initializationState = InitializationState.uninitialized + + // https://docs.statsig.com/sdk/debugging + public func isOn(feature: String) -> Bool? { + let featureGate = Statsig.getFeatureGate(feature) + let availability: Availabilty + if let existingAvailability = sessionValues[feature] { + // a featuregate value has already been set for this session + availability = existingAvailability + } else if featureGate.evaluationDetails.reason == .Recognized { + // a featuregate will be recognized if the feature gate has been fetched in previous Statsig inits (cache is non-empty) + availability = .available(featureGate.value) + } else { + // a featuregate will be unrecognized if the feature gate is new or this is first time initializing Statsig (cache would be empty) + availability = .unavailable + } + sessionValues[feature] = availability + Console.shared.log("analytics log | Statsig feature flag \(feature) is \(availability) for the session") + + switch availability { + case .available(let bool): + return bool + case .unavailable: + // defer to calling context for default value + return nil + } + } + + public func value(feature: String) -> String? { + // not yet implemented for Statsig feature flags since it is not yet needed + return nil + } + + public func customized() -> Bool { + return false + } +} + + diff --git a/UIToolkits/UIAppToolKits/Info.plist b/UIToolkits/UIAppToolKits/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/UIToolkits/UIAppToolKits/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/UIToolkits/UIAppToolKits/UIAppToolkits.h b/UIToolkits/UIAppToolKits/UIAppToolkits.h new file mode 100644 index 000000000..4c7393bcb --- /dev/null +++ b/UIToolkits/UIAppToolKits/UIAppToolkits.h @@ -0,0 +1,19 @@ +// +// UIAppToolkits.h +// UIAppToolkits +// +// Created by Qiang Huang on 12/30/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for UIAppToolkits. +FOUNDATION_EXPORT double UIAppToolkitsVersionNumber; + +//! Project version string for UIAppToolkits. +FOUNDATION_EXPORT const unsigned char UIAppToolkitsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/UIToolkits/UIAppToolKitsTests/Info.plist b/UIToolkits/UIAppToolKitsTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/UIToolkits/UIAppToolKitsTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/UIToolkits/UIAppToolKitsTests/UIAppToolkitsTests.swift b/UIToolkits/UIAppToolKitsTests/UIAppToolkitsTests.swift new file mode 100644 index 000000000..1882fd3ea --- /dev/null +++ b/UIToolkits/UIAppToolKitsTests/UIAppToolkitsTests.swift @@ -0,0 +1,34 @@ +// +// UIAppToolkitsTests.swift +// UIAppToolkitsTests +// +// Created by Qiang Huang on 12/30/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import XCTest +@testable import UIAppToolkits + +class UIAppToolkitsTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/UIToolkits/UIToolkits.xcodeproj/project.pbxproj b/UIToolkits/UIToolkits.xcodeproj/project.pbxproj new file mode 100644 index 000000000..ba3ea7a9f --- /dev/null +++ b/UIToolkits/UIToolkits.xcodeproj/project.pbxproj @@ -0,0 +1,2233 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 0295391229FB209F009026E3 /* UIImage+Gradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0295391129FB209F009026E3 /* UIImage+Gradient.swift */; }; + 310A153D27627E100059812C /* UIGestureRecognizer+Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310A153C27627E100059812C /* UIGestureRecognizer+Closure.swift */; }; + 310A154127627FA00059812C /* UIView+Tap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310A154027627FA00059812C /* UIView+Tap.swift */; }; + 310C3A1421D9623F0081E56D /* UIAppToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310C3A0B21D9623E0081E56D /* UIAppToolkits.framework */; }; + 310C3A1921D9623F0081E56D /* UIAppToolkitsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310C3A1821D9623F0081E56D /* UIAppToolkitsTests.swift */; }; + 310C3A1B21D9623F0081E56D /* UIAppToolkits.h in Headers */ = {isa = PBXBuildFile; fileRef = 310C3A0D21D9623F0081E56D /* UIAppToolkits.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 310C3B1121D9C8FD0081E56D /* UIToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31E65ADD216BC9E1008ABEE9 /* UIToolkits.framework */; }; + 310C3B1221D9C90D0081E56D /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 311D6F3C2176EF2A00655040 /* Utilities.framework */; }; + 310CC22D26A7388D00046434 /* ButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310CC22C26A7388D00046434 /* ButtonView.swift */; }; + 310E61F6216C69740043BB33 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310E61F5216C69740043BB33 /* UIKit.framework */; }; + 311BB0FA2550CA2A004331D8 /* UISearchPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311BB0F92550CA2A004331D8 /* UISearchPresenter.swift */; }; + 311D6FA72176EFEE00655040 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 311D6F3C2176EF2A00655040 /* Utilities.framework */; }; + 312FF056272874D200D35CEE /* SVGCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312FF055272874D200D35CEE /* SVGCache.swift */; }; + 31308B4424FAEC34003B5B9A /* ColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B4324FAEC34003B5B9A /* ColorPalette.swift */; }; + 31308B4524FAEC34003B5B9A /* ColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B4324FAEC34003B5B9A /* ColorPalette.swift */; }; + 31308B4624FAEC34003B5B9A /* ColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B4324FAEC34003B5B9A /* ColorPalette.swift */; }; + 31308B4824FAEC58003B5B9A /* String+Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B4724FAEC58003B5B9A /* String+Styles.swift */; }; + 31308B4924FAEC58003B5B9A /* String+Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B4724FAEC58003B5B9A /* String+Styles.swift */; }; + 31308B4A24FAEC58003B5B9A /* String+Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B4724FAEC58003B5B9A /* String+Styles.swift */; }; + 31308B4C24FAECAF003B5B9A /* UIView+Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B4B24FAECAF003B5B9A /* UIView+Color.swift */; }; + 31308B4D24FAECAF003B5B9A /* UIView+Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B4B24FAECAF003B5B9A /* UIView+Color.swift */; }; + 31308B4F24FAECCE003B5B9A /* UIViewController+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B4E24FAECCE003B5B9A /* UIViewController+Search.swift */; }; + 31308B5024FAECCE003B5B9A /* UIViewController+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B4E24FAECCE003B5B9A /* UIViewController+Search.swift */; }; + 31308B5224FAECED003B5B9A /* OutlineLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B5124FAECED003B5B9A /* OutlineLabel.swift */; }; + 31308B5324FAECED003B5B9A /* OutlineLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31308B5124FAECED003B5B9A /* OutlineLabel.swift */; }; + 3131853C24CE6B75005AC9CE /* OverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3131853B24CE6B75005AC9CE /* OverlayView.swift */; }; + 313EB86121BB730700BEF926 /* UIToolkits.h in Headers */ = {isa = PBXBuildFile; fileRef = 313EB85F21BB730700BEF926 /* UIToolkits.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 313EB86721BB732000BEF926 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB77721BB4DC000BEF926 /* Utilities.framework */; }; + 313EBB0D21BB79AD00BEF926 /* UIToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EBB0421BB79AD00BEF926 /* UIToolkits.framework */; }; + 313EBB1221BB79AD00BEF926 /* UIToolkitsAppleTVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EBB1121BB79AD00BEF926 /* UIToolkitsAppleTVTests.swift */; }; + 313EBB1421BB79AD00BEF926 /* UIToolkits.h in Headers */ = {isa = PBXBuildFile; fileRef = 313EBB0621BB79AD00BEF926 /* UIToolkits.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 313EBB3D21BB79ED00BEF926 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313EB77921BB4DC000BEF926 /* Utilities.framework */; }; + 313EBF3421BC4E4300BEF926 /* WKInterfaceImage+ImageUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313EBF3321BC4E4300BEF926 /* WKInterfaceImage+ImageUrl.swift */; }; + 31432B9C2760136F00213970 /* AlwaysVisibleScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31432B9B2760136F00213970 /* AlwaysVisibleScrollView.swift */; }; + 3145CDF725F40ED600BCCFCA /* NSLayoutConstraint+Changes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3145CDF625F40ED600BCCFCA /* NSLayoutConstraint+Changes.swift */; }; + 3145CDF825F40ED600BCCFCA /* NSLayoutConstraint+Changes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3145CDF625F40ED600BCCFCA /* NSLayoutConstraint+Changes.swift */; }; + 31471F7424BA300D00057221 /* OCRScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471F7324BA300D00057221 /* OCRScanner.swift */; }; + 31471F7624BA300D00057221 /* OCRScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471F7324BA300D00057221 /* OCRScanner.swift */; }; + 31471F7D24BA304E00057221 /* CompositeScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314D9C0E2485A8EE00582A1A /* CompositeScanner.swift */; }; + 31471F7E24BA305300057221 /* ScannerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314D9C0A2485A8C100582A1A /* ScannerProtocol.swift */; }; + 31471F7F24BA305600057221 /* Scanners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314D9C0C2485A8D300582A1A /* Scanners.swift */; }; + 31471F8124BA310100057221 /* CirclePreviewOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471F8024BA310100057221 /* CirclePreviewOverlay.swift */; }; + 31471F8324BA311E00057221 /* OverlayProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471F8224BA311E00057221 /* OverlayProtocol.swift */; }; + 31471F8524BA313A00057221 /* PreviewOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471F8424BA313A00057221 /* PreviewOverlay.swift */; }; + 31471F8724BA315200057221 /* RectPreviewOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471F8624BA315200057221 /* RectPreviewOverlay.swift */; }; + 314B630C23DCCEE500139EB3 /* OCRService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62B123DCCEE400139EB3 /* OCRService.swift */; }; + 314B630D23DCCEE500139EB3 /* OCRService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62B123DCCEE400139EB3 /* OCRService.swift */; }; + 314B631023DCCEE500139EB3 /* UIProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62B523DCCEE400139EB3 /* UIProtocols.swift */; }; + 314B631123DCCEE500139EB3 /* UIProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62B523DCCEE400139EB3 /* UIProtocols.swift */; }; + 314B631223DCCEE500139EB3 /* UIBarButtonItem+Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62B723DCCEE400139EB3 /* UIBarButtonItem+Target.swift */; }; + 314B631323DCCEE500139EB3 /* UIBarButtonItem+Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62B723DCCEE400139EB3 /* UIBarButtonItem+Target.swift */; }; + 314B631423DCCEE500139EB3 /* Debouncer+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62B823DCCEE400139EB3 /* Debouncer+View.swift */; }; + 314B631523DCCEE500139EB3 /* Debouncer+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62B823DCCEE400139EB3 /* Debouncer+View.swift */; }; + 314B631623DCCEE500139EB3 /* UIImageView+Tint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62B923DCCEE400139EB3 /* UIImageView+Tint.swift */; }; + 314B631723DCCEE500139EB3 /* UIImageView+Tint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62B923DCCEE400139EB3 /* UIImageView+Tint.swift */; }; + 314B631823DCCEE500139EB3 /* UIButton+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62BA23DCCEE400139EB3 /* UIButton+Layout.swift */; }; + 314B631923DCCEE500139EB3 /* UIButton+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62BA23DCCEE400139EB3 /* UIButton+Layout.swift */; }; + 314B631C23DCCEE500139EB3 /* UIFont+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62BC23DCCEE400139EB3 /* UIFont+Style.swift */; }; + 314B631D23DCCEE500139EB3 /* UIFont+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62BC23DCCEE400139EB3 /* UIFont+Style.swift */; }; + 314B631E23DCCEE500139EB3 /* UIView+Hierarchy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62BD23DCCEE400139EB3 /* UIView+Hierarchy.swift */; }; + 314B631F23DCCEE500139EB3 /* UIView+Hierarchy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62BD23DCCEE400139EB3 /* UIView+Hierarchy.swift */; }; + 314B632023DCCEE500139EB3 /* UIImage+Tint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62BE23DCCEE400139EB3 /* UIImage+Tint.swift */; }; + 314B632123DCCEE500139EB3 /* UIImage+Tint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62BE23DCCEE400139EB3 /* UIImage+Tint.swift */; }; + 314B632223DCCEE500139EB3 /* UIDevice+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62BF23DCCEE400139EB3 /* UIDevice+Utils.swift */; }; + 314B632323DCCEE500139EB3 /* UIDevice+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62BF23DCCEE400139EB3 /* UIDevice+Utils.swift */; }; + 314B632623DCCEE500139EB3 /* UIViewController+Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C123DCCEE400139EB3 /* UIViewController+Navigation.swift */; }; + 314B632723DCCEE500139EB3 /* UIViewController+Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C123DCCEE400139EB3 /* UIViewController+Navigation.swift */; }; + 314B632823DCCEE500139EB3 /* UIKit+Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C223DCCEE400139EB3 /* UIKit+Protocols.swift */; }; + 314B632923DCCEE500139EB3 /* UIKit+Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C223DCCEE400139EB3 /* UIKit+Protocols.swift */; }; + 314B632A23DCCEE500139EB3 /* UIViewController+Embed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C323DCCEE400139EB3 /* UIViewController+Embed.swift */; }; + 314B632C23DCCEE500139EB3 /* HMSegmentedControl+SegmentedProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C423DCCEE400139EB3 /* HMSegmentedControl+SegmentedProtocol.swift */; }; + 314B632E23DCCEE500139EB3 /* UIView+Border.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C523DCCEE400139EB3 /* UIView+Border.swift */; }; + 314B632F23DCCEE500139EB3 /* UIView+Border.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C523DCCEE400139EB3 /* UIView+Border.swift */; }; + 314B633023DCCEE500139EB3 /* UIAlertController+Text.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C623DCCEE400139EB3 /* UIAlertController+Text.swift */; }; + 314B633123DCCEE500139EB3 /* UIAlertController+Text.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C623DCCEE400139EB3 /* UIAlertController+Text.swift */; }; + 314B633423DCCEE500139EB3 /* UIView+Transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C823DCCEE400139EB3 /* UIView+Transitions.swift */; }; + 314B633523DCCEE500139EB3 /* UIView+Transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C823DCCEE400139EB3 /* UIView+Transitions.swift */; }; + 314B633623DCCEE500139EB3 /* UINavigationBar+Transparent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C923DCCEE400139EB3 /* UINavigationBar+Transparent.swift */; }; + 314B633723DCCEE500139EB3 /* UINavigationBar+Transparent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62C923DCCEE400139EB3 /* UINavigationBar+Transparent.swift */; }; + 314B633A23DCCEE500139EB3 /* UINavigationController+Load.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62CB23DCCEE400139EB3 /* UINavigationController+Load.swift */; }; + 314B633B23DCCEE500139EB3 /* UINavigationController+Load.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62CB23DCCEE400139EB3 /* UINavigationController+Load.swift */; }; + 314B633C23DCCEE500139EB3 /* ReachabilityMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62CD23DCCEE400139EB3 /* ReachabilityMessage.swift */; }; + 314B634223DCCEE500139EB3 /* UIViewControllerProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62D223DCCEE500139EB3 /* UIViewControllerProtocols.swift */; }; + 314B634323DCCEE500139EB3 /* UIViewControllerProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62D223DCCEE500139EB3 /* UIViewControllerProtocols.swift */; }; + 314B634623DCCEE500139EB3 /* LoadingIndicatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62D423DCCEE500139EB3 /* LoadingIndicatorProtocol.swift */; }; + 314B634723DCCEE500139EB3 /* LoadingIndicatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62D423DCCEE500139EB3 /* LoadingIndicatorProtocol.swift */; }; + 314B634E23DCCEE500139EB3 /* UISearchBar+Icons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62DA23DCCEE500139EB3 /* UISearchBar+Icons.swift */; }; + 314B634F23DCCEE500139EB3 /* UISearchBar+Icons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62DA23DCCEE500139EB3 /* UISearchBar+Icons.swift */; }; + 314B635023DCCEE500139EB3 /* UXTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62DC23DCCEE500139EB3 /* UXTableView.swift */; }; + 314B635123DCCEE500139EB3 /* UXTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62DC23DCCEE500139EB3 /* UXTableView.swift */; }; + 314B635223DCCEE500139EB3 /* TextCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62DD23DCCEE500139EB3 /* TextCollectionViewCell.swift */; }; + 314B635323DCCEE500139EB3 /* TextCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62DD23DCCEE500139EB3 /* TextCollectionViewCell.swift */; }; + 314B635423DCCEE500139EB3 /* SelfSizingCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62DE23DCCEE500139EB3 /* SelfSizingCollectionView.swift */; }; + 314B635523DCCEE500139EB3 /* SelfSizingCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62DE23DCCEE500139EB3 /* SelfSizingCollectionView.swift */; }; + 314B635623DCCEE500139EB3 /* DrawingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62DF23DCCEE500139EB3 /* DrawingView.swift */; }; + 314B635723DCCEE500139EB3 /* DrawingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62DF23DCCEE500139EB3 /* DrawingView.swift */; }; + 314B635823DCCEE500139EB3 /* CachedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E023DCCEE500139EB3 /* CachedImageView.swift */; }; + 314B635923DCCEE500139EB3 /* CachedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E023DCCEE500139EB3 /* CachedImageView.swift */; }; + 314B635E23DCCEE500139EB3 /* UXLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E323DCCEE500139EB3 /* UXLabel.swift */; }; + 314B635F23DCCEE500139EB3 /* UXLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E323DCCEE500139EB3 /* UXLabel.swift */; }; + 314B636023DCCEE500139EB3 /* ImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E423DCCEE500139EB3 /* ImageCollectionViewCell.swift */; }; + 314B636123DCCEE500139EB3 /* ImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E423DCCEE500139EB3 /* ImageCollectionViewCell.swift */; }; + 314B636223DCCEE500139EB3 /* CollectionViewGridLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E523DCCEE500139EB3 /* CollectionViewGridLayout.swift */; }; + 314B636323DCCEE500139EB3 /* CollectionViewGridLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E523DCCEE500139EB3 /* CollectionViewGridLayout.swift */; }; + 314B636423DCCEE500139EB3 /* SelfSizingTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E623DCCEE500139EB3 /* SelfSizingTableView.swift */; }; + 314B636523DCCEE500139EB3 /* SelfSizingTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E623DCCEE500139EB3 /* SelfSizingTableView.swift */; }; + 314B636623DCCEE500139EB3 /* UXSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E723DCCEE500139EB3 /* UXSearchBar.swift */; }; + 314B636723DCCEE500139EB3 /* UXSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E723DCCEE500139EB3 /* UXSearchBar.swift */; }; + 314B636823DCCEE500139EB3 /* UserInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E823DCCEE500139EB3 /* UserInteraction.swift */; }; + 314B636923DCCEE500139EB3 /* UserInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E823DCCEE500139EB3 /* UserInteraction.swift */; }; + 314B636A23DCCEE500139EB3 /* LabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E923DCCEE500139EB3 /* LabelView.swift */; }; + 314B636B23DCCEE500139EB3 /* LabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62E923DCCEE500139EB3 /* LabelView.swift */; }; + 314B636C23DCCEE500139EB3 /* UIKit+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62EB23DCCEE500139EB3 /* UIKit+Localization.swift */; }; + 314B636D23DCCEE500139EB3 /* UIKit+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62EB23DCCEE500139EB3 /* UIKit+Localization.swift */; }; + 314B636E23DCCEE500139EB3 /* MKCoordinateRegion+Corners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62ED23DCCEE500139EB3 /* MKCoordinateRegion+Corners.swift */; }; + 314B636F23DCCEE500139EB3 /* MKCoordinateRegion+Corners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62ED23DCCEE500139EB3 /* MKCoordinateRegion+Corners.swift */; }; + 314B637023DCCEE500139EB3 /* ViewControllerStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62EF23DCCEE500139EB3 /* ViewControllerStack.swift */; }; + 314B637123DCCEE500139EB3 /* ViewControllerStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62EF23DCCEE500139EB3 /* ViewControllerStack.swift */; }; + 314B637223DCCEE500139EB3 /* KeyboardAdjustingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62F023DCCEE500139EB3 /* KeyboardAdjustingViewController.swift */; }; + 314B637423DCCEE500139EB3 /* UIViewController+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62F123DCCEE500139EB3 /* UIViewController+Helper.swift */; }; + 314B637523DCCEE500139EB3 /* UIViewController+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62F123DCCEE500139EB3 /* UIViewController+Helper.swift */; }; + 314B637623DCCEE500139EB3 /* LoadingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62F223DCCEE500139EB3 /* LoadingNavigationController.swift */; }; + 314B637723DCCEE500139EB3 /* LoadingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62F223DCCEE500139EB3 /* LoadingNavigationController.swift */; }; + 314B637823DCCEE500139EB3 /* UXNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62F323DCCEE500139EB3 /* UXNavigationController.swift */; }; + 314B637923DCCEE500139EB3 /* UXNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62F323DCCEE500139EB3 /* UXNavigationController.swift */; }; + 314B637E23DCCEE500139EB3 /* LoadingProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62F723DCCEE500139EB3 /* LoadingProgressView.swift */; }; + 314B638123DCCEE500139EB3 /* UIKitAppViewControllerStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62F823DCCEE500139EB3 /* UIKitAppViewControllerStack.swift */; }; + 314B673A23DCE28B00139EB3 /* UIKitAppViewControllerStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62F823DCCEE500139EB3 /* UIKitAppViewControllerStack.swift */; }; + 314B673E23DCE2E200139EB3 /* KeyboardAdjustingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62D323DCCEE500139EB3 /* KeyboardAdjustingProtocol.swift */; }; + 314B674023DCE35800139EB3 /* UIViewController+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B673F23DCE35800139EB3 /* UIViewController+App.swift */; }; + 314C359924C7C0E200695F7E /* UIImage+Loading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314C359824C7C0E200695F7E /* UIImage+Loading.swift */; }; + 314C359A24C7C0E200695F7E /* UIImage+Loading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314C359824C7C0E200695F7E /* UIImage+Loading.swift */; }; + 314D9C022485A83F00582A1A /* TextEntryPrompter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314D9C012485A83F00582A1A /* TextEntryPrompter.swift */; }; + 314D9C042485A85800582A1A /* UIKitPrompterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314D9C032485A85800582A1A /* UIKitPrompterFactory.swift */; }; + 314D9C082485A88000582A1A /* AlertPrompter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314D9C072485A88000582A1A /* AlertPrompter.swift */; }; + 314D9C0B2485A8C100582A1A /* ScannerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314D9C0A2485A8C100582A1A /* ScannerProtocol.swift */; }; + 314D9C0D2485A8D300582A1A /* Scanners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314D9C0C2485A8D300582A1A /* Scanners.swift */; }; + 314D9C0F2485A8EE00582A1A /* CompositeScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314D9C0E2485A8EE00582A1A /* CompositeScanner.swift */; }; + 314D9C122485A91D00582A1A /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314D9C112485A91D00582A1A /* QRCode.swift */; }; + 3151A3AD26B1F41E00D34363 /* UXSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3151A3AC26B1F41E00D34363 /* UXSegmentedControl.swift */; }; + 31630676275A9DFC00E420C6 /* UIViewController+Half.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62D023DCCEE400139EB3 /* UIViewController+Half.swift */; }; + 316BAE0F26FA8A56002CDC03 /* UITextField+Done.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316BAE0E26FA8A56002CDC03 /* UITextField+Done.swift */; }; + 31762168245B307200C9F2FB /* FloatingLayoutProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31762167245B307200C9F2FB /* FloatingLayoutProviderProtocol.swift */; }; + 317F16EE2572D24600D178B8 /* TextTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16ED2572D24600D178B8 /* TextTableViewCell.swift */; }; + 317F16EF2572D24600D178B8 /* TextTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16ED2572D24600D178B8 /* TextTableViewCell.swift */; }; + 317F16FA2572D29400D178B8 /* ImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16F92572D29400D178B8 /* ImageTableViewCell.swift */; }; + 317F16FB2572D29400D178B8 /* ImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16F92572D29400D178B8 /* ImageTableViewCell.swift */; }; + 317F170B2572D2C800D178B8 /* ImageAddCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F170A2572D2C800D178B8 /* ImageAddCollectionViewCell.swift */; }; + 317F170C2572D2C800D178B8 /* ImageAddCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F170A2572D2C800D178B8 /* ImageAddCollectionViewCell.swift */; }; + 317F1BC22572E18900D178B8 /* NoteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F1BC12572E18900D178B8 /* NoteViewController.swift */; }; + 317F1BC32572E18900D178B8 /* NoteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F1BC12572E18900D178B8 /* NoteViewController.swift */; }; + 318A084524DF1A8400981B5F /* UIActivityIndicatorView+Waiting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318A084424DF1A8400981B5F /* UIActivityIndicatorView+Waiting.swift */; }; + 318A084924DF1AA100981B5F /* NVActivityIndicatorView+Waiting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318A084824DF1AA100981B5F /* NVActivityIndicatorView+Waiting.swift */; }; + 318A5C00272DC4A6000DA46C /* TabSegmentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318A5BFF272DC4A6000DA46C /* TabSegmentCollectionViewCell.swift */; }; + 318A5C05272DC9FA000DA46C /* MotionHapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318A5C04272DC9FA000DA46C /* MotionHapticFeedback.swift */; }; + 3192C86826FA72DD0072BD99 /* CircularProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3192C86726FA72DD0072BD99 /* CircularProgressView.swift */; }; + 3197D91B26F79CE400693D53 /* UITabbar+Transparent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3197D91A26F79CE400693D53 /* UITabbar+Transparent.swift */; }; + 3197DA1526F8FDFD00693D53 /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3197DA1426F8FDFD00693D53 /* CircularProgressBar.swift */; }; + 319BB74924292A6A00DC9E97 /* FloatingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62CF23DCCEE400139EB3 /* FloatingManager.swift */; }; + 319BB74A24292AE200DC9E97 /* UIResponder+First.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B62CA23DCCEE400139EB3 /* UIResponder+First.swift */; }; + 31A0373B26F40F93007EFC2F /* UXImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A0373A26F40F93007EFC2F /* UXImageView.swift */; }; + 31A2597926E67ADB00FB7CFD /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A2597826E67ADB00FB7CFD /* GradientView.swift */; }; + 31A7B178274040350089C713 /* OnOffSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A7B177274040350089C713 /* OnOffSwitch.swift */; }; + 31A7B1D227404C830089C713 /* TappableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A7B1D127404C830089C713 /* TappableLabel.swift */; }; + 31B5F2FC255A0798003CD590 /* ThinlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B5F2FB255A0798003CD590 /* ThinlineView.swift */; }; + 31BD72FD276BC0FF00302F75 /* StackViewSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BD72FC276BC0FF00302F75 /* StackViewSegmentedControl.swift */; }; + 31BD72FF276BC13700302F75 /* CustomSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BD72FE276BC13700302F75 /* CustomSegmentedControl.swift */; }; + 31C016EF2737266B004B300E /* SpinImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31C016EE2737266B004B300E /* SpinImageView.swift */; }; + 31CB239726D6BF2F00A0D66D /* CollectionViewSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CB239626D6BF2F00A0D66D /* CollectionViewSegmentedControl.swift */; }; + 31CB239B26D6C3DA00A0D66D /* SegmentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CB239A26D6C3DA00A0D66D /* SegmentCollectionViewCell.swift */; }; + 31DEFEB227754929009BEBF6 /* EmailExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DEFEB0277544EA009BEBF6 /* EmailExporter.swift */; }; + 31E65AE7216BC9E1008ABEE9 /* UIToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31E65ADD216BC9E1008ABEE9 /* UIToolkits.framework */; }; + 31E65AEC216BC9E1008ABEE9 /* UIToolkitsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E65AEB216BC9E1008ABEE9 /* UIToolkitsTests.swift */; }; + 31E65AEE216BC9E1008ABEE9 /* UIToolkits.h in Headers */ = {isa = PBXBuildFile; fileRef = 31E65AE0216BC9E1008ABEE9 /* UIToolkits.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 31F3D45E2719EC500007602A /* GradientSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F3D45D2719EC500007602A /* GradientSlider.swift */; }; + 55A95A926667032EC086332E /* Pods_iOS_UIAppToolkitsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C23629274FB562961EF367D /* Pods_iOS_UIAppToolkitsTests.framework */; }; + 642BB64927826E2600DE74CC /* ContextMenuViewController+Customize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 642BB64827826E2600DE74CC /* ContextMenuViewController+Customize.swift */; }; + 64CB23B027986FA6009A5062 /* TooltipManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64CB23AF27986FA6009A5062 /* TooltipManager.swift */; }; + 78C7664D42E2E365218C7A12 /* Pods_iOS_UIToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15C30AD6439E584B627185C8 /* Pods_iOS_UIToolkits.framework */; }; + 8C01DAE464A73E2FF9CD5BF4 /* Pods_iOS_UIAppToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 799FD1D0CC0B2695FE88DF5E /* Pods_iOS_UIAppToolkits.framework */; }; + F15FD947F10E216D0DD07EB3 /* Pods_iOS_UIToolkitsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1AFFE0445F489F97ED1F0BCA /* Pods_iOS_UIToolkitsTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 310C3A1521D9623F0081E56D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31E65AD4216BC9E1008ABEE9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 310C3A0A21D9623E0081E56D; + remoteInfo = UIAppToolkits; + }; + 310C3B0A21D9C8F20081E56D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31E65AD4216BC9E1008ABEE9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31E65ADC216BC9E1008ABEE9; + remoteInfo = UIToolkits; + }; + 310C3B0C21D9C8F20081E56D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F362176EF2A00655040 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 311D6F3B2176EF2A00655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F362176EF2A00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 311D6F3D2176EF2A00655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F362176EF2A00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 311D6F3F2176EF3700655040 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F362176EF2A00655040 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 313EB77621BB4DC000BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F362176EF2A00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 313EB77821BB4DC000BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F362176EF2A00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 313EB77C21BB4DC000BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F362176EF2A00655040 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 313EB86521BB731800BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F362176EF2A00655040 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 3196823621B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 313EBB0E21BB79AD00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31E65AD4216BC9E1008ABEE9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 313EBB0321BB79AD00BEF926; + remoteInfo = UIToolkitsAppleTV; + }; + 313EBB3721BB79CF00BEF926 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 311D6F362176EF2A00655040 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 313A536421B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 31E65AE8216BC9E1008ABEE9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31E65AD4216BC9E1008ABEE9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31E65ADC216BC9E1008ABEE9; + remoteInfo = UIToolkits; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0295391129FB209F009026E3 /* UIImage+Gradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Gradient.swift"; sourceTree = ""; }; + 0F319D9DE3EF6DAF915D127C /* Pods-iOS-UIAppToolkitsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-UIAppToolkitsTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-UIAppToolkitsTests/Pods-iOS-UIAppToolkitsTests.release.xcconfig"; sourceTree = ""; }; + 15C30AD6439E584B627185C8 /* Pods_iOS_UIToolkits.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_UIToolkits.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 17B0FA87BC34A4B27C1C6700 /* Pods-iOS-UIToolkitsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-UIToolkitsTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-UIToolkitsTests/Pods-iOS-UIToolkitsTests.release.xcconfig"; sourceTree = ""; }; + 1AFFE0445F489F97ED1F0BCA /* Pods_iOS_UIToolkitsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_UIToolkitsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 310A153C27627E100059812C /* UIGestureRecognizer+Closure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIGestureRecognizer+Closure.swift"; sourceTree = ""; }; + 310A154027627FA00059812C /* UIView+Tap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Tap.swift"; sourceTree = ""; }; + 310C3A0B21D9623E0081E56D /* UIAppToolkits.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = UIAppToolkits.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 310C3A0D21D9623F0081E56D /* UIAppToolkits.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIAppToolkits.h; sourceTree = ""; }; + 310C3A0E21D9623F0081E56D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 310C3A1321D9623F0081E56D /* UIAppToolkitsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIAppToolkitsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 310C3A1821D9623F0081E56D /* UIAppToolkitsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAppToolkitsTests.swift; sourceTree = ""; }; + 310C3A1A21D9623F0081E56D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 310CC22C26A7388D00046434 /* ButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonView.swift; sourceTree = ""; }; + 310E61F5216C69740043BB33 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 311BB0F92550CA2A004331D8 /* UISearchPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISearchPresenter.swift; sourceTree = ""; }; + 311D6F362176EF2A00655040 /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 312264B021CDE118000ADAD3 /* UIToolkitsExtensions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIToolkitsExtensions.h; sourceTree = ""; }; + 312264B121CDE118000ADAD3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 312FF055272874D200D35CEE /* SVGCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SVGCache.swift; sourceTree = ""; }; + 31308B4324FAEC34003B5B9A /* ColorPalette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPalette.swift; sourceTree = ""; }; + 31308B4724FAEC58003B5B9A /* String+Styles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Styles.swift"; sourceTree = ""; }; + 31308B4B24FAECAF003B5B9A /* UIView+Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Color.swift"; sourceTree = ""; }; + 31308B4E24FAECCE003B5B9A /* UIViewController+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Search.swift"; sourceTree = ""; }; + 31308B5124FAECED003B5B9A /* OutlineLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutlineLabel.swift; sourceTree = ""; }; + 3131853B24CE6B75005AC9CE /* OverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayView.swift; sourceTree = ""; }; + 313EB85D21BB730700BEF926 /* UIToolkits.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = UIToolkits.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EB85F21BB730700BEF926 /* UIToolkits.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIToolkits.h; sourceTree = ""; }; + 313EB86021BB730700BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EBB0421BB79AD00BEF926 /* UIToolkits.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = UIToolkits.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EBB0621BB79AD00BEF926 /* UIToolkits.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIToolkits.h; sourceTree = ""; }; + 313EBB0721BB79AD00BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EBB0C21BB79AD00BEF926 /* UIToolkitsAppleTVTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIToolkitsAppleTVTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 313EBB1121BB79AD00BEF926 /* UIToolkitsAppleTVTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIToolkitsAppleTVTests.swift; sourceTree = ""; }; + 313EBB1321BB79AD00BEF926 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313EBF3321BC4E4300BEF926 /* WKInterfaceImage+ImageUrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKInterfaceImage+ImageUrl.swift"; sourceTree = ""; }; + 31432B9B2760136F00213970 /* AlwaysVisibleScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlwaysVisibleScrollView.swift; sourceTree = ""; }; + 3145CDF625F40ED600BCCFCA /* NSLayoutConstraint+Changes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLayoutConstraint+Changes.swift"; sourceTree = ""; }; + 31471F7324BA300D00057221 /* OCRScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCRScanner.swift; sourceTree = ""; }; + 31471F8024BA310100057221 /* CirclePreviewOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CirclePreviewOverlay.swift; sourceTree = ""; }; + 31471F8224BA311E00057221 /* OverlayProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayProtocol.swift; sourceTree = ""; }; + 31471F8424BA313A00057221 /* PreviewOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewOverlay.swift; sourceTree = ""; }; + 31471F8624BA315200057221 /* RectPreviewOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectPreviewOverlay.swift; sourceTree = ""; }; + 314B62B123DCCEE400139EB3 /* OCRService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCRService.swift; sourceTree = ""; }; + 314B62B523DCCEE400139EB3 /* UIProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIProtocols.swift; sourceTree = ""; }; + 314B62B723DCCEE400139EB3 /* UIBarButtonItem+Target.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+Target.swift"; sourceTree = ""; }; + 314B62B823DCCEE400139EB3 /* Debouncer+View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Debouncer+View.swift"; sourceTree = ""; }; + 314B62B923DCCEE400139EB3 /* UIImageView+Tint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+Tint.swift"; sourceTree = ""; }; + 314B62BA23DCCEE400139EB3 /* UIButton+Layout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIButton+Layout.swift"; sourceTree = ""; }; + 314B62BC23DCCEE400139EB3 /* UIFont+Style.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIFont+Style.swift"; sourceTree = ""; }; + 314B62BD23DCCEE400139EB3 /* UIView+Hierarchy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Hierarchy.swift"; sourceTree = ""; }; + 314B62BE23DCCEE400139EB3 /* UIImage+Tint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Tint.swift"; sourceTree = ""; }; + 314B62BF23DCCEE400139EB3 /* UIDevice+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Utils.swift"; sourceTree = ""; }; + 314B62C123DCCEE400139EB3 /* UIViewController+Navigation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Navigation.swift"; sourceTree = ""; }; + 314B62C223DCCEE400139EB3 /* UIKit+Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIKit+Protocols.swift"; sourceTree = ""; }; + 314B62C323DCCEE400139EB3 /* UIViewController+Embed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Embed.swift"; sourceTree = ""; }; + 314B62C423DCCEE400139EB3 /* HMSegmentedControl+SegmentedProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HMSegmentedControl+SegmentedProtocol.swift"; sourceTree = ""; }; + 314B62C523DCCEE400139EB3 /* UIView+Border.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Border.swift"; sourceTree = ""; }; + 314B62C623DCCEE400139EB3 /* UIAlertController+Text.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Text.swift"; sourceTree = ""; }; + 314B62C823DCCEE400139EB3 /* UIView+Transitions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Transitions.swift"; sourceTree = ""; }; + 314B62C923DCCEE400139EB3 /* UINavigationBar+Transparent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Transparent.swift"; sourceTree = ""; }; + 314B62CA23DCCEE400139EB3 /* UIResponder+First.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIResponder+First.swift"; sourceTree = ""; }; + 314B62CB23DCCEE400139EB3 /* UINavigationController+Load.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Load.swift"; sourceTree = ""; }; + 314B62CD23DCCEE400139EB3 /* ReachabilityMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityMessage.swift; sourceTree = ""; }; + 314B62CF23DCCEE400139EB3 /* FloatingManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatingManager.swift; sourceTree = ""; }; + 314B62D023DCCEE400139EB3 /* UIViewController+Half.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Half.swift"; sourceTree = ""; }; + 314B62D223DCCEE500139EB3 /* UIViewControllerProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewControllerProtocols.swift; sourceTree = ""; }; + 314B62D323DCCEE500139EB3 /* KeyboardAdjustingProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardAdjustingProtocol.swift; sourceTree = ""; }; + 314B62D423DCCEE500139EB3 /* LoadingIndicatorProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingIndicatorProtocol.swift; sourceTree = ""; }; + 314B62DA23DCCEE500139EB3 /* UISearchBar+Icons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UISearchBar+Icons.swift"; sourceTree = ""; }; + 314B62DC23DCCEE500139EB3 /* UXTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UXTableView.swift; sourceTree = ""; }; + 314B62DD23DCCEE500139EB3 /* TextCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextCollectionViewCell.swift; sourceTree = ""; }; + 314B62DE23DCCEE500139EB3 /* SelfSizingCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelfSizingCollectionView.swift; sourceTree = ""; }; + 314B62DF23DCCEE500139EB3 /* DrawingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawingView.swift; sourceTree = ""; }; + 314B62E023DCCEE500139EB3 /* CachedImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedImageView.swift; sourceTree = ""; }; + 314B62E323DCCEE500139EB3 /* UXLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UXLabel.swift; sourceTree = ""; }; + 314B62E423DCCEE500139EB3 /* ImageCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCollectionViewCell.swift; sourceTree = ""; }; + 314B62E523DCCEE500139EB3 /* CollectionViewGridLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewGridLayout.swift; sourceTree = ""; }; + 314B62E623DCCEE500139EB3 /* SelfSizingTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelfSizingTableView.swift; sourceTree = ""; }; + 314B62E723DCCEE500139EB3 /* UXSearchBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UXSearchBar.swift; sourceTree = ""; }; + 314B62E823DCCEE500139EB3 /* UserInteraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInteraction.swift; sourceTree = ""; }; + 314B62E923DCCEE500139EB3 /* LabelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelView.swift; sourceTree = ""; }; + 314B62EB23DCCEE500139EB3 /* UIKit+Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIKit+Localization.swift"; sourceTree = ""; }; + 314B62ED23DCCEE500139EB3 /* MKCoordinateRegion+Corners.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MKCoordinateRegion+Corners.swift"; sourceTree = ""; }; + 314B62EF23DCCEE500139EB3 /* ViewControllerStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerStack.swift; sourceTree = ""; }; + 314B62F023DCCEE500139EB3 /* KeyboardAdjustingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardAdjustingViewController.swift; sourceTree = ""; }; + 314B62F123DCCEE500139EB3 /* UIViewController+Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Helper.swift"; sourceTree = ""; }; + 314B62F223DCCEE500139EB3 /* LoadingNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingNavigationController.swift; sourceTree = ""; }; + 314B62F323DCCEE500139EB3 /* UXNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UXNavigationController.swift; sourceTree = ""; }; + 314B62F723DCCEE500139EB3 /* LoadingProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingProgressView.swift; sourceTree = ""; }; + 314B62F823DCCEE500139EB3 /* UIKitAppViewControllerStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitAppViewControllerStack.swift; sourceTree = ""; }; + 314B673F23DCE35800139EB3 /* UIViewController+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+App.swift"; sourceTree = ""; }; + 314C359824C7C0E200695F7E /* UIImage+Loading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Loading.swift"; sourceTree = ""; }; + 314D9C012485A83F00582A1A /* TextEntryPrompter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryPrompter.swift; sourceTree = ""; }; + 314D9C032485A85800582A1A /* UIKitPrompterFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitPrompterFactory.swift; sourceTree = ""; }; + 314D9C072485A88000582A1A /* AlertPrompter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPrompter.swift; sourceTree = ""; }; + 314D9C0A2485A8C100582A1A /* ScannerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannerProtocol.swift; sourceTree = ""; }; + 314D9C0C2485A8D300582A1A /* Scanners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scanners.swift; sourceTree = ""; }; + 314D9C0E2485A8EE00582A1A /* CompositeScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositeScanner.swift; sourceTree = ""; }; + 314D9C112485A91D00582A1A /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = ""; }; + 3151A3AC26B1F41E00D34363 /* UXSegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UXSegmentedControl.swift; sourceTree = ""; }; + 316BAE0E26FA8A56002CDC03 /* UITextField+Done.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField+Done.swift"; sourceTree = ""; }; + 31762167245B307200C9F2FB /* FloatingLayoutProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingLayoutProviderProtocol.swift; sourceTree = ""; }; + 317F16ED2572D24600D178B8 /* TextTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextTableViewCell.swift; sourceTree = ""; }; + 317F16F92572D29400D178B8 /* ImageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTableViewCell.swift; sourceTree = ""; }; + 317F170A2572D2C800D178B8 /* ImageAddCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAddCollectionViewCell.swift; sourceTree = ""; }; + 317F1BC12572E18900D178B8 /* NoteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteViewController.swift; sourceTree = ""; }; + 318A084424DF1A8400981B5F /* UIActivityIndicatorView+Waiting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivityIndicatorView+Waiting.swift"; sourceTree = ""; }; + 318A084824DF1AA100981B5F /* NVActivityIndicatorView+Waiting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NVActivityIndicatorView+Waiting.swift"; sourceTree = ""; }; + 318A5BFF272DC4A6000DA46C /* TabSegmentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSegmentCollectionViewCell.swift; sourceTree = ""; }; + 318A5C04272DC9FA000DA46C /* MotionHapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionHapticFeedback.swift; sourceTree = ""; }; + 3192C86726FA72DD0072BD99 /* CircularProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressView.swift; sourceTree = ""; }; + 3197D91A26F79CE400693D53 /* UITabbar+Transparent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITabbar+Transparent.swift"; sourceTree = ""; }; + 3197DA1426F8FDFD00693D53 /* CircularProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = ""; }; + 31A0373A26F40F93007EFC2F /* UXImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UXImageView.swift; sourceTree = ""; }; + 31A2597826E67ADB00FB7CFD /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; }; + 31A7B177274040350089C713 /* OnOffSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnOffSwitch.swift; sourceTree = ""; }; + 31A7B1D127404C830089C713 /* TappableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TappableLabel.swift; sourceTree = ""; }; + 31B5F2FB255A0798003CD590 /* ThinlineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThinlineView.swift; sourceTree = ""; }; + 31BD72FC276BC0FF00302F75 /* StackViewSegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackViewSegmentedControl.swift; sourceTree = ""; }; + 31BD72FE276BC13700302F75 /* CustomSegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSegmentedControl.swift; sourceTree = ""; }; + 31C016EE2737266B004B300E /* SpinImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinImageView.swift; sourceTree = ""; }; + 31CB239626D6BF2F00A0D66D /* CollectionViewSegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewSegmentedControl.swift; sourceTree = ""; }; + 31CB239A26D6C3DA00A0D66D /* SegmentCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentCollectionViewCell.swift; sourceTree = ""; }; + 31CEE70A216E5BED00DC61DA /* Utilities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Utilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31DEFEB0277544EA009BEBF6 /* EmailExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailExporter.swift; sourceTree = ""; }; + 31E65ADD216BC9E1008ABEE9 /* UIToolkits.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = UIToolkits.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31E65AE0216BC9E1008ABEE9 /* UIToolkits.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIToolkits.h; sourceTree = ""; }; + 31E65AE1216BC9E1008ABEE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31E65AE6216BC9E1008ABEE9 /* UIToolkitsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIToolkitsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 31E65AEB216BC9E1008ABEE9 /* UIToolkitsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIToolkitsTests.swift; sourceTree = ""; }; + 31E65AED216BC9E1008ABEE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31F3D45D2719EC500007602A /* GradientSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientSlider.swift; sourceTree = ""; }; + 36FDC57AAF354EB0B5E2220B /* Pods-iOS-UIAppToolkits.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-UIAppToolkits.release.xcconfig"; path = "Target Support Files/Pods-iOS-UIAppToolkits/Pods-iOS-UIAppToolkits.release.xcconfig"; sourceTree = ""; }; + 642BB64827826E2600DE74CC /* ContextMenuViewController+Customize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContextMenuViewController+Customize.swift"; sourceTree = ""; }; + 64CB23AF27986FA6009A5062 /* TooltipManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipManager.swift; sourceTree = ""; }; + 668A73DB8721B9FD70685E16 /* Pods-iOS-UIToolkitsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-UIToolkitsTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-UIToolkitsTests/Pods-iOS-UIToolkitsTests.debug.xcconfig"; sourceTree = ""; }; + 799FD1D0CC0B2695FE88DF5E /* Pods_iOS_UIAppToolkits.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_UIAppToolkits.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8C23629274FB562961EF367D /* Pods_iOS_UIAppToolkitsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_UIAppToolkitsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 946EEAA78022B56C6B378E35 /* Pods-iOS-UIAppToolkitsTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-UIAppToolkitsTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-UIAppToolkitsTests/Pods-iOS-UIAppToolkitsTests.debug.xcconfig"; sourceTree = ""; }; + 9D5FF1D5D7AC3E369806232C /* Pods-iOS-UIToolkits.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-UIToolkits.release.xcconfig"; path = "Target Support Files/Pods-iOS-UIToolkits/Pods-iOS-UIToolkits.release.xcconfig"; sourceTree = ""; }; + D1BCBE47DC02A75CC9C0122D /* Pods-iOS-UIAppToolkits.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-UIAppToolkits.debug.xcconfig"; path = "Target Support Files/Pods-iOS-UIAppToolkits/Pods-iOS-UIAppToolkits.debug.xcconfig"; sourceTree = ""; }; + D4CB0CEDE0DA8FAF68D8464A /* Pods-iOS-UIToolkits.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-UIToolkits.debug.xcconfig"; path = "Target Support Files/Pods-iOS-UIToolkits/Pods-iOS-UIToolkits.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 310C3A0821D9623E0081E56D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 310C3B1221D9C90D0081E56D /* Utilities.framework in Frameworks */, + 310C3B1121D9C8FD0081E56D /* UIToolkits.framework in Frameworks */, + 8C01DAE464A73E2FF9CD5BF4 /* Pods_iOS_UIAppToolkits.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310C3A1021D9623F0081E56D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 310C3A1421D9623F0081E56D /* UIAppToolkits.framework in Frameworks */, + 55A95A926667032EC086332E /* Pods_iOS_UIAppToolkitsTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB85A21BB730700BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB86721BB732000BEF926 /* Utilities.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB0121BB79AD00BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBB3D21BB79ED00BEF926 /* Utilities.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB0921BB79AD00BEF926 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBB0D21BB79AD00BEF926 /* UIToolkits.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65ADA216BC9E1008ABEE9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 311D6FA72176EFEE00655040 /* Utilities.framework in Frameworks */, + 310E61F6216C69740043BB33 /* UIKit.framework in Frameworks */, + 78C7664D42E2E365218C7A12 /* Pods_iOS_UIToolkits.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AE3216BC9E1008ABEE9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31E65AE7216BC9E1008ABEE9 /* UIToolkits.framework in Frameworks */, + F15FD947F10E216D0DD07EB3 /* Pods_iOS_UIToolkitsTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02684F1028BD41740007CEFF /* Dependencies */ = { + isa = PBXGroup; + children = ( + 311D6F362176EF2A00655040 /* Utilities.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 310C3A0C21D9623F0081E56D /* UIAppToolkits */ = { + isa = PBXGroup; + children = ( + 310C3A0D21D9623F0081E56D /* UIAppToolkits.h */, + 310C3A0E21D9623F0081E56D /* Info.plist */, + ); + path = UIAppToolkits; + sourceTree = ""; + }; + 310C3A1721D9623F0081E56D /* UIAppToolkitsTests */ = { + isa = PBXGroup; + children = ( + 310C3A1821D9623F0081E56D /* UIAppToolkitsTests.swift */, + 310C3A1A21D9623F0081E56D /* Info.plist */, + ); + path = UIAppToolkitsTests; + sourceTree = ""; + }; + 311D6F372176EF2A00655040 /* Products */ = { + isa = PBXGroup; + children = ( + 311D6F3C2176EF2A00655040 /* Utilities.framework */, + 313EB77721BB4DC000BEF926 /* Utilities.framework */, + 313EB77921BB4DC000BEF926 /* Utilities.framework */, + 311D6F3E2176EF2A00655040 /* UtilitiesTests.xctest */, + 313EB77D21BB4DC000BEF926 /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 312264AF21CDE118000ADAD3 /* UIToolkitsExtensions */ = { + isa = PBXGroup; + children = ( + 312264B021CDE118000ADAD3 /* UIToolkitsExtensions.h */, + 312264B121CDE118000ADAD3 /* Info.plist */, + ); + path = UIToolkitsExtensions; + sourceTree = ""; + }; + 312FF052272874B700D35CEE /* _SVG */ = { + isa = PBXGroup; + children = ( + 312FF055272874D200D35CEE /* SVGCache.swift */, + ); + path = _SVG; + sourceTree = ""; + }; + 31308B4024FAEC18003B5B9A /* _Color */ = { + isa = PBXGroup; + children = ( + 31308B4324FAEC34003B5B9A /* ColorPalette.swift */, + ); + path = _Color; + sourceTree = ""; + }; + 31351F13272883A000C84FA7 /* _Haptic */ = { + isa = PBXGroup; + children = ( + 318A5C04272DC9FA000DA46C /* MotionHapticFeedback.swift */, + ); + path = _Haptic; + sourceTree = ""; + }; + 313EB85E21BB730700BEF926 /* UIToolkitsAppleWatch */ = { + isa = PBXGroup; + children = ( + 313EBF2F21BC4E2700BEF926 /* Extensions */, + 313EB85F21BB730700BEF926 /* UIToolkits.h */, + 313EB86021BB730700BEF926 /* Info.plist */, + ); + path = UIToolkitsAppleWatch; + sourceTree = ""; + }; + 313EBB0521BB79AD00BEF926 /* UIToolkitsAppleTV */ = { + isa = PBXGroup; + children = ( + 31AEC3F121C4AC0000132AAC /* Stub */, + 313EBB0621BB79AD00BEF926 /* UIToolkits.h */, + 313EBB0721BB79AD00BEF926 /* Info.plist */, + ); + path = UIToolkitsAppleTV; + sourceTree = ""; + }; + 313EBB1021BB79AD00BEF926 /* UIToolkitsAppleTVTests */ = { + isa = PBXGroup; + children = ( + 313EBB1121BB79AD00BEF926 /* UIToolkitsAppleTVTests.swift */, + 313EBB1321BB79AD00BEF926 /* Info.plist */, + ); + path = UIToolkitsAppleTVTests; + sourceTree = ""; + }; + 313EBF2F21BC4E2700BEF926 /* Extensions */ = { + isa = PBXGroup; + children = ( + 313EBF3321BC4E4300BEF926 /* WKInterfaceImage+ImageUrl.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 314B62A423DCCEE400139EB3 /* _Location */ = { + isa = PBXGroup; + children = ( + ); + path = _Location; + sourceTree = ""; + }; + 314B62B023DCCEE400139EB3 /* _OCR */ = { + isa = PBXGroup; + children = ( + 314B62B123DCCEE400139EB3 /* OCRService.swift */, + ); + path = _OCR; + sourceTree = ""; + }; + 314B62B223DCCEE400139EB3 /* _Rating */ = { + isa = PBXGroup; + children = ( + ); + path = _Rating; + sourceTree = ""; + }; + 314B62B423DCCEE400139EB3 /* _Shared */ = { + isa = PBXGroup; + children = ( + 314B62B523DCCEE400139EB3 /* UIProtocols.swift */, + ); + path = _Shared; + sourceTree = ""; + }; + 314B62B623DCCEE400139EB3 /* _Extensions */ = { + isa = PBXGroup; + children = ( + 642BB64827826E2600DE74CC /* ContextMenuViewController+Customize.swift */, + 314B62B823DCCEE400139EB3 /* Debouncer+View.swift */, + 314B62C423DCCEE400139EB3 /* HMSegmentedControl+SegmentedProtocol.swift */, + 3145CDF625F40ED600BCCFCA /* NSLayoutConstraint+Changes.swift */, + 318A084824DF1AA100981B5F /* NVActivityIndicatorView+Waiting.swift */, + 31308B4724FAEC58003B5B9A /* String+Styles.swift */, + 318A084424DF1A8400981B5F /* UIActivityIndicatorView+Waiting.swift */, + 314B62C623DCCEE400139EB3 /* UIAlertController+Text.swift */, + 314B62B723DCCEE400139EB3 /* UIBarButtonItem+Target.swift */, + 314B62BA23DCCEE400139EB3 /* UIButton+Layout.swift */, + 314B62BF23DCCEE400139EB3 /* UIDevice+Utils.swift */, + 314B62BC23DCCEE400139EB3 /* UIFont+Style.swift */, + 310A153C27627E100059812C /* UIGestureRecognizer+Closure.swift */, + 314C359824C7C0E200695F7E /* UIImage+Loading.swift */, + 314B62BE23DCCEE400139EB3 /* UIImage+Tint.swift */, + 0295391129FB209F009026E3 /* UIImage+Gradient.swift */, + 314B62B923DCCEE400139EB3 /* UIImageView+Tint.swift */, + 314B62C223DCCEE400139EB3 /* UIKit+Protocols.swift */, + 314B62C923DCCEE400139EB3 /* UINavigationBar+Transparent.swift */, + 314B62CB23DCCEE400139EB3 /* UINavigationController+Load.swift */, + 314B62CA23DCCEE400139EB3 /* UIResponder+First.swift */, + 311BB0F92550CA2A004331D8 /* UISearchPresenter.swift */, + 3197D91A26F79CE400693D53 /* UITabbar+Transparent.swift */, + 316BAE0E26FA8A56002CDC03 /* UITextField+Done.swift */, + 314B62C523DCCEE400139EB3 /* UIView+Border.swift */, + 31308B4B24FAECAF003B5B9A /* UIView+Color.swift */, + 314B62BD23DCCEE400139EB3 /* UIView+Hierarchy.swift */, + 310A154027627FA00059812C /* UIView+Tap.swift */, + 314B62C823DCCEE400139EB3 /* UIView+Transitions.swift */, + 314B62C323DCCEE400139EB3 /* UIViewController+Embed.swift */, + 314B62C123DCCEE400139EB3 /* UIViewController+Navigation.swift */, + 31308B4E24FAECCE003B5B9A /* UIViewController+Search.swift */, + ); + path = _Extensions; + sourceTree = ""; + }; + 314B62CC23DCCEE400139EB3 /* _Connectivity */ = { + isa = PBXGroup; + children = ( + 314B62CD23DCCEE400139EB3 /* ReachabilityMessage.swift */, + ); + path = _Connectivity; + sourceTree = ""; + }; + 314B62CE23DCCEE400139EB3 /* _Float */ = { + isa = PBXGroup; + children = ( + 31762167245B307200C9F2FB /* FloatingLayoutProviderProtocol.swift */, + 314B62CF23DCCEE400139EB3 /* FloatingManager.swift */, + 314B62D023DCCEE400139EB3 /* UIViewController+Half.swift */, + ); + path = _Float; + sourceTree = ""; + }; + 314B62D123DCCEE500139EB3 /* _Protocols */ = { + isa = PBXGroup; + children = ( + 314B62D323DCCEE500139EB3 /* KeyboardAdjustingProtocol.swift */, + 314B62D423DCCEE500139EB3 /* LoadingIndicatorProtocol.swift */, + 314B62D223DCCEE500139EB3 /* UIViewControllerProtocols.swift */, + ); + path = _Protocols; + sourceTree = ""; + }; + 314B62D823DCCEE500139EB3 /* _iOS */ = { + isa = PBXGroup; + children = ( + 314B62DA23DCCEE500139EB3 /* UISearchBar+Icons.swift */, + ); + path = _iOS; + sourceTree = ""; + }; + 314B62DB23DCCEE500139EB3 /* _View */ = { + isa = PBXGroup; + children = ( + 31BD72FB276BC09F00302F75 /* _Segments */, + 31432B9B2760136F00213970 /* AlwaysVisibleScrollView.swift */, + 310CC22C26A7388D00046434 /* ButtonView.swift */, + 314B62E023DCCEE500139EB3 /* CachedImageView.swift */, + 3197DA1426F8FDFD00693D53 /* CircularProgressBar.swift */, + 3192C86726FA72DD0072BD99 /* CircularProgressView.swift */, + 314B62E523DCCEE500139EB3 /* CollectionViewGridLayout.swift */, + 314B62DF23DCCEE500139EB3 /* DrawingView.swift */, + 31F3D45D2719EC500007602A /* GradientSlider.swift */, + 31A2597826E67ADB00FB7CFD /* GradientView.swift */, + 317F170A2572D2C800D178B8 /* ImageAddCollectionViewCell.swift */, + 314B62E423DCCEE500139EB3 /* ImageCollectionViewCell.swift */, + 317F16F92572D29400D178B8 /* ImageTableViewCell.swift */, + 314B62E923DCCEE500139EB3 /* LabelView.swift */, + 31A7B177274040350089C713 /* OnOffSwitch.swift */, + 31308B5124FAECED003B5B9A /* OutlineLabel.swift */, + 3131853B24CE6B75005AC9CE /* OverlayView.swift */, + 314B62DE23DCCEE500139EB3 /* SelfSizingCollectionView.swift */, + 314B62E623DCCEE500139EB3 /* SelfSizingTableView.swift */, + 31C016EE2737266B004B300E /* SpinImageView.swift */, + 31A7B1D127404C830089C713 /* TappableLabel.swift */, + 314B62DD23DCCEE500139EB3 /* TextCollectionViewCell.swift */, + 317F16ED2572D24600D178B8 /* TextTableViewCell.swift */, + 31B5F2FB255A0798003CD590 /* ThinlineView.swift */, + 314B62E823DCCEE500139EB3 /* UserInteraction.swift */, + 31A0373A26F40F93007EFC2F /* UXImageView.swift */, + 314B62E323DCCEE500139EB3 /* UXLabel.swift */, + 314B62E723DCCEE500139EB3 /* UXSearchBar.swift */, + 3151A3AC26B1F41E00D34363 /* UXSegmentedControl.swift */, + 314B62DC23DCCEE500139EB3 /* UXTableView.swift */, + 64CB23AF27986FA6009A5062 /* TooltipManager.swift */, + ); + path = _View; + sourceTree = ""; + }; + 314B62EA23DCCEE500139EB3 /* _Localization */ = { + isa = PBXGroup; + children = ( + 314B62EB23DCCEE500139EB3 /* UIKit+Localization.swift */, + ); + path = _Localization; + sourceTree = ""; + }; + 314B62EC23DCCEE500139EB3 /* _Map */ = { + isa = PBXGroup; + children = ( + 314B62ED23DCCEE500139EB3 /* MKCoordinateRegion+Corners.swift */, + ); + path = _Map; + sourceTree = ""; + }; + 314B62EE23DCCEE500139EB3 /* _ViewController */ = { + isa = PBXGroup; + children = ( + 314B62F023DCCEE500139EB3 /* KeyboardAdjustingViewController.swift */, + 314B62F223DCCEE500139EB3 /* LoadingNavigationController.swift */, + 314B673F23DCE35800139EB3 /* UIViewController+App.swift */, + 314B62F123DCCEE500139EB3 /* UIViewController+Helper.swift */, + 314B62F323DCCEE500139EB3 /* UXNavigationController.swift */, + 314B62EF23DCCEE500139EB3 /* ViewControllerStack.swift */, + 317F1BC12572E18900D178B8 /* NoteViewController.swift */, + ); + path = _ViewController; + sourceTree = ""; + }; + 314B62F423DCCEE500139EB3 /* _App */ = { + isa = PBXGroup; + children = ( + 314B62F723DCCEE500139EB3 /* LoadingProgressView.swift */, + 314B62F823DCCEE500139EB3 /* UIKitAppViewControllerStack.swift */, + ); + path = _App; + sourceTree = ""; + }; + 314D9BFE2485A80900582A1A /* _Prompter */ = { + isa = PBXGroup; + children = ( + 314D9C072485A88000582A1A /* AlertPrompter.swift */, + 314D9C012485A83F00582A1A /* TextEntryPrompter.swift */, + 314D9C032485A85800582A1A /* UIKitPrompterFactory.swift */, + ); + path = _Prompter; + sourceTree = ""; + }; + 314D9C092485A8A900582A1A /* _Scan */ = { + isa = PBXGroup; + children = ( + 314D9C0E2485A8EE00582A1A /* CompositeScanner.swift */, + 31471F7324BA300D00057221 /* OCRScanner.swift */, + 314D9C0A2485A8C100582A1A /* ScannerProtocol.swift */, + 314D9C0C2485A8D300582A1A /* Scanners.swift */, + ); + path = _Scan; + sourceTree = ""; + }; + 314D9C102485A90B00582A1A /* _QRCode */ = { + isa = PBXGroup; + children = ( + 314D9C112485A91D00582A1A /* QRCode.swift */, + ); + path = _QRCode; + sourceTree = ""; + }; + 314D9C132485A93200582A1A /* _Camera */ = { + isa = PBXGroup; + children = ( + 31471F8024BA310100057221 /* CirclePreviewOverlay.swift */, + 31471F8224BA311E00057221 /* OverlayProtocol.swift */, + 31471F8424BA313A00057221 /* PreviewOverlay.swift */, + 31471F8624BA315200057221 /* RectPreviewOverlay.swift */, + ); + path = _Camera; + sourceTree = ""; + }; + 31AEC3F121C4AC0000132AAC /* Stub */ = { + isa = PBXGroup; + children = ( + ); + path = Stub; + sourceTree = ""; + }; + 31BD72FB276BC09F00302F75 /* _Segments */ = { + isa = PBXGroup; + children = ( + 31CB239626D6BF2F00A0D66D /* CollectionViewSegmentedControl.swift */, + 31BD72FE276BC13700302F75 /* CustomSegmentedControl.swift */, + 31CB239A26D6C3DA00A0D66D /* SegmentCollectionViewCell.swift */, + 31BD72FC276BC0FF00302F75 /* StackViewSegmentedControl.swift */, + 318A5BFF272DC4A6000DA46C /* TabSegmentCollectionViewCell.swift */, + ); + path = _Segments; + sourceTree = ""; + }; + 31DEFEAD2775444C009BEBF6 /* _Export */ = { + isa = PBXGroup; + children = ( + 31DEFEB0277544EA009BEBF6 /* EmailExporter.swift */, + ); + path = _Export; + sourceTree = ""; + }; + 31E65AD3216BC9E1008ABEE9 = { + isa = PBXGroup; + children = ( + 02684F1028BD41740007CEFF /* Dependencies */, + 82D559A2CC901F66DFB67D4E /* Frameworks */, + 31E65ADE216BC9E1008ABEE9 /* Products */, + 310C3A0C21D9623F0081E56D /* UIAppToolkits */, + 310C3A1721D9623F0081E56D /* UIAppToolkitsTests */, + 31E65ADF216BC9E1008ABEE9 /* UIToolkits */, + 313EBB0521BB79AD00BEF926 /* UIToolkitsAppleTV */, + 313EBB1021BB79AD00BEF926 /* UIToolkitsAppleTVTests */, + 313EB85E21BB730700BEF926 /* UIToolkitsAppleWatch */, + 312264AF21CDE118000ADAD3 /* UIToolkitsExtensions */, + 31E65AEA216BC9E1008ABEE9 /* UIToolkitsTests */, + D369E02F942366BFEA4C29FA /* Pods */, + ); + sourceTree = ""; + }; + 31E65ADE216BC9E1008ABEE9 /* Products */ = { + isa = PBXGroup; + children = ( + 31E65ADD216BC9E1008ABEE9 /* UIToolkits.framework */, + 31E65AE6216BC9E1008ABEE9 /* UIToolkitsTests.xctest */, + 313EB85D21BB730700BEF926 /* UIToolkits.framework */, + 313EBB0421BB79AD00BEF926 /* UIToolkits.framework */, + 313EBB0C21BB79AD00BEF926 /* UIToolkitsAppleTVTests.xctest */, + 310C3A0B21D9623E0081E56D /* UIAppToolkits.framework */, + 310C3A1321D9623F0081E56D /* UIAppToolkitsTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31E65ADF216BC9E1008ABEE9 /* UIToolkits */ = { + isa = PBXGroup; + children = ( + 314B62F423DCCEE500139EB3 /* _App */, + 314D9C132485A93200582A1A /* _Camera */, + 31308B4024FAEC18003B5B9A /* _Color */, + 314B62CC23DCCEE400139EB3 /* _Connectivity */, + 31DEFEAD2775444C009BEBF6 /* _Export */, + 314B62B623DCCEE400139EB3 /* _Extensions */, + 314B62CE23DCCEE400139EB3 /* _Float */, + 31351F13272883A000C84FA7 /* _Haptic */, + 314B62D823DCCEE500139EB3 /* _iOS */, + 314B62EA23DCCEE500139EB3 /* _Localization */, + 314B62A423DCCEE400139EB3 /* _Location */, + 314B62EC23DCCEE500139EB3 /* _Map */, + 314B62B023DCCEE400139EB3 /* _OCR */, + 314D9BFE2485A80900582A1A /* _Prompter */, + 314B62D123DCCEE500139EB3 /* _Protocols */, + 314D9C102485A90B00582A1A /* _QRCode */, + 314B62B223DCCEE400139EB3 /* _Rating */, + 314D9C092485A8A900582A1A /* _Scan */, + 314B62B423DCCEE400139EB3 /* _Shared */, + 312FF052272874B700D35CEE /* _SVG */, + 314B62DB23DCCEE500139EB3 /* _View */, + 314B62EE23DCCEE500139EB3 /* _ViewController */, + 31E65AE1216BC9E1008ABEE9 /* Info.plist */, + 31E65AE0216BC9E1008ABEE9 /* UIToolkits.h */, + ); + path = UIToolkits; + sourceTree = ""; + }; + 31E65AEA216BC9E1008ABEE9 /* UIToolkitsTests */ = { + isa = PBXGroup; + children = ( + 31E65AEB216BC9E1008ABEE9 /* UIToolkitsTests.swift */, + 31E65AED216BC9E1008ABEE9 /* Info.plist */, + ); + path = UIToolkitsTests; + sourceTree = ""; + }; + 82D559A2CC901F66DFB67D4E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 31CEE70A216E5BED00DC61DA /* Utilities.framework */, + 310E61F5216C69740043BB33 /* UIKit.framework */, + 799FD1D0CC0B2695FE88DF5E /* Pods_iOS_UIAppToolkits.framework */, + 8C23629274FB562961EF367D /* Pods_iOS_UIAppToolkitsTests.framework */, + 15C30AD6439E584B627185C8 /* Pods_iOS_UIToolkits.framework */, + 1AFFE0445F489F97ED1F0BCA /* Pods_iOS_UIToolkitsTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D369E02F942366BFEA4C29FA /* Pods */ = { + isa = PBXGroup; + children = ( + D1BCBE47DC02A75CC9C0122D /* Pods-iOS-UIAppToolkits.debug.xcconfig */, + 36FDC57AAF354EB0B5E2220B /* Pods-iOS-UIAppToolkits.release.xcconfig */, + 946EEAA78022B56C6B378E35 /* Pods-iOS-UIAppToolkitsTests.debug.xcconfig */, + 0F319D9DE3EF6DAF915D127C /* Pods-iOS-UIAppToolkitsTests.release.xcconfig */, + D4CB0CEDE0DA8FAF68D8464A /* Pods-iOS-UIToolkits.debug.xcconfig */, + 9D5FF1D5D7AC3E369806232C /* Pods-iOS-UIToolkits.release.xcconfig */, + 668A73DB8721B9FD70685E16 /* Pods-iOS-UIToolkitsTests.debug.xcconfig */, + 17B0FA87BC34A4B27C1C6700 /* Pods-iOS-UIToolkitsTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 310C3A0621D9623E0081E56D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 310C3A1B21D9623F0081E56D /* UIAppToolkits.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB85821BB730700BEF926 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EB86121BB730700BEF926 /* UIToolkits.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBAFF21BB79AD00BEF926 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBB1421BB79AD00BEF926 /* UIToolkits.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AD8216BC9E1008ABEE9 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 31E65AEE216BC9E1008ABEE9 /* UIToolkits.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 310C3A0A21D9623E0081E56D /* UIAppToolkits */ = { + isa = PBXNativeTarget; + buildConfigurationList = 310C3A2321D9623F0081E56D /* Build configuration list for PBXNativeTarget "UIAppToolkits" */; + buildPhases = ( + 1F2AE7E1622345ACAE6D26EA /* [CP] Check Pods Manifest.lock */, + 310C3A0621D9623E0081E56D /* Headers */, + 310C3A0721D9623E0081E56D /* Sources */, + 310C3A0821D9623E0081E56D /* Frameworks */, + 310C3A0921D9623E0081E56D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 310C3B0B21D9C8F20081E56D /* PBXTargetDependency */, + 310C3B0D21D9C8F20081E56D /* PBXTargetDependency */, + ); + name = UIAppToolkits; + productName = UIAppToolkits; + productReference = 310C3A0B21D9623E0081E56D /* UIAppToolkits.framework */; + productType = "com.apple.product-type.framework"; + }; + 310C3A1221D9623F0081E56D /* UIAppToolkitsTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 310C3A2421D9623F0081E56D /* Build configuration list for PBXNativeTarget "UIAppToolkitsTests" */; + buildPhases = ( + 6335DE5A0A70EB35008676A7 /* [CP] Check Pods Manifest.lock */, + 310C3A0F21D9623F0081E56D /* Sources */, + 310C3A1021D9623F0081E56D /* Frameworks */, + 310C3A1121D9623F0081E56D /* Resources */, + B35B36B7F40D2535C98315E7 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 310C3A1621D9623F0081E56D /* PBXTargetDependency */, + ); + name = UIAppToolkitsTests; + productName = UIAppToolkitsTests; + productReference = 310C3A1321D9623F0081E56D /* UIAppToolkitsTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 313EB85C21BB730700BEF926 /* UIToolkitsAppleWatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EB86221BB730700BEF926 /* Build configuration list for PBXNativeTarget "UIToolkitsAppleWatch" */; + buildPhases = ( + 313EB85821BB730700BEF926 /* Headers */, + 313EB85921BB730700BEF926 /* Sources */, + 313EB85A21BB730700BEF926 /* Frameworks */, + 313EB85B21BB730700BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EB86621BB731800BEF926 /* PBXTargetDependency */, + ); + name = UIToolkitsAppleWatch; + productName = UIToolkitsAppleWatch; + productReference = 313EB85D21BB730700BEF926 /* UIToolkits.framework */; + productType = "com.apple.product-type.framework"; + }; + 313EBB0321BB79AD00BEF926 /* UIToolkitsAppleTV */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EBB1521BB79AD00BEF926 /* Build configuration list for PBXNativeTarget "UIToolkitsAppleTV" */; + buildPhases = ( + 313EBAFF21BB79AD00BEF926 /* Headers */, + 313EBB0021BB79AD00BEF926 /* Sources */, + 313EBB0121BB79AD00BEF926 /* Frameworks */, + 313EBB0221BB79AD00BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EBB3821BB79CF00BEF926 /* PBXTargetDependency */, + ); + name = UIToolkitsAppleTV; + productName = UIToolkitsAppleTV; + productReference = 313EBB0421BB79AD00BEF926 /* UIToolkits.framework */; + productType = "com.apple.product-type.framework"; + }; + 313EBB0B21BB79AD00BEF926 /* UIToolkitsAppleTVTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313EBB1821BB79AD00BEF926 /* Build configuration list for PBXNativeTarget "UIToolkitsAppleTVTests" */; + buildPhases = ( + 313EBB0821BB79AD00BEF926 /* Sources */, + 313EBB0921BB79AD00BEF926 /* Frameworks */, + 313EBB0A21BB79AD00BEF926 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313EBB0F21BB79AD00BEF926 /* PBXTargetDependency */, + ); + name = UIToolkitsAppleTVTests; + productName = UIToolkitsAppleTVTests; + productReference = 313EBB0C21BB79AD00BEF926 /* UIToolkitsAppleTVTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 31E65ADC216BC9E1008ABEE9 /* UIToolkits */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31E65AF1216BC9E1008ABEE9 /* Build configuration list for PBXNativeTarget "UIToolkits" */; + buildPhases = ( + 2CF2E8843AEEAE9D7E3280E5 /* [CP] Check Pods Manifest.lock */, + 31E65AD8216BC9E1008ABEE9 /* Headers */, + 31E65AD9216BC9E1008ABEE9 /* Sources */, + 31E65ADA216BC9E1008ABEE9 /* Frameworks */, + 31E65ADB216BC9E1008ABEE9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 311D6F402176EF3700655040 /* PBXTargetDependency */, + ); + name = UIToolkits; + productName = UIToolkits; + productReference = 31E65ADD216BC9E1008ABEE9 /* UIToolkits.framework */; + productType = "com.apple.product-type.framework"; + }; + 31E65AE5216BC9E1008ABEE9 /* UIToolkitsTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31E65AF4216BC9E1008ABEE9 /* Build configuration list for PBXNativeTarget "UIToolkitsTests" */; + buildPhases = ( + 1B7654E654AF4F2F114699E4 /* [CP] Check Pods Manifest.lock */, + 31E65AE2216BC9E1008ABEE9 /* Sources */, + 31E65AE3216BC9E1008ABEE9 /* Frameworks */, + 31E65AE4216BC9E1008ABEE9 /* Resources */, + 160966E9F3148435C3309824 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 31E65AE9216BC9E1008ABEE9 /* PBXTargetDependency */, + ); + name = UIToolkitsTests; + productName = UIToolkitsTests; + productReference = 31E65AE6216BC9E1008ABEE9 /* UIToolkitsTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 31E65AD4216BC9E1008ABEE9 /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Original; + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "dYdX Trading Inc"; + TargetAttributes = { + 310C3A0A21D9623E0081E56D = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1020; + }; + 310C3A1221D9623F0081E56D = { + CreatedOnToolsVersion = 10.1; + }; + 313EB85C21BB730700BEF926 = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1010; + }; + 313EBB0321BB79AD00BEF926 = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1010; + }; + 313EBB0B21BB79AD00BEF926 = { + CreatedOnToolsVersion = 10.1; + }; + 31E65ADC216BC9E1008ABEE9 = { + CreatedOnToolsVersion = 10.0; + LastSwiftMigration = 1300; + }; + 31E65AE5216BC9E1008ABEE9 = { + CreatedOnToolsVersion = 10.0; + }; + }; + }; + buildConfigurationList = 31E65AD7216BC9E1008ABEE9 /* Build configuration list for PBXProject "UIToolkits" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 31E65AD3216BC9E1008ABEE9; + productRefGroup = 31E65ADE216BC9E1008ABEE9 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 311D6F372176EF2A00655040 /* Products */; + ProjectRef = 311D6F362176EF2A00655040 /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 31E65ADC216BC9E1008ABEE9 /* UIToolkits */, + 313EB85C21BB730700BEF926 /* UIToolkitsAppleWatch */, + 313EBB0321BB79AD00BEF926 /* UIToolkitsAppleTV */, + 310C3A0A21D9623E0081E56D /* UIAppToolkits */, + 31E65AE5216BC9E1008ABEE9 /* UIToolkitsTests */, + 313EBB0B21BB79AD00BEF926 /* UIToolkitsAppleTVTests */, + 310C3A1221D9623F0081E56D /* UIAppToolkitsTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 311D6F3C2176EF2A00655040 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 311D6F3B2176EF2A00655040 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 311D6F3E2176EF2A00655040 /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 311D6F3D2176EF2A00655040 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB77721BB4DC000BEF926 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 313EB77621BB4DC000BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB77921BB4DC000BEF926 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 313EB77821BB4DC000BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 313EB77D21BB4DC000BEF926 /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 313EB77C21BB4DC000BEF926 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 310C3A0921D9623E0081E56D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310C3A1121D9623F0081E56D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB85B21BB730700BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB0221BB79AD00BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB0A21BB79AD00BEF926 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65ADB216BC9E1008ABEE9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AE4216BC9E1008ABEE9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 160966E9F3148435C3309824 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-UIToolkitsTests/Pods-iOS-UIToolkitsTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-UIToolkitsTests/Pods-iOS-UIToolkitsTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-UIToolkitsTests/Pods-iOS-UIToolkitsTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 1B7654E654AF4F2F114699E4 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-UIToolkitsTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 1F2AE7E1622345ACAE6D26EA /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-UIAppToolkits-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 2CF2E8843AEEAE9D7E3280E5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-UIToolkits-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 6335DE5A0A70EB35008676A7 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-UIAppToolkitsTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B35B36B7F40D2535C98315E7 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-UIAppToolkitsTests/Pods-iOS-UIAppToolkitsTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-UIAppToolkitsTests/Pods-iOS-UIAppToolkitsTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-UIAppToolkitsTests/Pods-iOS-UIAppToolkitsTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 310C3A0721D9623E0081E56D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B673A23DCE28B00139EB3 /* UIKitAppViewControllerStack.swift in Sources */, + 31DEFEB227754929009BEBF6 /* EmailExporter.swift in Sources */, + 319BB74924292A6A00DC9E97 /* FloatingManager.swift in Sources */, + 314B674023DCE35800139EB3 /* UIViewController+App.swift in Sources */, + 31630676275A9DFC00E420C6 /* UIViewController+Half.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 310C3A0F21D9623F0081E56D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 310C3A1921D9623F0081E56D /* UIAppToolkitsTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EB85921BB730700BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31308B4924FAEC58003B5B9A /* String+Styles.swift in Sources */, + 313EBF3421BC4E4300BEF926 /* WKInterfaceImage+ImageUrl.swift in Sources */, + 31308B4524FAEC34003B5B9A /* ColorPalette.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB0021BB79AD00BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 317F1BC32572E18900D178B8 /* NoteViewController.swift in Sources */, + 314B637523DCCEE500139EB3 /* UIViewController+Helper.swift in Sources */, + 31308B5324FAECED003B5B9A /* OutlineLabel.swift in Sources */, + 31308B5024FAECCE003B5B9A /* UIViewController+Search.swift in Sources */, + 314B636323DCCEE500139EB3 /* CollectionViewGridLayout.swift in Sources */, + 314B636123DCCEE500139EB3 /* ImageCollectionViewCell.swift in Sources */, + 314B632F23DCCEE500139EB3 /* UIView+Border.swift in Sources */, + 314B631923DCCEE500139EB3 /* UIButton+Layout.swift in Sources */, + 31471F7E24BA305300057221 /* ScannerProtocol.swift in Sources */, + 314B631723DCCEE500139EB3 /* UIImageView+Tint.swift in Sources */, + 314B633123DCCEE500139EB3 /* UIAlertController+Text.swift in Sources */, + 314B632323DCCEE500139EB3 /* UIDevice+Utils.swift in Sources */, + 314B631523DCCEE500139EB3 /* Debouncer+View.swift in Sources */, + 31308B4624FAEC34003B5B9A /* ColorPalette.swift in Sources */, + 31308B4D24FAECAF003B5B9A /* UIView+Color.swift in Sources */, + 317F16EF2572D24600D178B8 /* TextTableViewCell.swift in Sources */, + 31471F7624BA300D00057221 /* OCRScanner.swift in Sources */, + 314B637723DCCEE500139EB3 /* LoadingNavigationController.swift in Sources */, + 314B633B23DCCEE500139EB3 /* UINavigationController+Load.swift in Sources */, + 314B635723DCCEE500139EB3 /* DrawingView.swift in Sources */, + 314B631123DCCEE500139EB3 /* UIProtocols.swift in Sources */, + 317F16FB2572D29400D178B8 /* ImageTableViewCell.swift in Sources */, + 314B632723DCCEE500139EB3 /* UIViewController+Navigation.swift in Sources */, + 314B637923DCCEE500139EB3 /* UXNavigationController.swift in Sources */, + 314B630D23DCCEE500139EB3 /* OCRService.swift in Sources */, + 3145CDF825F40ED600BCCFCA /* NSLayoutConstraint+Changes.swift in Sources */, + 314B636F23DCCEE500139EB3 /* MKCoordinateRegion+Corners.swift in Sources */, + 314B635F23DCCEE500139EB3 /* UXLabel.swift in Sources */, + 314B636D23DCCEE500139EB3 /* UIKit+Localization.swift in Sources */, + 31308B4A24FAEC58003B5B9A /* String+Styles.swift in Sources */, + 314B638123DCCEE500139EB3 /* UIKitAppViewControllerStack.swift in Sources */, + 314B636923DCCEE500139EB3 /* UserInteraction.swift in Sources */, + 314B632923DCCEE500139EB3 /* UIKit+Protocols.swift in Sources */, + 314B635123DCCEE500139EB3 /* UXTableView.swift in Sources */, + 314B636523DCCEE500139EB3 /* SelfSizingTableView.swift in Sources */, + 314B633723DCCEE500139EB3 /* UINavigationBar+Transparent.swift in Sources */, + 314B631F23DCCEE500139EB3 /* UIView+Hierarchy.swift in Sources */, + 314B636B23DCCEE500139EB3 /* LabelView.swift in Sources */, + 314B631D23DCCEE500139EB3 /* UIFont+Style.swift in Sources */, + 314C359A24C7C0E200695F7E /* UIImage+Loading.swift in Sources */, + 314B635923DCCEE500139EB3 /* CachedImageView.swift in Sources */, + 314B635323DCCEE500139EB3 /* TextCollectionViewCell.swift in Sources */, + 314B633523DCCEE500139EB3 /* UIView+Transitions.swift in Sources */, + 314B635523DCCEE500139EB3 /* SelfSizingCollectionView.swift in Sources */, + 314B634F23DCCEE500139EB3 /* UISearchBar+Icons.swift in Sources */, + 314B636723DCCEE500139EB3 /* UXSearchBar.swift in Sources */, + 31471F7F24BA305600057221 /* Scanners.swift in Sources */, + 317F170C2572D2C800D178B8 /* ImageAddCollectionViewCell.swift in Sources */, + 314B634323DCCEE500139EB3 /* UIViewControllerProtocols.swift in Sources */, + 314B631323DCCEE500139EB3 /* UIBarButtonItem+Target.swift in Sources */, + 314B634723DCCEE500139EB3 /* LoadingIndicatorProtocol.swift in Sources */, + 31471F7D24BA304E00057221 /* CompositeScanner.swift in Sources */, + 314B632123DCCEE500139EB3 /* UIImage+Tint.swift in Sources */, + 314B637123DCCEE500139EB3 /* ViewControllerStack.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313EBB0821BB79AD00BEF926 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 313EBB1221BB79AD00BEF926 /* UIToolkitsAppleTVTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AD9216BC9E1008ABEE9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B637423DCCEE500139EB3 /* UIViewController+Helper.swift in Sources */, + 314B636223DCCEE500139EB3 /* CollectionViewGridLayout.swift in Sources */, + 314B636023DCCEE500139EB3 /* ImageCollectionViewCell.swift in Sources */, + 31CB239B26D6C3DA00A0D66D /* SegmentCollectionViewCell.swift in Sources */, + 314B632E23DCCEE500139EB3 /* UIView+Border.swift in Sources */, + 31BD72FD276BC0FF00302F75 /* StackViewSegmentedControl.swift in Sources */, + 31308B5224FAECED003B5B9A /* OutlineLabel.swift in Sources */, + 314B631823DCCEE500139EB3 /* UIButton+Layout.swift in Sources */, + 318A5C05272DC9FA000DA46C /* MotionHapticFeedback.swift in Sources */, + 3197D91B26F79CE400693D53 /* UITabbar+Transparent.swift in Sources */, + 314D9C082485A88000582A1A /* AlertPrompter.swift in Sources */, + 314B631623DCCEE500139EB3 /* UIImageView+Tint.swift in Sources */, + 642BB64927826E2600DE74CC /* ContextMenuViewController+Customize.swift in Sources */, + 314B633023DCCEE500139EB3 /* UIAlertController+Text.swift in Sources */, + 3131853C24CE6B75005AC9CE /* OverlayView.swift in Sources */, + 314D9C0B2485A8C100582A1A /* ScannerProtocol.swift in Sources */, + 314B632223DCCEE500139EB3 /* UIDevice+Utils.swift in Sources */, + 318A084524DF1A8400981B5F /* UIActivityIndicatorView+Waiting.swift in Sources */, + 31471F7424BA300D00057221 /* OCRScanner.swift in Sources */, + 314B631423DCCEE500139EB3 /* Debouncer+View.swift in Sources */, + 31432B9C2760136F00213970 /* AlwaysVisibleScrollView.swift in Sources */, + 311BB0FA2550CA2A004331D8 /* UISearchPresenter.swift in Sources */, + 31BD72FF276BC13700302F75 /* CustomSegmentedControl.swift in Sources */, + 314B637623DCCEE500139EB3 /* LoadingNavigationController.swift in Sources */, + 314B633A23DCCEE500139EB3 /* UINavigationController+Load.swift in Sources */, + 31A7B1D227404C830089C713 /* TappableLabel.swift in Sources */, + 314D9C042485A85800582A1A /* UIKitPrompterFactory.swift in Sources */, + 310A154127627FA00059812C /* UIView+Tap.swift in Sources */, + 314B637E23DCCEE500139EB3 /* LoadingProgressView.swift in Sources */, + 3145CDF725F40ED600BCCFCA /* NSLayoutConstraint+Changes.swift in Sources */, + 31471F8324BA311E00057221 /* OverlayProtocol.swift in Sources */, + 314B635623DCCEE500139EB3 /* DrawingView.swift in Sources */, + 314B631023DCCEE500139EB3 /* UIProtocols.swift in Sources */, + 310A153D27627E100059812C /* UIGestureRecognizer+Closure.swift in Sources */, + 314B632623DCCEE500139EB3 /* UIViewController+Navigation.swift in Sources */, + 3197DA1526F8FDFD00693D53 /* CircularProgressBar.swift in Sources */, + 314B637823DCCEE500139EB3 /* UXNavigationController.swift in Sources */, + 314B630C23DCCEE500139EB3 /* OCRService.swift in Sources */, + 3151A3AD26B1F41E00D34363 /* UXSegmentedControl.swift in Sources */, + 31B5F2FC255A0798003CD590 /* ThinlineView.swift in Sources */, + 314B636E23DCCEE500139EB3 /* MKCoordinateRegion+Corners.swift in Sources */, + 0295391229FB209F009026E3 /* UIImage+Gradient.swift in Sources */, + 314C359924C7C0E200695F7E /* UIImage+Loading.swift in Sources */, + 314B635E23DCCEE500139EB3 /* UXLabel.swift in Sources */, + 314B632A23DCCEE500139EB3 /* UIViewController+Embed.swift in Sources */, + 31471F8524BA313A00057221 /* PreviewOverlay.swift in Sources */, + 318A5C00272DC4A6000DA46C /* TabSegmentCollectionViewCell.swift in Sources */, + 314B636C23DCCEE500139EB3 /* UIKit+Localization.swift in Sources */, + 312FF056272874D200D35CEE /* SVGCache.swift in Sources */, + 314B673E23DCE2E200139EB3 /* KeyboardAdjustingProtocol.swift in Sources */, + 314D9C0D2485A8D300582A1A /* Scanners.swift in Sources */, + 314B636823DCCEE500139EB3 /* UserInteraction.swift in Sources */, + 314B632823DCCEE500139EB3 /* UIKit+Protocols.swift in Sources */, + 314B635023DCCEE500139EB3 /* UXTableView.swift in Sources */, + 314D9C022485A83F00582A1A /* TextEntryPrompter.swift in Sources */, + 31A2597926E67ADB00FB7CFD /* GradientView.swift in Sources */, + 314B636423DCCEE500139EB3 /* SelfSizingTableView.swift in Sources */, + 314B633623DCCEE500139EB3 /* UINavigationBar+Transparent.swift in Sources */, + 314D9C122485A91D00582A1A /* QRCode.swift in Sources */, + 31308B4F24FAECCE003B5B9A /* UIViewController+Search.swift in Sources */, + 314B631E23DCCEE500139EB3 /* UIView+Hierarchy.swift in Sources */, + 314B633C23DCCEE500139EB3 /* ReachabilityMessage.swift in Sources */, + 317F1BC22572E18900D178B8 /* NoteViewController.swift in Sources */, + 314B636A23DCCEE500139EB3 /* LabelView.swift in Sources */, + 314B637223DCCEE500139EB3 /* KeyboardAdjustingViewController.swift in Sources */, + 316BAE0F26FA8A56002CDC03 /* UITextField+Done.swift in Sources */, + 314B631C23DCCEE500139EB3 /* UIFont+Style.swift in Sources */, + 318A084924DF1AA100981B5F /* NVActivityIndicatorView+Waiting.swift in Sources */, + 314B632C23DCCEE500139EB3 /* HMSegmentedControl+SegmentedProtocol.swift in Sources */, + 317F16EE2572D24600D178B8 /* TextTableViewCell.swift in Sources */, + 31308B4424FAEC34003B5B9A /* ColorPalette.swift in Sources */, + 314B635823DCCEE500139EB3 /* CachedImageView.swift in Sources */, + 31C016EF2737266B004B300E /* SpinImageView.swift in Sources */, + 31471F8724BA315200057221 /* RectPreviewOverlay.swift in Sources */, + 64CB23B027986FA6009A5062 /* TooltipManager.swift in Sources */, + 317F170B2572D2C800D178B8 /* ImageAddCollectionViewCell.swift in Sources */, + 31762168245B307200C9F2FB /* FloatingLayoutProviderProtocol.swift in Sources */, + 310CC22D26A7388D00046434 /* ButtonView.swift in Sources */, + 314D9C0F2485A8EE00582A1A /* CompositeScanner.swift in Sources */, + 314B635223DCCEE500139EB3 /* TextCollectionViewCell.swift in Sources */, + 314B633423DCCEE500139EB3 /* UIView+Transitions.swift in Sources */, + 314B635423DCCEE500139EB3 /* SelfSizingCollectionView.swift in Sources */, + 31A7B178274040350089C713 /* OnOffSwitch.swift in Sources */, + 314B634E23DCCEE500139EB3 /* UISearchBar+Icons.swift in Sources */, + 314B636623DCCEE500139EB3 /* UXSearchBar.swift in Sources */, + 31F3D45E2719EC500007602A /* GradientSlider.swift in Sources */, + 314B634223DCCEE500139EB3 /* UIViewControllerProtocols.swift in Sources */, + 31308B4C24FAECAF003B5B9A /* UIView+Color.swift in Sources */, + 314B631223DCCEE500139EB3 /* UIBarButtonItem+Target.swift in Sources */, + 317F16FA2572D29400D178B8 /* ImageTableViewCell.swift in Sources */, + 31A0373B26F40F93007EFC2F /* UXImageView.swift in Sources */, + 31CB239726D6BF2F00A0D66D /* CollectionViewSegmentedControl.swift in Sources */, + 31308B4824FAEC58003B5B9A /* String+Styles.swift in Sources */, + 314B634623DCCEE500139EB3 /* LoadingIndicatorProtocol.swift in Sources */, + 3192C86826FA72DD0072BD99 /* CircularProgressView.swift in Sources */, + 314B632023DCCEE500139EB3 /* UIImage+Tint.swift in Sources */, + 31471F8124BA310100057221 /* CirclePreviewOverlay.swift in Sources */, + 319BB74A24292AE200DC9E97 /* UIResponder+First.swift in Sources */, + 314B637023DCCEE500139EB3 /* ViewControllerStack.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AE2216BC9E1008ABEE9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31E65AEC216BC9E1008ABEE9 /* UIToolkitsTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 310C3A1621D9623F0081E56D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 310C3A0A21D9623E0081E56D /* UIAppToolkits */; + targetProxy = 310C3A1521D9623F0081E56D /* PBXContainerItemProxy */; + }; + 310C3B0B21D9C8F20081E56D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31E65ADC216BC9E1008ABEE9 /* UIToolkits */; + targetProxy = 310C3B0A21D9C8F20081E56D /* PBXContainerItemProxy */; + }; + 310C3B0D21D9C8F20081E56D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 310C3B0C21D9C8F20081E56D /* PBXContainerItemProxy */; + }; + 311D6F402176EF3700655040 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 311D6F3F2176EF3700655040 /* PBXContainerItemProxy */; + }; + 313EB86621BB731800BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleWatch; + targetProxy = 313EB86521BB731800BEF926 /* PBXContainerItemProxy */; + }; + 313EBB0F21BB79AD00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 313EBB0321BB79AD00BEF926 /* UIToolkitsAppleTV */; + targetProxy = 313EBB0E21BB79AD00BEF926 /* PBXContainerItemProxy */; + }; + 313EBB3821BB79CF00BEF926 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UtilitiesAppleTV; + targetProxy = 313EBB3721BB79CF00BEF926 /* PBXContainerItemProxy */; + }; + 31E65AE9216BC9E1008ABEE9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31E65ADC216BC9E1008ABEE9 /* UIToolkits */; + targetProxy = 31E65AE8216BC9E1008ABEE9 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 310C3A1C21D9623F0081E56D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D1BCBE47DC02A75CC9C0122D /* Pods-iOS-UIAppToolkits.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = UIAppToolkits/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UIAppToolkits; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 310C3A1D21D9623F0081E56D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 36FDC57AAF354EB0B5E2220B /* Pods-iOS-UIAppToolkits.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = UIAppToolkits/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UIAppToolkits; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 310C3A1E21D9623F0081E56D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 946EEAA78022B56C6B378E35 /* Pods-iOS-UIAppToolkitsTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = UIAppToolkitsTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UIAppToolkitsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 310C3A1F21D9623F0081E56D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0F319D9DE3EF6DAF915D127C /* Pods-iOS-UIAppToolkitsTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = UIAppToolkitsTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UIAppToolkitsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 313EB86321BB730700BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = UIToolkitsAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UIToolkitsAppleWatch; + PRODUCT_NAME = UIToolkits; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _watchOS"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Debug; + }; + 313EB86421BB730700BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = UIToolkitsAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UIToolkitsAppleWatch; + PRODUCT_NAME = UIToolkits; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _watchOS"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 5.1; + }; + name = Release; + }; + 313EBB1621BB79AD00BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = UIToolkitsAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UIToolkitsAppleTV; + PRODUCT_NAME = UIToolkits; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _tvOS"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313EBB1721BB79AD00BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = UIToolkitsAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UIToolkitsAppleTV; + PRODUCT_NAME = UIToolkits; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _tvOS"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 313EBB1921BB79AD00BEF926 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = UIToolkitsAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UIToolkitsAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313EBB1A21BB79AD00BEF926 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = UIToolkitsAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UIToolkitsAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 31E65AEF216BC9E1008ABEE9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 31E65AF0216BC9E1008ABEE9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 31E65AF2216BC9E1008ABEE9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D4CB0CEDE0DA8FAF68D8464A /* Pods-iOS-UIToolkits.debug.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = UIToolkits/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.UIToolkits; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _iOS"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31E65AF3216BC9E1008ABEE9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9D5FF1D5D7AC3E369806232C /* Pods-iOS-UIToolkits.release.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = UIToolkits/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.UIToolkits; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _iOS"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 31E65AF5216BC9E1008ABEE9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 668A73DB8721B9FD70685E16 /* Pods-iOS-UIToolkitsTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = UIToolkitsTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UIToolkitsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31E65AF6216BC9E1008ABEE9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 17B0FA87BC34A4B27C1C6700 /* Pods-iOS-UIToolkitsTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = UIToolkitsTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UIToolkitsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 310C3A2321D9623F0081E56D /* Build configuration list for PBXNativeTarget "UIAppToolkits" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 310C3A1C21D9623F0081E56D /* Debug */, + 310C3A1D21D9623F0081E56D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 310C3A2421D9623F0081E56D /* Build configuration list for PBXNativeTarget "UIAppToolkitsTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 310C3A1E21D9623F0081E56D /* Debug */, + 310C3A1F21D9623F0081E56D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313EB86221BB730700BEF926 /* Build configuration list for PBXNativeTarget "UIToolkitsAppleWatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EB86321BB730700BEF926 /* Debug */, + 313EB86421BB730700BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313EBB1521BB79AD00BEF926 /* Build configuration list for PBXNativeTarget "UIToolkitsAppleTV" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EBB1621BB79AD00BEF926 /* Debug */, + 313EBB1721BB79AD00BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313EBB1821BB79AD00BEF926 /* Build configuration list for PBXNativeTarget "UIToolkitsAppleTVTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313EBB1921BB79AD00BEF926 /* Debug */, + 313EBB1A21BB79AD00BEF926 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31E65AD7216BC9E1008ABEE9 /* Build configuration list for PBXProject "UIToolkits" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31E65AEF216BC9E1008ABEE9 /* Debug */, + 31E65AF0216BC9E1008ABEE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31E65AF1216BC9E1008ABEE9 /* Build configuration list for PBXNativeTarget "UIToolkits" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31E65AF2216BC9E1008ABEE9 /* Debug */, + 31E65AF3216BC9E1008ABEE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31E65AF4216BC9E1008ABEE9 /* Build configuration list for PBXNativeTarget "UIToolkitsTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31E65AF5216BC9E1008ABEE9 /* Debug */, + 31E65AF6216BC9E1008ABEE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 31E65AD4216BC9E1008ABEE9 /* Project object */; +} diff --git a/UIToolkits/UIToolkits.xcodeproj/xcshareddata/xcschemes/UIAppToolkits.xcscheme b/UIToolkits/UIToolkits.xcodeproj/xcshareddata/xcschemes/UIAppToolkits.xcscheme new file mode 100644 index 000000000..2a13b9f35 --- /dev/null +++ b/UIToolkits/UIToolkits.xcodeproj/xcshareddata/xcschemes/UIAppToolkits.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UIToolkits/UIToolkits.xcodeproj/xcshareddata/xcschemes/UIAppToolkitsTests.xcscheme b/UIToolkits/UIToolkits.xcodeproj/xcshareddata/xcschemes/UIAppToolkitsTests.xcscheme new file mode 100644 index 000000000..44515dc78 --- /dev/null +++ b/UIToolkits/UIToolkits.xcodeproj/xcshareddata/xcschemes/UIAppToolkitsTests.xcscheme @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/UIToolkits/UIToolkits.xcodeproj/xcshareddata/xcschemes/UIToolkits.xcscheme b/UIToolkits/UIToolkits.xcodeproj/xcshareddata/xcschemes/UIToolkits.xcscheme new file mode 100644 index 000000000..2a2b196b0 --- /dev/null +++ b/UIToolkits/UIToolkits.xcodeproj/xcshareddata/xcschemes/UIToolkits.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UIToolkits/UIToolkits.xcodeproj/xcshareddata/xcschemes/UIToolkitsTests.xcscheme b/UIToolkits/UIToolkits.xcodeproj/xcshareddata/xcschemes/UIToolkitsTests.xcscheme new file mode 100644 index 000000000..d54e728d5 --- /dev/null +++ b/UIToolkits/UIToolkits.xcodeproj/xcshareddata/xcschemes/UIToolkitsTests.xcscheme @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/UIToolkits/UIToolkits/Info.plist b/UIToolkits/UIToolkits/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/UIToolkits/UIToolkits/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/UIToolkits/UIToolkits/UIToolkits.h b/UIToolkits/UIToolkits/UIToolkits.h new file mode 100644 index 000000000..c6c7a5440 --- /dev/null +++ b/UIToolkits/UIToolkits/UIToolkits.h @@ -0,0 +1,17 @@ +// +// UIToolkits.h +// UIToolkits +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for UIToolkits. +FOUNDATION_EXPORT double UIToolkitsVersionNumber; + +//! Project version string for UIToolkits. +FOUNDATION_EXPORT const unsigned char UIToolkitsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/UIToolkits/UIToolkits/_App/LoadingProgressView.swift b/UIToolkits/UIToolkits/_App/LoadingProgressView.swift new file mode 100644 index 000000000..3b765f5c1 --- /dev/null +++ b/UIToolkits/UIToolkits/_App/LoadingProgressView.swift @@ -0,0 +1,50 @@ +// +// LoadingProgressView.swift +// UIToolkits +// +// Created by Qiang Huang on 11/24/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import MaterialActivityIndicator +import UIKit +import Utilities + +open class LoadingProgressView: UIView, LoadingIndicatorProtocol { + @IBOutlet var indicator: MaterialActivityIndicatorView? + + private var animating: Bool = false { + didSet { + if animating != oldValue { + if animating { + indicator?.startAnimating() + } else { + indicator?.stopAnimating() + } + } + } + } + + public var status: LoadingStatusProtocol? { + didSet { + changeObservation(from: oldValue, to: status, keyPath: #keyPath(LoadingStatusProtocol.running)) { [weak self] _, _, _, _ in + self?.update() + } + } + } + + open override func awakeFromNib() { + super.awakeFromNib() + status = LoadingStatus.shared + } + + open func update() { + if status?.running ?? false { + animating = true + } else { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + self?.animating = self?.status?.running ?? false + } + } + } +} diff --git a/UIToolkits/UIToolkits/_App/UIKitAppViewControllerStack.swift b/UIToolkits/UIToolkits/_App/UIKitAppViewControllerStack.swift new file mode 100644 index 000000000..410599950 --- /dev/null +++ b/UIToolkits/UIToolkits/_App/UIKitAppViewControllerStack.swift @@ -0,0 +1,37 @@ +// +// UIKitAppViewControllerStack.swift +// UIAppToolkits +// +// Created by Qiang Huang on 12/30/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#if _tvOS + import UIKit +#else + import UIToolkits +#endif + +public extension UIViewController { + static func root() -> UIViewController? { + return UIApplication.shared.windows.first?.rootViewController + } + + static func topmost() -> UIViewController? { + return root()?.topmost() + } +} + +public class UIKitAppViewControllerStack: NSObject, ViewControllerStackProtocol { + public func didShow(viewController: UIViewController) { + // Do nothing + } + + public func root() -> UIViewController? { + return UIViewController.root() + } + + public func topmost() -> UIViewController? { + return UIViewController.topmost() + } +} diff --git a/UIToolkits/UIToolkits/_Camera/CirclePreviewOverlay.swift b/UIToolkits/UIToolkits/_Camera/CirclePreviewOverlay.swift new file mode 100644 index 000000000..e2cd9303c --- /dev/null +++ b/UIToolkits/UIToolkits/_Camera/CirclePreviewOverlay.swift @@ -0,0 +1,28 @@ +// +// CirclePreviewOverlay.swift +// UIToolkits +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +open class CirclePreviewOverlay: PreviewOverlay { + open var margin: CGFloat = 0.1 + + open override func path(rect: CGRect) -> UIBezierPath? { + var width = rect.width * (1.0 - margin * 2) + var height = rect.height * (1.0 - margin * 2) + // aspectRatio = width / height + if width > height { + width = height + } else { + height = width + } + let circleRect = CGRect(x: (rect.width - width) / 2.0, y: (rect.height - height) / 2.0, width: width, height: height) + let path = UIBezierPath(rect: rect) + path.append(UIBezierPath(ovalIn: circleRect)) + return path + } +} diff --git a/UIToolkits/UIToolkits/_Camera/OverlayProtocol.swift b/UIToolkits/UIToolkits/_Camera/OverlayProtocol.swift new file mode 100644 index 000000000..28ae959d9 --- /dev/null +++ b/UIToolkits/UIToolkits/_Camera/OverlayProtocol.swift @@ -0,0 +1,13 @@ +// +// OverlayProtocol.swift +// UIToolkits +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol OverlayProtocol: NSObjectProtocol { + func layer(rect: CGRect) -> CALayer? +} diff --git a/UIToolkits/UIToolkits/_Camera/PreviewOverlay.swift b/UIToolkits/UIToolkits/_Camera/PreviewOverlay.swift new file mode 100644 index 000000000..771d13c00 --- /dev/null +++ b/UIToolkits/UIToolkits/_Camera/PreviewOverlay.swift @@ -0,0 +1,31 @@ +// +// PreviewOverlay.swift +// UIToolkits +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +@objc open class PreviewOverlay: NSObject, OverlayProtocol { + open func layer(rect: CGRect) -> CALayer? { + if let path = path(rect: rect) { + return layer(path: path) + } + return nil + } + + open func path(rect: CGRect) -> UIBezierPath? { + return nil + } + + open func layer(path: UIBezierPath) -> CALayer { + let layer = CAShapeLayer() + layer.path = path.cgPath + layer.fillRule = CAShapeLayerFillRule.evenOdd + layer.fillColor = UIColor.black.cgColor + layer.opacity = 0.5 + return layer + } +} diff --git a/UIToolkits/UIToolkits/_Camera/RectPreviewOverlay.swift b/UIToolkits/UIToolkits/_Camera/RectPreviewOverlay.swift new file mode 100644 index 000000000..1f94272bb --- /dev/null +++ b/UIToolkits/UIToolkits/_Camera/RectPreviewOverlay.swift @@ -0,0 +1,29 @@ +// +// RectPreviewOverlay.swift +// UIToolkits +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +open class RectPreviewOverlay: PreviewOverlay { + open var aspectRatio: CGFloat = 1.0 + open var margin: CGFloat = 0.1 + + open override func path(rect: CGRect) -> UIBezierPath? { + var width = rect.width * (1.0 - margin * 2) + var height = rect.height * (1.0 - margin * 2) + // aspectRatio = width / height + if width / aspectRatio > height { + width = height * aspectRatio + } else { + height = width / aspectRatio + } + let rectRect = CGRect(x: (rect.width - width) / 2.0, y: (rect.height - height) / 2.0, width: width, height: height) + let path = UIBezierPath(rect: rect) + path.append(UIBezierPath(rect: rectRect)) + return path + } +} diff --git a/UIToolkits/UIToolkits/_Color/ColorPalette.swift b/UIToolkits/UIToolkits/_Color/ColorPalette.swift new file mode 100644 index 000000000..e102dcb3a --- /dev/null +++ b/UIToolkits/UIToolkits/_Color/ColorPalette.swift @@ -0,0 +1,150 @@ +// +// ColorPalette.swift +// UIToolkits +// +// Created by Qiang Huang on 8/29/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +public final class ColorPalette: NSObject, SingletonProtocol { + public static var shared: ColorPalette = { + ColorPalette() + }() + + public static var parserOverwrite: Parser? + + override public var parser: Parser { + return ColorPalette.parserOverwrite ?? super.parser + } + + private var colors: [String: Any]? + + private var cache: [String: UIColor] = [:] + + public func color(text: String?) -> UIColor? { + if let text = text { + if let color = UIColor(hex: text) { + return color + } else { + return color(keyed: text) + } + } else { + return nil + } + } + + public func color(keyed colorText: String?) -> UIColor? { + if let colorText = colorText { + if let color = cache[colorText] { + return color + } else { + if let hex = hex(keyed: colorText) { + let color = UIColor(hex: hex) + cache[colorText] = color + return color + } else { + if let color = UIColor(hex: colorText) { + cache[colorText] = color + return color + } else { + assertionFailure("invalid color hex: \(colorText)") + let color = UIColor.red + cache[colorText] = color + return color + } + } + } + } + #if DEBUG + return UIColor.red + #else + return nil + #endif + } + + public func color(system colorText: String?) -> UIColor? { + switch colorText { + case "label": + if #available(iOS 13.0, *) { + return UIColor.label + } else { + return color(keyed: "black") + } + + case "secondary": + if #available(iOS 13.0, *) { + return UIColor.secondaryLabel + } else { + return color(keyed: "gray") + } + + case "dark": + if #available(iOS 13.0, *) { + return UIColor.systemGray2 + } else { + return color(keyed: "darkGray") + } + + case "gray": + if #available(iOS 13.0, *) { + return UIColor.systemGray4 + } else { + return color(keyed: "gray") + } + + case "superlight": + if #available(iOS 13.0, *) { + return UIColor.systemGray6 + } else { + return color(keyed: "gray0") + } + + case "light": + if #available(iOS 13.0, *) { + return UIColor.systemGray4 + } else { + return color(keyed: "gray2") + } + + case "quaternary": + if #available(iOS 13.0, *) { + return UIColor.quaternaryLabel + } else { + return color(keyed: "lightGray") + } + + case "disabled": + if #available(iOS 13.0, *) { + return UIColor.lightGray + } else { + return color(keyed: "lightGray") + } + + case "background": + if #available(iOS 13.0, *) { + return UIColor.systemBackground + } else { + return color(keyed: "white") + } + + case "clear": + return UIColor.clear + + default: + return color(keyed: colorText) + } + } + + public func hex(keyed color: String) -> String? { + return parser.asString(colors?[color]) + } + + override public init() { + super.init() + + colors = JsonLoader.load(bundles: Bundle.particles, fileName: "colors.json") as? [String: Any] + } +} diff --git a/UIToolkits/UIToolkits/_Connectivity/ReachabilityMessage.swift b/UIToolkits/UIToolkits/_Connectivity/ReachabilityMessage.swift new file mode 100644 index 000000000..24b0d0dbb --- /dev/null +++ b/UIToolkits/UIToolkits/_Connectivity/ReachabilityMessage.swift @@ -0,0 +1,34 @@ +// +// ReachabilityManager.swift +// UIToolkits +// +// Created by Qiang Huang on 5/15/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import SwiftMessages +import Utilities + +public final class ReachabilityMessage: NSObject, SingletonProtocol { + public var connectivityXib: String? + @objc var connection: NetworkConnection? { + didSet { + changeObservation(from: oldValue, to: connection, keyPath: #keyPath(NetworkConnection.connected)) { [weak self] _, _, _, _ in + self?.updateConnectivityMessage() + } + } + } + + public static var shared: ReachabilityMessage = { + let reachability = ReachabilityMessage() + reachability.connection = NetworkConnection.shared + return reachability + }() + + private func updateConnectivityMessage() { + if let connected = connection?.connected?.boolValue, !connected { + let error = NSError(domain: "reachability", code: 400, userInfo: nil) + ErrorInfo.shared?.info(title: "Network", message: "Connection not available", type: .info, error: error) + } + } +} diff --git a/UIToolkits/UIToolkits/_Export/EmailExporter.swift b/UIToolkits/UIToolkits/_Export/EmailExporter.swift new file mode 100644 index 000000000..f7b726494 --- /dev/null +++ b/UIToolkits/UIToolkits/_Export/EmailExporter.swift @@ -0,0 +1,40 @@ +// +// EmailExporter.swift +// UIToolkits +// +// Created by Qiang Huang on 12/23/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Foundation +import MessageUI +import Utilities + +public class EmailExporter: NSObject, ExporterProtocol, MFMailComposeViewControllerDelegate { + public func export(exporters: [DataExportProtocol]?) { + if MFMailComposeViewController.canSendMail(), let exporters = exporters { + let mail = MFMailComposeViewController() + mail.setSubject("dYdX data export") + mail.setMessageBody("Data attached", isHTML: true) + mail.mailComposeDelegate = self + // add attachment + for exporter in exporters { + if let fileName = exporter.fileName, let mimeType = exporter.memeType, let data = exporter.export() { + mail.addAttachmentData(data as Data, mimeType: mimeType, fileName: fileName) + } + } + + UIViewController.topmost()?.present(mail, animated: true) + } else { + ErrorInfo.shared?.info(title: "Error", message: "Please set up your email to export data", error: nil) + } + } + + public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + controller.dismiss(animated: true) { + if let error = error { + ErrorInfo.shared?.info(title: "Error", message: nil, error: error) + } + } + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/ContextMenuViewController+Customize.swift b/UIToolkits/UIToolkits/_Extensions/ContextMenuViewController+Customize.swift new file mode 100644 index 000000000..cff20d95d --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/ContextMenuViewController+Customize.swift @@ -0,0 +1,9 @@ +// +// ContextMenuViewController+Customize.swift +// UIToolkits +// +// Created by John Huang on 1/2/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +// import SwiftyContextMenu diff --git a/UIToolkits/UIToolkits/_Extensions/Debouncer+View.swift b/UIToolkits/UIToolkits/_Extensions/Debouncer+View.swift new file mode 100644 index 000000000..12f602437 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/Debouncer+View.swift @@ -0,0 +1,28 @@ +// +// Debouncer+View.swift +// UIToolkits +// +// Created by Qiang Huang on 1/19/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Utilities + +public extension Debouncer { + func layout(view: UIView?, animated: Bool, run: @escaping DebouncedFunction) { + if animated { + if let handler = debounce() { + handler.run({ + run() + handler.run({ + UIView.animate(withDuration: UIView.defaultAnimationDuration) { + view?.layoutIfNeeded() + } + }, delay: nil) + }, delay: nil) + } + } else { + run() + } + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/HMSegmentedControl+SegmentedProtocol.swift b/UIToolkits/UIToolkits/_Extensions/HMSegmentedControl+SegmentedProtocol.swift new file mode 100644 index 000000000..85d84cc22 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/HMSegmentedControl+SegmentedProtocol.swift @@ -0,0 +1,47 @@ +// +// HMSegmentedControl+SegmentedProtocol.swift +// UIToolkits +// +// Created by Qiang Huang on 12/26/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import HMSegmentedControl +import ObjectiveC + +extension HMSegmentedControl: SegmentedProtocol { + public var selectedIndex: Int { + get { + if selectedSegmentIndex == HMSegmentedControlNoSegment { + return -1 + } else { + return Int(selectedSegmentIndex) + } + } + set { + if numberOfSegments != 0 { + if newValue == -1 || newValue >= 0 && newValue < numberOfSegments { + setSelectedSegmentIndex(UInt(newValue), animated: true) + } + } + } + } + + public var numberOfSegments: Int { + return sectionTitles?.count ?? 0 + } + + @objc public func fill(titles: [String]?) { + selectedSegmentIndex = HMSegmentedControlNoSegment + sectionTitles = titles + } + + public static func segments(with titles: [String]) -> SegmentedProtocol { + let segments = HMSegmentedControl(sectionTitles: titles) + segments.selectionIndicatorLocation = .bottom + segments.selectionStyle = .fullWidthStripe + segments.selectionIndicatorHeight = 4.0 + segments.frame = CGRect(x: 0, y: 0, width: 180, height: 44) + return segments + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/NSLayoutConstraint+Changes.swift b/UIToolkits/UIToolkits/_Extensions/NSLayoutConstraint+Changes.swift new file mode 100644 index 000000000..86d0fad28 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/NSLayoutConstraint+Changes.swift @@ -0,0 +1,40 @@ +// +// NSLayoutConstraint+Changes.swift +// UIToolkits +// +// Created by Qiang Huang on 3/6/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import UIKit + +public extension NSLayoutConstraint { + /** + Change multiplier constraint + + - parameter multiplier: CGFloat + - returns: NSLayoutConstraint + */ + func set(multiplier: CGFloat) -> NSLayoutConstraint { + if let firstItem = firstItem { + NSLayoutConstraint.deactivate([self]) + + let newConstraint = NSLayoutConstraint( + item: firstItem, + attribute: firstAttribute, + relatedBy: relation, + toItem: secondItem, + attribute: secondAttribute, + multiplier: multiplier == 0 ? 0.001 : multiplier, // to get around some OS bug + constant: constant) + + newConstraint.priority = priority + newConstraint.shouldBeArchived = shouldBeArchived + newConstraint.identifier = identifier + + NSLayoutConstraint.activate([newConstraint]) + return newConstraint + } + return self + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/NVActivityIndicatorView+Waiting.swift b/UIToolkits/UIToolkits/_Extensions/NVActivityIndicatorView+Waiting.swift new file mode 100644 index 000000000..619af4c2a --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/NVActivityIndicatorView+Waiting.swift @@ -0,0 +1,26 @@ +// +// NVActivityIndicatorView+Waiting.swift +// UIToolkits +// +// Created by Qiang Huang on 8/8/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import NVActivityIndicatorView + +extension NVActivityIndicatorView: WaitProtocol { + public var waiting: Bool { + get { + return isAnimating + } + set { + if newValue != waiting { + if newValue { + startAnimating() + } else { + stopAnimating() + } + } + } + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/String+Styles.swift b/UIToolkits/UIToolkits/_Extensions/String+Styles.swift new file mode 100644 index 000000000..dd4b269d5 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/String+Styles.swift @@ -0,0 +1,34 @@ +// +// String+Styles.swift +// UIToolkits +// +// Created by Qiang Huang on 8/29/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation +import UIKit + +public extension String { + func outline(color: UIColor) -> NSAttributedString? { + let outlineTextAttributes: [NSAttributedString.Key: Any] = [ + NSAttributedString.Key.strokeColor: UIColor.white, + NSAttributedString.Key.foregroundColor: color, + NSAttributedString.Key.strokeWidth: -1.0, + ] + return NSAttributedString(string: self, attributes: outlineTextAttributes) + } +} + +extension String { + public func htmlAttributedString(font: UIFont, color: UIColor?) -> NSAttributedString? { + guard let data = self.data(using: .utf16) else { + return nil + } + return try? NSAttributedString( + data: data, + options: [.documentType: NSAttributedString.DocumentType.html], + documentAttributes: nil + ) + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIActivityIndicatorView+Waiting.swift b/UIToolkits/UIToolkits/_Extensions/UIActivityIndicatorView+Waiting.swift new file mode 100644 index 000000000..3b58f7145 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIActivityIndicatorView+Waiting.swift @@ -0,0 +1,26 @@ +// +// UIActivityIndicatorView+Waiting.swift +// UIToolkits +// +// Created by Qiang Huang on 8/8/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit + +extension UIActivityIndicatorView: WaitProtocol { + public var waiting: Bool { + get { + return isAnimating + } + set { + if newValue != waiting { + if newValue { + startAnimating() + } else { + stopAnimating() + } + } + } + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIAlertController+Text.swift b/UIToolkits/UIToolkits/_Extensions/UIAlertController+Text.swift new file mode 100644 index 000000000..6cf01be10 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIAlertController+Text.swift @@ -0,0 +1,38 @@ +// +// UIAlertViewController+Text.swift +// UIToolkits +// +// Created by Qiang Huang on 10/30/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIKit + +public typealias TextInputFunction = (_ text: String?, _ ok: Bool) -> Void + +public extension UIAlertController { + static func prompt(title: String? = nil, message: String? = nil, text: String? = nil, placeholder: String? = nil, completion: @escaping TextInputFunction) { + var textField: UITextField? + + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addTextField { pTextField in + pTextField.text = text + pTextField.placeholder = placeholder + pTextField.clearButtonMode = .whileEditing + pTextField.borderStyle = .none + textField = pTextField + } + + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in + alertController.dismiss(animated: true, completion: nil) + completion(nil, false) + })) + + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in + completion(textField?.text, true) + alertController.dismiss(animated: true, completion: nil) + })) + + ViewControllerStack.shared?.topmost()?.present(alertController, animated: true, completion: nil) + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIBarButtonItem+Target.swift b/UIToolkits/UIToolkits/_Extensions/UIBarButtonItem+Target.swift new file mode 100644 index 000000000..b5c7a8d8d --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIBarButtonItem+Target.swift @@ -0,0 +1,16 @@ +// +// UIBarButtonItem+Target.swift +// UIToolkits +// +// Created by Qiang Huang on 11/15/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIKit + +public extension UIBarButtonItem { + func set(target: AnyObject?, action: Selector?) { + self.target = target + self.action = action + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIButton+Layout.swift b/UIToolkits/UIToolkits/_Extensions/UIButton+Layout.swift new file mode 100644 index 000000000..af864c167 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIButton+Layout.swift @@ -0,0 +1,65 @@ +// +// UIButton+Layout.swift +// UIToolkits +// +// Created by Qiang Huang on 11/23/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIKit + +public extension UIButton { + func centerImageAndButton(_ gap: CGFloat, imageOnTop: Bool) { + guard let imageView = currentImage, + let titleLabel = self.titleLabel?.text else { return } + + let sign: CGFloat = imageOnTop ? 1 : -1 + titleEdgeInsets = UIEdgeInsets(top: (imageView.size.height + gap) * sign, left: -imageView.size.width, bottom: 0, right: 0) + + let titleSize = titleLabel.size(withAttributes: [NSAttributedString.Key.font: self.titleLabel!.font!]) + imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + gap) * sign, left: 0, bottom: 0, right: -titleSize.width) + } + + func setInsets(forContentPadding contentPadding: UIEdgeInsets, imageTitlePadding: CGFloat) { + contentEdgeInsets = UIEdgeInsets(top: contentPadding.top, left: contentPadding.left, bottom: contentPadding.bottom, right: contentPadding.right + imageTitlePadding) + titleEdgeInsets = UIEdgeInsets(top: 0, left: imageTitlePadding, bottom: 0, right: -imageTitlePadding) + } + + func setImages(right: UIImage? = nil, left: UIImage? = nil) { + if let leftImage = left, right == nil { + setImage(leftImage, for: .normal) + imageEdgeInsets = UIEdgeInsets(top: 5, left: bounds.width - 35, bottom: 5, right: 5) + titleEdgeInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: (imageView?.frame.width)!) + contentHorizontalAlignment = .left + } + if let rightImage = right, left == nil { + setImage(rightImage, for: .normal) + imageEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: bounds.width - 35) + titleEdgeInsets = UIEdgeInsets(top: 0, left: (imageView?.frame.width)!, bottom: 0, right: 16) + contentHorizontalAlignment = .right + } + + if let rightImage = right, let leftImage = left { + setImage(rightImage, for: .normal) + imageEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: bounds.width - 35) + titleEdgeInsets = UIEdgeInsets(top: 0, left: (imageView?.frame.width)!, bottom: 0, right: 16) + contentHorizontalAlignment = .left + + let leftImageView = UIImageView(frame: CGRect(x: bounds.maxX - 30, + y: (titleLabel?.bounds.midY)! - 5, + width: 20, + height: frame.height - 16)) + leftImageView.image?.withRenderingMode(.alwaysOriginal) + leftImageView.image = leftImage + leftImageView.contentMode = .scaleAspectFit + leftImageView.layer.masksToBounds = true + addSubview(leftImageView) + } + } + + func imageToRight() { + transform = CGAffineTransform(scaleX: -1.0, y: 1.0) + titleLabel?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0) + imageView?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0) + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIDevice+Utils.swift b/UIToolkits/UIToolkits/_Extensions/UIDevice+Utils.swift new file mode 100644 index 000000000..159e60300 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIDevice+Utils.swift @@ -0,0 +1,46 @@ +// +// UIDevice+Utils.swift +// UIToolkits +// +// Created by Qiang Huang on 12/16/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIKit + +extension UIDevice { + public var systemVersionAsFloat: Double { + let version = systemVersion + let versions = version.split(separator: ".") + var versionAsFloat: Double = 0 + for i in 0 ..< versions.count { + let element = parser.asNumber(versions[i])?.doubleValue ?? 0 + switch i { + case 0: + versionAsFloat = element + + case 1: + versionAsFloat += element / 100.0 + + case 2: + versionAsFloat += element / 10000.0 + + default: + break + } + } + return versionAsFloat + } + + public var canSplit: Bool { + return userInterfaceIdiom == .pad || userInterfaceIdiom == .tv + } + + public var isSimulator: Bool { + #if targetEnvironment(simulator) + return true + #else + return false + #endif + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIFont+Style.swift b/UIToolkits/UIToolkits/_Extensions/UIFont+Style.swift new file mode 100644 index 000000000..03907c050 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIFont+Style.swift @@ -0,0 +1,28 @@ +// +// UIFont+Style.swift +// UIToolkits +// +// Created by Qiang Huang on 11/13/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIKit + +extension UIFont { + public func withTraits(traits: UIFontDescriptor.SymbolicTraits) -> UIFont { + let descriptor = fontDescriptor.withSymbolicTraits(traits) + return UIFont(descriptor: descriptor!, size: 0) // size 0 means keep the size as it is + } + + public func bold() -> UIFont { + return withTraits(traits: .traitBold) + } + + public func italic() -> UIFont { + return withTraits(traits: .traitItalic) + } + + public func normal() -> UIFont { + return withTraits(traits: .traitUIOptimized) + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIGestureRecognizer+Closure.swift b/UIToolkits/UIToolkits/_Extensions/UIGestureRecognizer+Closure.swift new file mode 100644 index 000000000..3bc15f846 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIGestureRecognizer+Closure.swift @@ -0,0 +1,36 @@ +// +// UIGestureRecognizer+Closure.swift +// UIToolkits +// +// Created by Qiang Huang on 12/9/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Utilities + +extension UIGestureRecognizer { + public typealias Action = ((UIGestureRecognizer) -> Void) + + private struct Keys { + static var actionKey = "ActionKey" + } + + private var block: Action? { + get { + return associatedObject(base: self, key: &Keys.actionKey) + } + set { + retainObject(base: self, key: &Keys.actionKey, value: newValue) + } + } + + @objc func handleAction(recognizer: UIGestureRecognizer) { + block?(recognizer) + } + + public convenience init(block: @escaping ((UIGestureRecognizer) -> Void)) { + self.init() + self.block = block + addTarget(self, action: #selector(handleAction(recognizer:))) + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIImage+Gradient.swift b/UIToolkits/UIToolkits/_Extensions/UIImage+Gradient.swift new file mode 100644 index 000000000..f20149859 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIImage+Gradient.swift @@ -0,0 +1,97 @@ +// +// UIImage+Gradient.swift +// UIToolkits +// +// Created by Rui Huang on 4/27/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation +import UIKit + +private let ChannelDivider: CGFloat = 255 + +public class RGBA: NSObject { + public var red: CGFloat + public var green: CGFloat + public var blue: CGFloat + public var alpha: CGFloat + + public init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { + self.red = red + self.green = green + self.blue = blue + self.alpha = alpha + } + + public init(intRed: Int, green: Int, blue: Int, alpha: Int) { + self.red = CGFloat(intRed)/ChannelDivider + self.green = CGFloat(green)/ChannelDivider + self.blue = CGFloat(blue)/ChannelDivider + self.alpha = CGFloat(alpha)/ChannelDivider + } + + public convenience init(uiColor: UIColor) { + self.init(red: 0, green: 0, blue: 0, alpha: 0) + uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + } +} + +public class Grayscale: NSObject { + public var white: CGFloat + public var alpha: CGFloat + + public init(white: CGFloat, alpha: CGFloat) { + self.white = white + self.alpha = alpha + } +} + +public class GradientPoint: NSObject { + public var location: CGFloat + public var color: C + + public init(location: CGFloat, color: C) { + self.location = location + self.color = color + } +} + +extension UIImage { + public class func image(withGradientPoints gradientPoints: [GradientPoint<[CGFloat]>], colorSpace: CGColorSpace, size: CGSize) -> UIImage? { + UIGraphicsBeginImageContextWithOptions(size, false, 0) + guard + let context = UIGraphicsGetCurrentContext(), + let gradient = CGGradient(colorSpace: colorSpace, + colorComponents: gradientPoints.flatMap { $0.color }, + locations: gradientPoints.map { $0.location }, count: gradientPoints.count) else { + return nil + } + + context.drawLinearGradient(gradient, start: CGPoint.zero, end: CGPoint(x: size.width, y: 0), options: CGGradientDrawingOptions()) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image + } + + public class func image(withRGBAGradientPoints gradientPoints: [GradientPoint], size: CGSize) -> UIImage? { + return image(withGradientPoints: gradientPoints.map { + GradientPoint(location: $0.location, color: [$0.color.red, $0.color.green, $0.color.blue, $0.color.alpha]) + }, colorSpace: CGColorSpaceCreateDeviceRGB(), size: size) + } + + public class func image(withRGBAGradientColors gradientColors: [CGFloat: RGBA], size: CGSize) -> UIImage? { + return image(withRGBAGradientPoints: gradientColors.map { GradientPoint(location: $0, color: $1)}, size: size) + } + + public class func image(withGrayscaleGradientPoints gradientPoints: [GradientPoint], size: CGSize) -> UIImage? { + return image(withGradientPoints: gradientPoints.map { + GradientPoint(location: $0.location, color: [$0.color.white, $0.color.alpha]) }, + colorSpace: CGColorSpaceCreateDeviceGray(), size: size) + } + + public class func image(withGrayscaleGradientColors gradientColors: [CGFloat: Grayscale], size: CGSize) -> UIImage? { + return image(withGrayscaleGradientPoints: gradientColors.map { GradientPoint(location: $0, color: $1) }, size: size) + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIImage+Loading.swift b/UIToolkits/UIToolkits/_Extensions/UIImage+Loading.swift new file mode 100644 index 000000000..785a7d489 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIImage+Loading.swift @@ -0,0 +1,42 @@ +// +// UIImage+Loading.swift +// UIToolkits +// +// Created by Qiang Huang on 7/21/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit + +public extension UIImage { + static func named(_ name: String, bundles: [Bundle]) -> UIImage? { + var image: UIImage? + for bundle in bundles { + image = UIImage(named: name, in: bundle, compatibleWith: nil) + if image != nil { + break + } + } + return image + } + + func normalized() -> UIImage { + autoreleasepool { + if imageOrientation == UIImage.Orientation.up { + return self + } + + UIGraphicsBeginImageContextWithOptions(size, false, scale) + let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) + draw(in: rect) + + if let normalizedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext() { + UIGraphicsEndImageContext() + return normalizedImage + } else { + UIGraphicsEndImageContext() + return self + } + } + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIImage+Tint.swift b/UIToolkits/UIToolkits/_Extensions/UIImage+Tint.swift new file mode 100644 index 000000000..ab5b2cc2f --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIImage+Tint.swift @@ -0,0 +1,93 @@ +// +// UIImage+Tint.swift +// UIToolkits +// +// Created by Qiang Huang on 1/19/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit + +public extension UIImage { + static func load(file: String?) -> UIImage? { + if let file = file, let data = NSData(contentsOfFile: file) { + return UIImage(data: data as Data) + } + return nil + } + + func tint(color: UIColor?) -> UIImage? { + if let color = color, let maskImage = cgImage { + let width = size.width + let height = size.height + let bounds = CGRect(x: 0, y: 0, width: width, height: height) + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) + let context = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)! + + context.clip(to: bounds, mask: maskImage) + context.setFillColor(color.cgColor) + context.fill(bounds) + + if let cgImage = context.makeImage() { + let coloredImage = UIImage(cgImage: cgImage) + return coloredImage + } + } + return self + } + + func resize(to targetSize: CGSize) -> UIImage? { + autoreleasepool { + let size = self.size + + let widthRatio = targetSize.width / size.width + let heightRatio = targetSize.height / size.height + + // Figure out what our orientation is, and use that to form the rectangle + var newSize: CGSize + if widthRatio > heightRatio { + newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio) + } else { + newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio) + } + + // This is the rect that we've calculated out and this is what is actually used below + let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height) + + // Actually do the resizing to the rect using the ImageContext stuff + UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0) + draw(in: rect) + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return newImage + } + } + + func resizeProportional(to targetSize: CGSize) -> UIImage? { + var calculated = CGSize() + if size.width > size.height { + calculated = CGSize(width: size.width / size.height * targetSize.height, height: targetSize.height) + } else { + calculated = CGSize(width: targetSize.width, height: size.height / size.width * targetSize.width) + } + return resize(to: calculated) + } + + func resize(to targetSize: CGSize, leftPercentage: CGFloat, rightPercentage: CGFloat) -> UIImage? { + autoreleasepool { + UIGraphicsBeginImageContextWithOptions(targetSize, false, 1.0) + let left = targetSize.width * leftPercentage + let right = targetSize.width * rightPercentage + let rect = CGRect(x: left * -1, y: 0, width: targetSize.width + left + right, height: targetSize.height) + let myRect = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height) + UIBezierPath(roundedRect: myRect, cornerRadius: targetSize.height / 2).addClip() + draw(in: rect) + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return newImage + } + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIImageView+Tint.swift b/UIToolkits/UIToolkits/_Extensions/UIImageView+Tint.swift new file mode 100644 index 000000000..7ba4cc28a --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIImageView+Tint.swift @@ -0,0 +1,16 @@ +// +// UIImageView+Tint.swift +// UIToolkits +// +// Created by Qiang Huang on 1/19/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit + +extension UIImageView { + open override func awakeFromNib() { + super.awakeFromNib() + tintColorDidChange() + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIKit+Protocols.swift b/UIToolkits/UIToolkits/_Extensions/UIKit+Protocols.swift new file mode 100644 index 000000000..9e8a9f6b4 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIKit+Protocols.swift @@ -0,0 +1,334 @@ +// +// UILabel+Protocol.swift +// UIToolkits +// +// Created by Qiang Huang on 12/11/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import MaterialActivityIndicator +import UIKit +import Utilities +import Combine + +extension UIView: ViewProtocol { + public var visible: Bool { + get { return !isHidden } + set { + if isHidden != !newValue { + isHidden = !newValue + } + } + } +} + +extension UIActivityIndicatorView: SpinnerProtocol { + public var spinning: Bool { + get { return isAnimating } + set { + if newValue { + startAnimating() + } else { + stopAnimating() + } + } + } +} + +extension MaterialActivityIndicatorView: SpinnerProtocol, CombineObserving { + fileprivate struct SpinnerKey { + static var reallySpinning = "MaterialActivityIndicatorView.reallySpinning" + static var spinning = "MaterialActivityIndicatorView.spinning" + static var appState = "MaterialActivityIndicatorView.appState" + static var cancellableMap = "MaterialActivityIndicatorView.cancellableMap" + } + + var _spinning: Bool { + get { + let isSpinning: NSNumber? = associatedObject(base: self, key: &SpinnerKey.reallySpinning) + return isSpinning?.boolValue ?? false + } + set { + let oldValue = _spinning + if oldValue != newValue { + let isSpinning: NSNumber = NSNumber(value: newValue) + retainObject(base: self, key: &SpinnerKey.reallySpinning, value: isSpinning) + if newValue { + startAnimating() + } else { + stopAnimating() + } + } + } + } + + public var cancellableMap: [AnyKeyPath : AnyCancellable] { + get { + associatedObject(base: self, key: &SpinnerKey.cancellableMap) { + [AnyKeyPath : AnyCancellable]() + } + } + set { + retainObject(base: self, key: &SpinnerKey.cancellableMap, value: newValue) + } + } + + + public var spinning: Bool { + get { + let isSpinning: NSNumber? = associatedObject(base: self, key: &SpinnerKey.spinning) + return isSpinning?.boolValue ?? false + } + set { + let oldValue = spinning + if oldValue != newValue { + let isSpinning: NSNumber = NSNumber(value: newValue) + retainObject(base: self, key: &SpinnerKey.spinning, value: isSpinning) + if appState?.background == false { + _spinning = newValue + } + } + } + } + + private var appState: AppState? { + get { + return associatedObject(base: self, key: &SpinnerKey.appState) + } + set { + let oldValue = appState + if oldValue !== newValue { + retainObject(base: self, key: &SpinnerKey.appState, value: newValue) + changeObservation(from: oldValue, to: appState, keyPath: #keyPath(AppState.background)) { [weak self] _, _, _, animated in + if self?.appState?.background == false { + self?._spinning = self?.spinning ?? false + } else { + self?._spinning = false + } + } + } + } + } + + override public func awakeFromNib() { + super.awakeFromNib() + appState = AppState.shared + } +} + +extension UILabel: LabelProtocol { + public func formatUrl(text: String?) -> URL? { + if let text = text { + if let ranges = text.detectHttpUrl() { + let styledText = NSMutableAttributedString(string: text) + + for range in ranges { + styledText.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range) + styledText.addAttribute(NSAttributedString.Key.foregroundColor, value: ColorPalette.shared.color(system: "blue") ?? UIColor.blue, range: range) + } + self.text = nil + attributedText = styledText + if let first = ranges.first, let range = Range(first, in: text) { + let urlString = String(text[range]) + return URL(string: urlString) + } + return nil + } else { + attributedText = nil + self.text = text + return nil + } + } else { + attributedText = nil + self.text = nil + return nil + } + } +} + +extension UITextField: LabelProtocol { + public func formatUrl(text: String?) -> URL? { + if let text = text, let ranges = text.detectHttpUrl() { + let styledText = NSMutableAttributedString(string: text) + + for range in ranges { + styledText.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range) + } + attributedText = styledText + if let first = ranges.first, let range = Range(first, in: text) { + let urlString = String(text[range]) + return URL(string: urlString) + } + return nil + } else { + self.text = text + return nil + } + } +} + +extension UIImageView: ImageViewProtocol { +} + +extension UIControl: ControlProtocol { + public func removeTarget() { + removeTarget(nil, action: nil, for: .allEvents) + } + + public func addTarget(_ target: AnyObject?, action: Selector) { + removeTarget() + addTarget(target, action: action, for: .touchUpInside) + } + + public func add(target: AnyObject?, action: Selector, for controlEvents: UIControl.Event) { + addTarget(target, action: action, for: controlEvents) + } +} + +extension UIButton: ButtonProtocol { + open var buttonTitle: String? { + get { + return title(for: .normal) + } + set { + setTitle(newValue, for: .normal) + } + } + + public var buttonImage: UIImage? { + get { + return image(for: .normal) + } + set { + DispatchQueue.runInMainThread { [weak self] in + self?.setImage(newValue, for: .normal) + } + } + } + + public var buttonChecked: Bool { + get { + return isSelected + } + set { + isSelected = newValue + } + } + + public var buttonTitleColor: UIColor? { + get { + return titleColor(for: .normal) + } + set { + setTitleColor(newValue, for: .normal) + } + } +} + +extension UIBarButtonItem: ButtonProtocol { + public var backgroundColor: NativeColor! { + get { + return UIColor.clear + } + set { + } + } + + public var buttonTitle: String? { + get { + return title + } + set { + title = newValue + } + } + + public var buttonImage: NativeImage? { + get { + return image + } + set { + image = newValue + } + } + + public var buttonChecked: Bool { + get { + return checked + } + set { + checked = newValue + } + } + + public var frame: CGRect { + get { + return CGRect() + } + set { + } + } + + public var visible: Bool { + get { + return isEnabled + } + set { + isEnabled = newValue + } + } + + public var checked: Bool { + get { + return tintColor == UIColor.blue + } + set { + tintColor = newValue ? UIColor.blue : UIColor.darkGray + } + } + + public func removeTarget() { + target = nil + action = nil + } + + public func addTarget(_ target: AnyObject?, action: Selector) { + self.target = target + self.action = action + } + + public func add(target: AnyObject?, action: Selector, for controlEvents: UIControl.Event) { + addTarget(target, action: action) + } +} + +extension UISegmentedControl: SegmentedProtocol { + public var selectedIndex: Int { + get { + return selectedSegmentIndex + } + set { + selectedSegmentIndex = newValue + } + } + + @objc public func fill(titles: [String]?) { + removeAllSegments() + if let titles = titles { + for title in titles { + insertSegment(withTitle: title, at: numberOfSegments, animated: false) + } + } + } + + public static func segments(with titles: [String]) -> SegmentedProtocol { + let segments = UISegmentedControl() + segments.fill(titles: titles) + return segments + } + + override public func addTarget(_ target: AnyObject?, action: Selector) { + removeTarget() + addTarget(target, action: action, for: .valueChanged) + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UINavigationBar+Transparent.swift b/UIToolkits/UIToolkits/_Extensions/UINavigationBar+Transparent.swift new file mode 100644 index 000000000..3b2335779 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UINavigationBar+Transparent.swift @@ -0,0 +1,28 @@ +// +// UINavigationBar+Transparent.swift +// UIToolkits +// +// Created by Qiang Huang on 8/19/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIKit + +public extension UINavigationBar { + @IBInspectable var transparent: Bool { + get { + return backgroundImage(for: .default) == nil + } + set { + if newValue == true { + shadowImage = UIImage() + isTranslucent = true + setBackgroundImage(UIImage(), for: .default) + } else { + shadowImage = UIImage() + isTranslucent = false + setBackgroundImage(nil, for: .default) + } + } + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UINavigationController+Load.swift b/UIToolkits/UIToolkits/_Extensions/UINavigationController+Load.swift new file mode 100644 index 000000000..706dc57b1 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UINavigationController+Load.swift @@ -0,0 +1,33 @@ +// +// UINavigationController+Load.swift +// UIToolkits +// +// Created by Qiang Huang on 9/12/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +public extension UINavigationController { + @objc static func loadNavigator(storyboard: String?) -> UINavigationController? { + if let storyboard = storyboard, UIDevice.current.userInterfaceIdiom == .pad { + if let nav = UIViewController.load(storyboard: "\(storyboard)-iPad") as? UINavigationController { + return nav + } + } + return UIViewController.load(storyboard: storyboard) as? UINavigationController + } + + @objc static func load(storyboard: String?, with viewController: UIViewController?) -> UINavigationController? { + if let viewController = viewController { + if let navigationController = viewController as? UINavigationController { + return navigationController + } else { + if let navigationController = loadNavigator(storyboard: storyboard) { + navigationController.viewControllers = [viewController] + return navigationController + } + return UINavigationController(rootViewController: viewController) + } + } + return nil + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIResponder+First.swift b/UIToolkits/UIToolkits/_Extensions/UIResponder+First.swift new file mode 100644 index 000000000..09959a750 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIResponder+First.swift @@ -0,0 +1,23 @@ +// +// UIResponder+First.swift +// UIToolkits +// +// Created by Qiang Huang on 9/1/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIKit + +extension UIResponder { + private weak static var _current: UIResponder? + + public static var current: UIResponder? { + UIResponder._current = nil + UIApplication.shared.sendAction(#selector(findFirstResponder(_:)), to: nil, from: nil, for: nil) + return UIResponder._current + } + + @objc internal func findFirstResponder(_ sender: AnyObject) { + UIResponder._current = self + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UISearchPresenter.swift b/UIToolkits/UIToolkits/_Extensions/UISearchPresenter.swift new file mode 100644 index 000000000..4c3a2322c --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UISearchPresenter.swift @@ -0,0 +1,122 @@ +// +// UISearchPresenter.swift +// UIToolkits +// +// Created by Qiang Huang on 11/2/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +@objc open class UISearchPresenter: NSObject, SearchUIProtocol { + @IBInspectable var xib: String? + + @IBOutlet @objc open dynamic var searchButton: UIBarButtonItem? { + didSet { + if searchButton !== oldValue { + oldValue?.removeTarget() + searchButton?.addTarget(self, action: #selector(search(_:))) + } + } + } + + @IBOutlet @objc open dynamic var cancelSearchButton: UIBarButtonItem? { + didSet { + if cancelSearchButton !== oldValue { + oldValue?.removeTarget() + cancelSearchButton?.addTarget(self, action: #selector(cancelSearch(_:))) + } + } + } + + @IBOutlet @objc open dynamic var searchSaveButton: ButtonProtocol? { + didSet { + if searchSaveButton !== oldValue { + oldValue?.removeTarget() + searchSaveButton?.addTarget(self, action: #selector(saveSearch(_:))) + } + } + } + + @IBOutlet @objc open dynamic var searchDoneButton: ButtonProtocol? { + didSet { + if searchDoneButton !== oldValue { + oldValue?.removeTarget() + searchDoneButton?.addTarget(self, action: #selector(doneSearch(_:))) + } + } + } + + @IBOutlet @objc open dynamic var searchView: UIView? + + @IBOutlet @objc open dynamic var searchBar: UISearchBar? { + didSet { + if searchBar !== oldValue { + oldValue?.delegate = nil + searchBar?.delegate = self + } + } + } + + @objc open dynamic var isSearching: Bool = false { + didSet { + if isSearching != oldValue { + searchingChanged() + } + } + } + + @objc open dynamic var searchText: String? + + @objc open func searchingChanged() { + if isSearching { + searchBar?.becomeFirstResponder() + searchText = searchBar?.text + } else { + searchBar?.resignFirstResponder() + searchText = nil + } + } + + @objc open func searchTextChanged() { + } + + @objc @IBAction open func search(_ sender: Any?) { + isSearching = true + } + + @objc @IBAction open func cancelSearch(_ sender: Any?) { + isSearching = false + } + + @objc @IBAction open func saveSearch(_ sender: Any?) { + } + + @objc @IBAction open func doneSearch(_ sender: Any?) { + searchBar?.resignFirstResponder() + } +} + +extension UISearchPresenter: UISearchBarDelegate { + open func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool { + isSearching = true + return true + } + + open func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { + isSearching = false + } + + open func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + self.searchText = searchText + } + + open func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { + isSearching = false + } + + open func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { + searchBar.resignFirstResponder() + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UITabbar+Transparent.swift b/UIToolkits/UIToolkits/_Extensions/UITabbar+Transparent.swift new file mode 100644 index 000000000..4fa5ce22d --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UITabbar+Transparent.swift @@ -0,0 +1,28 @@ +// +// UITabbar+Transparent.swift +// UIToolkits +// +// Created by Qiang Huang on 9/19/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import UIKit + +public extension UITabBar { + @IBInspectable var transparent: Bool { + get { + return backgroundImage != nil + } + set { + if newValue == true { + backgroundImage = UIImage() + shadowImage = UIImage() + isTranslucent = true + } else { + backgroundImage = nil + shadowImage = nil + isTranslucent = false + } + } + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UITextField+Done.swift b/UIToolkits/UIToolkits/_Extensions/UITextField+Done.swift new file mode 100644 index 000000000..924e9da4d --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UITextField+Done.swift @@ -0,0 +1,40 @@ +// +// UITextField+Done.swift +// UIToolkits +// +// Created by Qiang Huang on 9/21/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import UIKit + +public extension UITextField { + @IBInspectable var doneAccessory: Bool { + get { + return self.doneAccessory + } + set(hasDone) { + if hasDone { + addDoneButtonOnKeyboard() + } + } + } + + func addDoneButtonOnKeyboard() { + let doneToolbar: UIToolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50)) + doneToolbar.barStyle = .default + + let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(doneButtonAction)) + + let items = [flexSpace, done] + doneToolbar.items = items + doneToolbar.sizeToFit() + + inputAccessoryView = doneToolbar + } + + @objc func doneButtonAction() { + resignFirstResponder() + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIView+Border.swift b/UIToolkits/UIToolkits/_Extensions/UIView+Border.swift new file mode 100644 index 000000000..8070da694 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIView+Border.swift @@ -0,0 +1,66 @@ +// +// UIView+Border.swift +// UIToolkits +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIKit + +@IBDesignable +public extension UIView { + @IBInspectable var corner: CGFloat { + get { return layer.cornerRadius } + set { layer.cornerRadius = newValue } + } + + @IBInspectable var borderWidth: CGFloat { + get { return layer.borderWidth } + set { layer.borderWidth = newValue } + } + + @IBInspectable var borderColor: UIColor? { + get { + if let cgColor = self.layer.borderColor { + return UIColor(cgColor: cgColor) + } else { + return UIColor.clear + } + } + set { layer.borderColor = newValue?.cgColor } + } + + @IBInspectable var shadowOpacity: Float { + get { return layer.shadowOpacity } + set { + layer.shadowOpacity = newValue + layer.masksToBounds = false + } + } + + @IBInspectable var shadowOffset: CGSize { + get { return layer.shadowOffset } + set { layer.shadowOffset = newValue } + } + + @IBInspectable var shadowRadius: CGFloat { + get { return layer.shadowRadius } + set { layer.shadowRadius = newValue } + } + + @IBInspectable var shadowColor: UIColor? { + get { + if let cgColor = self.layer.shadowColor { + return UIColor(cgColor: cgColor) + } else { + return UIColor.clear + } + } + set { + if let color = newValue { + layer.shadowColor = color.cgColor + } + } + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIView+Color.swift b/UIToolkits/UIToolkits/_Extensions/UIView+Color.swift new file mode 100644 index 000000000..101add11c --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIView+Color.swift @@ -0,0 +1,86 @@ +// +// UIView+Color.swift +// UIToolkits +// +// Created by Qiang Huang on 8/29/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +@objc public extension UIView { + fileprivate struct ColorKey { + static var background = "view.color.background" + static var border = "view.color.border" + static var text = "view.color.text" + } + + @IBInspectable var backgroundColorName: String? { + get { + return associatedObject(base: self, key: &ColorKey.background) + } + set { + retainObject(base: self, key: &ColorKey.background, value: newValue) + setupBackgroundColor() + } + } + + @objc func setupBackgroundColor() { + if let backgroundColorName = backgroundColorName, let color = ColorPalette.shared.color(system: backgroundColorName) { + backgroundColor = color + } + } + + @IBInspectable var borderColorName: String? { + get { + return associatedObject(base: self, key: &ColorKey.border) + } + set { + retainObject(base: self, key: &ColorKey.border, value: newValue) + setupBorderColor() + } + } + + @objc func setupBorderColor() { + if let borderColorName = borderColorName, let color = ColorPalette.shared.color(keyed: borderColorName) { + borderColor = color + } + } +} + +@objc public extension UIButton { + @IBInspectable var textColorName: String? { + get { + return associatedObject(base: self, key: &ColorKey.text) + } + set { + retainObject(base: self, key: &ColorKey.text, value: newValue) + setupTextColor() + } + } + + @objc func setupTextColor() { + if let textColorName = textColorName, let color = ColorPalette.shared.color(keyed: textColorName) { + setTitleColor(color, for: .normal) + } + } +} + +@objc public extension UILabel { + @IBInspectable var textColorName: String? { + get { + return associatedObject(base: self, key: &ColorKey.text) + } + set { + retainObject(base: self, key: &ColorKey.text, value: newValue) + setupTextColor() + } + } + + @objc func setupTextColor() { + if let textColorName = textColorName, let color = ColorPalette.shared.color(keyed: textColorName) { + textColor = color + } + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIView+Hierarchy.swift b/UIToolkits/UIToolkits/_Extensions/UIView+Hierarchy.swift new file mode 100644 index 000000000..42f2590a9 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIView+Hierarchy.swift @@ -0,0 +1,62 @@ +// +// UIView+Hierarchy.swift +// UIToolkits +// +// Created by John Huang on 1/17/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import SnapKit +import UIKit + +extension UIView { + public func parent() -> T? { + if let superview = superview as? T { + return superview + } else { + let view: T? = superview?.parent() + return view + } + } + + public func subview() -> T? { + if let subview: T = subviews.first(where: { (view) -> Bool in + view is T + }) as? T { + return subview + } else { + var found: T? + _ = subviews.first { (subview) -> Bool in + found = subview.subview() + return found != nil + } + return found + } + } + + public func viewController() -> UIViewController? { + var responder: UIResponder? = self + while responder != nil && !(responder is UIViewController) { + responder = responder?.next + } + return responder as? UIViewController + } + + public func bringToFront() { + superview?.bringSubviewToFront(self) + } + + @objc open func install(view: UIView, into contentView: UIView) { + if contentView.bounds.size.width == 0.0 && contentView.bounds.size.height == 0.0 { + contentView.bounds = view.bounds + } else { + view.frame = contentView.bounds + } + contentView.addSubview(view) + view.translatesAutoresizingMaskIntoConstraints = false + view.snp.updateConstraints() { make in + make.edges.equalToSuperview() + } + contentView.sendSubviewToBack(view) + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIView+Tap.swift b/UIToolkits/UIToolkits/_Extensions/UIView+Tap.swift new file mode 100644 index 000000000..a4ef1c10f --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIView+Tap.swift @@ -0,0 +1,54 @@ +// +// UIView+Tap.swift +// UIToolkits +// +// Created by Qiang Huang on 12/9/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import UIKit +import Utilities + +public extension UIView { + private struct TapKeys { + static var tapKey = "Tap" + static var tapFocusKey = "TapFocus" + } + + private var tap: UITapGestureRecognizer? { + get { + return associatedObject(base: self, key: &TapKeys.tapKey) + } + set { + if tap !== newValue { + if let oldValue = tap { + removeGestureRecognizer(oldValue) + } + retainObject(base: self, key: &TapKeys.tapKey, value: newValue) + if let newValue = newValue { + addGestureRecognizer(newValue) + } + } + } + } + + var tapFocus: UITextField? { + get { + return associatedObject(base: self, key: &TapKeys.tapFocusKey) + } + set { + if tapFocus !== newValue { + retainObject(base: self, key: &TapKeys.tapFocusKey, value: newValue) + if newValue !== nil { + if tap === nil { + tap = UITapGestureRecognizer { [weak self] _ in + self?.tapFocus?.becomeFirstResponder() + } + } + } else { + tap = nil + } + } + } + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIView+Transitions.swift b/UIToolkits/UIToolkits/_Extensions/UIView+Transitions.swift new file mode 100644 index 000000000..94d81d008 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIView+Transitions.swift @@ -0,0 +1,169 @@ +// +// AnimationBlock.swift +// UIToolkits +// +// Created by John Huang on 10/24/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +import QuartzCore + +public enum AnimationType { + case none + case curl + case flip + case resolve + case fade + case push + case reveal + case move +} + +public enum AnimationDirection { + case none + case up + case down + case left + case right +} + +public typealias AnimationBlock = () -> Void +public typealias AnimationCompletionBlock = (Bool) -> Void + +public extension UIView { + static var defaultAnimationDuration: TimeInterval { return 0.15 } + static var fastAnimationDuration: TimeInterval { return 0.05 } + static func animate(_ view: UIView?, type: AnimationType, direction: AnimationDirection, duration: TimeInterval, animations: @escaping AnimationBlock, completion: AnimationCompletionBlock?) { + if let view = view { + switch type { + case .curl: + fallthrough + case .flip: + fallthrough + case .resolve: + UIView.transition(with: view, duration: duration, options: transition(type, direction: direction), animations: { animations() }, completion: completion) + + case .fade: + fallthrough + case .push: + fallthrough + case .reveal: + fallthrough + case .move: + UIView.layerTransition(view, type: type, direction: direction, duration: duration, animations: animations, completion: completion) + + default: + animations() + completion?(false) + break + } + } else { + animations() + completion?(false) + } + } + + private static func transition(_ type: AnimationType, direction: AnimationDirection) -> UIView.AnimationOptions { + switch type { + case .curl: + switch direction { + case .up: + return .transitionCurlUp + + case .down: + return .transitionCurlDown + + default: + return .transitionCurlUp + } + + case .flip: + switch direction { + case .left: + return .transitionFlipFromRight + + case .right: + return .transitionFlipFromLeft + + case .up: + return .transitionFlipFromBottom + + case .down: + return .transitionFlipFromTop + + default: + return .transitionFlipFromRight + } + + default: + return .transitionCrossDissolve + } + } + + private static func transitionType(_ type: AnimationType) -> CATransitionType { + switch type { + case .push: + return CATransitionType.push + + case .reveal: + return CATransitionType.reveal + + case .move: + return CATransitionType.moveIn + + case .fade: + return CATransitionType.fade + + default: + return CATransitionType.fade + } + } + + private static func transitionSubtype(direction: AnimationDirection) -> CATransitionSubtype? { + switch direction { + case .up: + return CATransitionSubtype.fromBottom + + case .down: + return CATransitionSubtype.fromTop + + case .left: + return CATransitionSubtype.fromRight + + case .right: + return CATransitionSubtype.fromTop + + default: + return nil + } + } + + private static func layerTransition(_ view: UIView?, type: AnimationType, direction: AnimationDirection, duration: TimeInterval, animations: @escaping AnimationBlock, completion: AnimationCompletionBlock?) { + CATransaction.begin() + if let completion = completion { + CATransaction.setCompletionBlock({ + completion(true) + }) + } + + let animation = CATransition() + animation.type = transitionType(type) + animation.subtype = transitionSubtype(direction: direction) + animation.duration = duration + animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + + view?.layer.add(animation, forKey: nil) + animations() + + CATransaction.commit() + } +} + +public extension UIView { + func screenshot() -> UIImage { + return UIGraphicsImageRenderer(size: bounds.size).image { _ in + drawHierarchy(in: CGRect(origin: .zero, size: bounds.size), afterScreenUpdates: true) + } + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIViewController+Embed.swift b/UIToolkits/UIToolkits/_Extensions/UIViewController+Embed.swift new file mode 100644 index 000000000..d159d066d --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIViewController+Embed.swift @@ -0,0 +1,57 @@ +// +// UIViewController+Embed.swift +// UIToolkits +// +// Created by Qiang Huang on 9/12/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import PanModal +import UIKit + +extension UIViewController { + @objc open var intrinsicHeight: NSNumber? { + return nil + } + + public func embed(_ viewController: UIViewController?, in view: UIView?) { + if let viewController = viewController, let view = view { + viewController.willMove(toParent: self) +// viewController.view.frame = view.bounds +// view.addSubview(viewController.view) + view.install(view: viewController.view, into: view) + addChild(viewController) + viewController.didMove(toParent: self) + } + } + + public func remove(_ viewController: UIViewController?) { + if let viewController = viewController { + viewController.willMove(toParent: nil) + viewController.view.removeFromSuperview() + viewController.removeFromParent() + } + } +} + +extension UIViewController: PanModalPresentable { + public var panScrollable: UIScrollView? { + return scrollable + } + + public var longFormHeight: PanModalHeight { + return panScrollable == nil ? .intrinsicHeight : .maxHeightWithTopInset(20) + } + + @objc open var cornerRadius: CGFloat { + return 36.0 + } + + @objc open var scrollable: UIScrollView? { + return nil + } + + @objc open var showDragIndicator: Bool { + return false + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIViewController+Navigation.swift b/UIToolkits/UIToolkits/_Extensions/UIViewController+Navigation.swift new file mode 100644 index 000000000..c319b2de1 --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIViewController+Navigation.swift @@ -0,0 +1,146 @@ +// +// UIView+Navigation.swift +// UIToolkits +// +// Created by John Huang on 10/8/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +extension UIViewController { + @objc public static func load(storyboard: String?) -> UIViewController? { + if let storyboardName = storyboard { + if let bundle = find(storyboard: storyboard, in: Bundle.particles) { + let storyboard = UIStoryboard(name: storyboardName, bundle: bundle) + return storyboard.instantiateInitialViewController() + } + } + return nil + } + + @objc public static func find(storyboard: String?, in bundles: [Bundle]) -> Bundle? { + if storyboard != nil { + var found: Bundle? + for bundle in bundles { + if bundle.path(forResource: storyboard, ofType: "storyboardc") != nil { + found = bundle + break + } + } + return found + } + return nil + } + + @objc public static func navigation(with viewController: UIViewController) -> UINavigationController { + var navigationController = viewController as? UINavigationController + if navigationController === nil { + navigationController = UINavigationController.load(storyboard: "Nav", with: viewController) + navigationController?.modalPresentationStyle = viewController.modalPresentationStyle + } + return navigationController! + } + + @objc open func topmost() -> UIViewController? { + var topmost: UIViewController? + if let halfController = self as? UIViewControllerHalfProtocol { + topmost = halfController.floatingManager?.halved?.topmost() + } + if topmost == nil, let navigationController = self as? UINavigationController { + if let last = last(of: navigationController.viewControllers) { + topmost = last.topmost() + } + } + + if topmost == nil, let tabbarController = self as? UITabBarController { + topmost = tabbarController.selectedViewController?.topmost() + } + + if topmost == nil { + if UIDevice.current.canSplit, let splitViewController = self as? UISplitViewController { + var index: Int = splitViewController.viewControllers.count - 1 + while topmost == nil && index >= 0 { + let current = splitViewController.viewControllers[index] + if let local = current.topmost() { + if local !== current || !(current is UINavigationController) { + topmost = local + } + } + index -= 1 + } + } + if let presentedViewController = self.presentedViewController, !presentedViewController.dismissing() { + topmost = presentedViewController.topmost() + } + } + return topmost ?? self + } + + @objc open func last(of viewControllers: [UIViewController]) -> UIViewController? { + return viewControllers.last { (viewController) -> Bool in + !viewController.popping() + } + } + + @objc open func presenting() -> Bool { + return isBeingPresented || (navigationController?.isBeingPresented ?? false) + } + + @objc open func dismissing() -> Bool { + return isBeingDismissed || (navigationController?.isBeingDismissed ?? false) + } + + @objc open func pushing() -> Bool { + return isMovingToParent && (navigationController?.viewControllers.count ?? 0) > 1 + } + + @objc open func popping() -> Bool { + return isMovingFromParent + } + + public func parentViewControllerConforming(protocol _protocol: Protocol) -> UIViewController? { + if conforms(to: _protocol) { + return self + } else { + var found: UIViewController? + var node: UIViewController? = parentViewController() + while found == nil && node != nil { + if node?.conforms(to: _protocol) ?? false { + found = node + } else { + node = node?.parentViewController() + } + } + return found + } + } + + public func parentViewController() -> UIViewController? { + return navigationController ?? presentingViewController ?? parent + } + + func printTransitionStates() { + Console.shared.log("pushing=\(pushing())") + Console.shared.log("popping=\(popping())") + Console.shared.log("presenting=\(presenting())") + Console.shared.log("dismissing=\(dismissing())") + } + + public func dismissWhenForegrounding(animated: Bool) { + AppState.shared.runForegrounding(task: { [weak self] in + self?.dismiss(animated: animated) + }) + } + + public func dismissWhenForegrounding(animated: Bool, error: Error?) { + AppState.shared.runForegrounding(task: { [weak self] in + self?.dismiss(animated: true, completion: { + if let error = error { + ErrorInfo.shared?.info(title: nil, message: nil, type: .error, error: error) + } + }) + }) + } +} diff --git a/UIToolkits/UIToolkits/_Extensions/UIViewController+Search.swift b/UIToolkits/UIToolkits/_Extensions/UIViewController+Search.swift new file mode 100644 index 000000000..6bfea969c --- /dev/null +++ b/UIToolkits/UIToolkits/_Extensions/UIViewController+Search.swift @@ -0,0 +1,213 @@ +// +// UIViewController+Search.swift +// UIToolkits +// +// Created by Qiang Huang on 8/29/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +@objc public protocol SearchUIProtocol { + var searchButton: UIBarButtonItem? { get set } + var cancelSearchButton: UIBarButtonItem? { get set } + var searchView: UIView? { get set } + var searchBar: UISearchBar? { get set } + var isSearching: Bool { get set } + var searchText: String? { get set } + + func search(_ sender: Any?) + func cancelSearch(_ sender: Any?) +} + +extension UIViewController: SearchUIProtocol { + private struct SearchKey { + static var search = "viewController.search.search" + static var cancel = "viewController.search.cancel" + static var save = "viewController.search.save" + static var done = "viewController.search.done" + static var view = "viewController.search.view" + static var bar = "viewController.search.bar" + static var searching = "viewController.search.searching" + static var text = "viewController.search.text" + } + + @IBOutlet open var searchButton: UIBarButtonItem? { + get { + return associatedObject(base: self, key: &SearchKey.search) + } + set { + let oldValue = searchButton + if oldValue !== newValue { + retainObject(base: self, key: &SearchKey.search, value: newValue) + oldValue?.removeTarget() + searchButton?.addTarget(self, action: #selector(search(_:))) + } + } + } + + @IBOutlet open var cancelSearchButton: UIBarButtonItem? { + get { + return associatedObject(base: self, key: &SearchKey.cancel) + } + set { + let oldValue = cancelSearchButton + if oldValue !== newValue { + retainObject(base: self, key: &SearchKey.cancel, value: newValue) + oldValue?.removeTarget() + cancelSearchButton?.addTarget(self, action: #selector(cancelSearch(_:))) + } + } + } + + @IBOutlet open var searchSaveButton: ButtonProtocol? { + get { + return associatedObject(base: self, key: &SearchKey.save) + } + set { + let oldValue = searchSaveButton + if oldValue !== newValue { + retainObject(base: self, key: &SearchKey.save, value: newValue) + oldValue?.removeTarget() + searchSaveButton?.addTarget(self, action: #selector(saveSearch(_:))) + } + } + } + + @IBOutlet open var searchDoneButton: ButtonProtocol? { + get { + return associatedObject(base: self, key: &SearchKey.done) + } + set { + let oldValue = searchDoneButton + if oldValue !== newValue { + retainObject(base: self, key: &SearchKey.done, value: newValue) + oldValue?.removeTarget() + searchDoneButton?.addTarget(self, action: #selector(doneSearch(_:))) + } + } + } + + @IBOutlet open var searchView: UIView? { + get { + return associatedObject(base: self, key: &SearchKey.view) + } + set { + let oldValue = searchButton + if oldValue !== newValue { + retainObject(base: self, key: &SearchKey.view, value: newValue) + } + } + } + + @IBOutlet open var searchBar: UISearchBar? { + get { + return associatedObject(base: self, key: &SearchKey.bar) + } + set { + let oldValue = searchButton + if oldValue !== newValue { + retainObject(base: self, key: &SearchKey.bar, value: newValue) + searchBar?.delegate = self + configureSearchBar() + } + } + } + + private var _isSearching: NSNumber? { + get { + return associatedObject(base: self, key: &SearchKey.searching) + } + set { + let oldValue = _isSearching + if oldValue?.boolValue ?? false != newValue?.boolValue ?? false { + retainObject(base: self, key: &SearchKey.searching, value: newValue) + } + } + } + + open var isSearching: Bool { + get { + return _isSearching?.boolValue ?? false + } + set { + let oldValue = isSearching + if oldValue != newValue { + _isSearching = NSNumber(booleanLiteral: newValue) + searchingChanged(animated: true) + } + } + } + + open var searchText: String? { + get { + return associatedObject(base: self, key: &SearchKey.text) + } + set { + let oldValue = searchText + if oldValue != newValue { + retainObject(base: self, key: &SearchKey.text, value: newValue) + searchTextChanged() + } + } + } + + @objc open func searchingChanged(animated: Bool) { + if isSearching { + searchBar?.becomeFirstResponder() + searchText = searchBar?.text + } else { + searchBar?.resignFirstResponder() + searchText = nil + } + } + + @objc open func searchTextChanged() { + } + + @objc @IBAction open func search(_ sender: Any?) { + isSearching = true + } + + @objc @IBAction open func cancelSearch(_ sender: Any?) { + isSearching = false + } + + @objc @IBAction open func saveSearch(_ sender: Any?) { + } + + @objc @IBAction open func doneSearch(_ sender: Any?) { + searchBar?.resignFirstResponder() + } + + @objc open func configureSearchBar() { + #if _iOS + searchBar?.showsCancelButton = false + #endif + searchBar?.returnKeyType = .done + } +} + +extension UIViewController: UISearchBarDelegate { + open func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool { + isSearching = true + return true + } + + open func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { + isSearching = false + } + + open func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + self.searchText = searchText + } + + open func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { + isSearching = false + } + + open func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { + searchBar.resignFirstResponder() + } +} diff --git a/UIToolkits/UIToolkits/_Float/FloatingLayoutProviderProtocol.swift b/UIToolkits/UIToolkits/_Float/FloatingLayoutProviderProtocol.swift new file mode 100644 index 000000000..63bb4196c --- /dev/null +++ b/UIToolkits/UIToolkits/_Float/FloatingLayoutProviderProtocol.swift @@ -0,0 +1,14 @@ +// +// FloatingLayoutProviderProtocol.swift +// UIToolkits +// +// Created by Qiang Huang on 4/30/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import FloatingPanel +import UIKit + +public protocol FloatingLayoutProviderProtocol { + func floatingLayout(traitCollection: UITraitCollection) -> FloatingPanelLayout? +} diff --git a/UIToolkits/UIToolkits/_Float/FloatingManager.swift b/UIToolkits/UIToolkits/_Float/FloatingManager.swift new file mode 100644 index 000000000..3b48e179b --- /dev/null +++ b/UIToolkits/UIToolkits/_Float/FloatingManager.swift @@ -0,0 +1,110 @@ +// +// HalfFloatingManager.swift +// UIToolkits +// +// Created by Qiang Huang on 10/22/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import FloatingPanel +import UIToolkits +import Utilities + +open class IntrinsicPanelLayout: FloatingPanelBottomLayout { +} + +open class FloatingManager: NSObject, FloatingProtocol, FloatingPanelControllerDelegate { + public weak var parent: UIViewController? + + @objc open var halved: UIViewController? { + return half?.contentViewController + } + + open var half: FloatingPanelController? { + didSet { + if half !== oldValue { + if let oldValue = oldValue { + oldValue.removePanelFromParent(animated: true) + } + if let half = half, let parent = parent { + half.addPanel(toParent: parent) + } + } + } + } + + public init(parent: UIViewController?) { + super.init() + self.parent = parent + } + + @objc open func half(_ viewController: UIViewController?, animated: Bool) { + half(viewController, shadow: true, presentationStyle: .overFullScreen, animated: animated) + } + + @objc open func half(_ viewController: UIViewController?, shadow: Bool, presentationStyle: UIModalPresentationStyle, animated: Bool) { + let floater = FloatingPanelController() + + // Initialize FloatingPanelController and add the view + floater.surfaceView.layer.cornerRadius = 6.0 + floater.surfaceView.layer.shadowOpacity = shadow ? 1 : 0 + if #available(iOS 13.0, *) { + floater.surfaceView.backgroundColor = UIColor.systemBackground + } else { + // Fallback on earlier versions + } + floater.isRemovalInteractionEnabled = true + floater.modalPresentationStyle = presentationStyle + + let backdropTapGesture = UITapGestureRecognizer(target: self, action: #selector(backdrop(tapGesture:))) + floater.backdropView.addGestureRecognizer(backdropTapGesture) + floater.delegate = self + + // Set a content view controller + floater.set(contentViewController: viewController) + + half = floater + } + + @objc open func backdrop(tapGesture: UITapGestureRecognizer) { + half = nil + } + + open func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout { + IntrinsicPanelLayout() + } + + public func floatingPanelDidMove(_ vc: FloatingPanelController) { + DispatchQueue.main.async { + if let current = UIResponder.current, current is UITextInput { + current.resignFirstResponder() + } + } + } + + open func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith gestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } + + open func floatingPanelDidRemove(_ vc: FloatingPanelController) { + if vc == half { + half = nil + } + } + + open func floatingPanelDidChangeState(_ vc: FloatingPanelController) { + } + + open func floatingPanelDidEndAttracting(_ vc: FloatingPanelController) { + } + + open func dismiss(_ viewController: UIViewController?, animated: Bool) { + if viewController?.floatingParent == half { + half = nil + } + } + + open func floatingPanelShouldBeginDragging(_ fpc: FloatingPanelController) -> Bool { + true + } +} diff --git a/UIToolkits/UIToolkits/_Float/UIViewController+Half.swift b/UIToolkits/UIToolkits/_Float/UIViewController+Half.swift new file mode 100644 index 000000000..cc1d3dd10 --- /dev/null +++ b/UIToolkits/UIToolkits/_Float/UIViewController+Half.swift @@ -0,0 +1,57 @@ +// +// UIViewController+Half.swift +// UIToolkits +// +// Created by Qiang Huang on 8/16/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import FloatingPanel +import PanModal +import UIKit +import UIToolkits +import Utilities + +@objc extension UIViewController: UIViewControllerHalfProtocol { + private struct AssociatedKey { + static var floatingManagerKey = "viewController.half.manager" + } + + public var floatingParent: FloatingPanelController? { + return (navigationController?.parent as? FloatingPanelController) ?? (parent as? FloatingPanelController) + } + + public var _floatingManager: FloatingProtocol? { + get { + return associatedObject(base: self, key: &AssociatedKey.floatingManagerKey) + } + set { + let oldValue = _floatingManager + if oldValue !== newValue { + retainObject(base: self, key: &AssociatedKey.floatingManagerKey, value: newValue) + } + } + } + + public var floatingManager: FloatingProtocol? { + get { + if _floatingManager == nil { + _floatingManager = FloatingManager(parent: self) + } + return _floatingManager + } + set { + _floatingManager = newValue + } + } + + public func dismiss(_ viewController: UIViewController?, animated: Bool) { + floatingManager?.dismiss(viewController, animated: animated) + } +} + +public extension UIViewController { + func move(to position: FloatingPanelState) { + floatingParent?.move(to: position, animated: true, completion: nil) + } +} diff --git a/UIToolkits/UIToolkits/_Haptic/MotionHapticFeedback.swift b/UIToolkits/UIToolkits/_Haptic/MotionHapticFeedback.swift new file mode 100644 index 000000000..b75cd0174 --- /dev/null +++ b/UIToolkits/UIToolkits/_Haptic/MotionHapticFeedback.swift @@ -0,0 +1,238 @@ +// +// HapticFeedback.swift +// UIToolkits +// +// Created by Qiang Huang on 10/30/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import UIKit +import Utilities + +private enum FeedbackType: String { + case lowImpact + case mediumImpact + case highImpact + case selection + case success + case warning + case error +} + +public enum FeedbackKey: String { + case lowImpact + case mediumImpact + case highImpact + case selection + case notification +} + +public typealias HapticCallback = (_ feedback: UIFeedbackGenerator?) -> Void + +@objc public class HapticData: NSObject { + public var key: FeedbackKey? { + didSet { + didSetKey(oldValue: oldValue) + } + } + + public var lock: Int = 0 { + didSet { + didSetLock(oldValue: oldValue) + } + } + + public var feedback: UIFeedbackGenerator? + public var prepareTime: Date? + + private func didSetKey(oldValue: FeedbackKey?) { + if key != oldValue { + generate() + } + } + + private func didSetLock(oldValue: Int?) { + if lock != oldValue { + generate() + } + } + + private func generate() { + if let key = key { + if lock != 0 { + if feedback === nil { + switch key { + case .lowImpact: + feedback = UIImpactFeedbackGenerator(style: .light) + + case .mediumImpact: + feedback = UIImpactFeedbackGenerator(style: .medium) + + case .highImpact: + feedback = UIImpactFeedbackGenerator(style: .heavy) + + case .selection: + feedback = UISelectionFeedbackGenerator() + + case .notification: + feedback = UINotificationFeedbackGenerator() + } + } + } else { + feedback = nil + } + } + } + + public func prepare(callback: HapticCallback?) { + let now = Date() + if let previousPrepare = prepareTime { + let elapsed = now.timeIntervalSince(previousPrepare) + if elapsed > 1 { + feedback?.prepare() + } + } else { + feedback?.prepare() + } + prepareTime = Date() + callback?(feedback) + } +} + +@objc public class MotionHapticFeedback: NSObject, HapticFeedbackProtocol { +// private var cache: [FeedbackKey: UIFeedbackGenerator] = [:] +// private var lockCache: [FeedbackKey: Int] = [:] + + private var cache: [FeedbackKey: HapticData] = [:] + + public func prepareImpact(level: ImpactLevel) { + switch level { + case .low: + prepare(type: .lowImpact) + case .medium: + prepare(type: .mediumImpact) + case .high: + prepare(type: .highImpact) + } + } + + public func prepareSelection() { + prepare(type: .selection) + } + + public func prepareNotify(type: NotificationType) { + switch type { + case .success: + prepare(type: .success) + case .warnng: + prepare(type: .warning) + case .error: + prepare(type: .error) + } + } + + public func impact(level: ImpactLevel) { + switch level { + case .low: + trigger(type: .lowImpact) + case .medium: + trigger(type: .mediumImpact) + case .high: + trigger(type: .highImpact) + } + } + + public func selection() { + trigger(type: .selection) + } + + public func notify(type: NotificationType) { + switch type { + case .success: + trigger(type: .success) + case .warnng: + trigger(type: .warning) + case .error: + trigger(type: .error) + } + } + + private func prepare(type: FeedbackType) { + lock(key: key(type: type), callback: nil) + } + + private func trigger(type: FeedbackType) { + lock(key: key(type: type), callback: { [weak self] feedback in + self?.trigger(feedback: feedback, type: type) + }) + } + + private func key(type: FeedbackType) -> FeedbackKey { + switch type { + case .lowImpact: + return .lowImpact + case .mediumImpact: + return .mediumImpact + case .highImpact: + return .highImpact + case .selection: + return .selection + case .success: + fallthrough + case .warning: + fallthrough + case .error: + return .notification + } + } + + private func trigger(feedback: UIFeedbackGenerator?, type: FeedbackType) { + if let feedback = feedback { + switch type { + case .lowImpact: + fallthrough + case .mediumImpact: + fallthrough + case .highImpact: + (feedback as? UIImpactFeedbackGenerator)?.impactOccurred() + + case .selection: + (feedback as? UISelectionFeedbackGenerator)?.selectionChanged() + + case .success: + (feedback as? UINotificationFeedbackGenerator)?.notificationOccurred(.success) + + case .warning: + (feedback as? UINotificationFeedbackGenerator)?.notificationOccurred(.warning) + + case .error: + (feedback as? UINotificationFeedbackGenerator)?.notificationOccurred(.error) + } + } else { + } + } + + private func lock(key: FeedbackKey, callback: HapticCallback?) { + incrementLock(key: key, callback: callback) + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in + self?.decrementLock(key: key) + } + } + + private func incrementLock(key: FeedbackKey, callback: HapticCallback?) { + var data = cache[key] + if data === nil { + data = HapticData() + data?.key = key + cache[key] = data + } + data?.lock = (data?.lock ?? 0) + 1 + data?.prepare(callback: callback) + } + + private func decrementLock(key: FeedbackKey) { + if let data = cache[key] { + data.lock = data.lock - 1 + } + } +} diff --git a/UIToolkits/UIToolkits/_Localization/UIKit+Localization.swift b/UIToolkits/UIToolkits/_Localization/UIKit+Localization.swift new file mode 100644 index 000000000..828ba98d2 --- /dev/null +++ b/UIToolkits/UIToolkits/_Localization/UIKit+Localization.swift @@ -0,0 +1,63 @@ +// +// File.swift +// UIToolkits +// +// Created by Qiang Huang on 4/30/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +extension UILabel { + open override func awakeFromNib() { + super.awakeFromNib() +// text = text?.localized + } +} + +extension UITextField { + open override func awakeFromNib() { + super.awakeFromNib() + text = text?.localized + placeholder = placeholder?.localized + } +} + +extension UITextView { + open override func awakeFromNib() { + super.awakeFromNib() + text = text?.localized + } +} + +extension UINavigationItem { + open override func awakeFromNib() { + super.awakeFromNib() + title = title?.localized + } +} + +extension UIBarItem { + open override func awakeFromNib() { + super.awakeFromNib() + title = title?.localized + } +} + +extension UISegmentedControl { + open override func awakeFromNib() { + super.awakeFromNib() + for i in 0 ..< numberOfSegments { + let title = titleForSegment(at: i) + setTitle(title?.localized, forSegmentAt: i) + } + } +} + +extension UIButton { + open override func awakeFromNib() { + super.awakeFromNib() + buttonTitle = buttonTitle?.localized + } +} diff --git a/UIToolkits/UIToolkits/_Location/RealLocation.swift b/UIToolkits/UIToolkits/_Location/RealLocation.swift new file mode 100644 index 000000000..8b8877c4b --- /dev/null +++ b/UIToolkits/UIToolkits/_Location/RealLocation.swift @@ -0,0 +1,108 @@ +// +// RealLocation.swift +// UIToolkits +// +// Created by Qiang Huang on 8/18/19. +// Copyright © 2019 Qiang Huang. All rights reserved. +// + +import CoreLocation +import Utilities + +open class RealLocation: NSObject, LocationProviderProtocol { + @objc private dynamic var appState: AppState? { + didSet { + changeObservation(from: oldValue, to: appState, keyPath: #keyPath(AppState.background)) {[weak self] (_, _, _) in + self?.background = self?.appState?.background ?? false + } + } + } + + @objc public dynamic var location: CLLocation? + + open var background: Bool = false { + didSet { + if background != oldValue { + updateBackground() + } + } + } + + open var updating: Bool = true { + didSet { + if updating != oldValue { + updateUpdating() + } + } + } + + public var authorization: LocationPermission? { + didSet { + changeObservation(from: oldValue, to: authorization, keyPath: #keyPath(PrivacyPermission.authorization)) { [weak self] _, _, _ in + if let self = self { + if self.authorization?.authorization == .authorized { + self.locationManager = self.authorization?.locationManager ?? CLLocationManager() + } else { + self.locationManager = nil + self.location = nil + } + } + } + } + } + + public var locationManager: CLLocationManager? { + didSet { + if locationManager !== oldValue { + oldValue?.stopUpdatingLocation() + oldValue?.delegate = nil + locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation + locationManager?.delegate = self + updateUpdating() + } + } + } + + override public init() { + super.init() + DispatchQueue.main.async {[weak self] in + self?.appState = AppState.shared + } + } + + open func updateBackground() { + updating = !background + } + + private func updateUpdating() { + if updating { + locationManager?.startUpdatingLocation() + } else { + locationManager?.stopUpdatingLocation() + } + } +} + +extension RealLocation: CLLocationManagerDelegate { + public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + if let first = locations.first, first.coordinate.latitude != 0.0, first.coordinate.longitude != 0.0 { + location = first + } + } + + public func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) { + if let circular = region as? CLCircularRegion { + RegionMonitor.shared?.enter(lat: circular.center.latitude, lng: circular.center.longitude) + } + } + + public func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) { + if let circular = region as? CLCircularRegion { + RegionMonitor.shared?.exit(lat: circular.center.latitude, lng: circular.center.longitude) + } + } + + public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + authorization?.refreshStatus() + } +} diff --git a/UIToolkits/UIToolkits/_Map/MKCoordinateRegion+Corners.swift b/UIToolkits/UIToolkits/_Map/MKCoordinateRegion+Corners.swift new file mode 100644 index 000000000..c16c3e668 --- /dev/null +++ b/UIToolkits/UIToolkits/_Map/MKCoordinateRegion+Corners.swift @@ -0,0 +1,34 @@ +// +// MKCoordinateRegion+Corners.swift +// UIToolkits +// +// Created by Qiang Huang on 7/23/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import MapKit + +public extension MKCoordinateRegion { + var northWest: CLLocationCoordinate2D { + return CLLocationCoordinate2D(latitude: center.latitude + span.latitudeDelta / 2, longitude: center.longitude - span.longitudeDelta / 2) + } + + var northEast: CLLocationCoordinate2D { + return CLLocationCoordinate2D(latitude: center.latitude + span.latitudeDelta / 2, longitude: center.longitude + span.longitudeDelta / 2) + } + + var southWest: CLLocationCoordinate2D { + return CLLocationCoordinate2D(latitude: center.latitude - span.latitudeDelta / 2, longitude: center.longitude - span.longitudeDelta / 2) + } + + var southEast: CLLocationCoordinate2D { + return CLLocationCoordinate2D(latitude: center.latitude - span.latitudeDelta / 2, longitude: center.longitude + span.longitudeDelta / 2) + } + + func contains(coordinate: CLLocationCoordinate2D) -> Bool { + let center = self.center + let span = self.span + + return cos((center.latitude - coordinate.latitude) * .pi / 180.0) > cos(span.latitudeDelta / 2.0 * .pi / 180.0) && cos((center.longitude - coordinate.longitude) * .pi / 180.0) > cos(span.longitudeDelta / 2.0 * .pi / 180.0) + } +} diff --git a/UIToolkits/UIToolkits/_OCR/OCRService.swift b/UIToolkits/UIToolkits/_OCR/OCRService.swift new file mode 100644 index 000000000..2c96b4148 --- /dev/null +++ b/UIToolkits/UIToolkits/_OCR/OCRService.swift @@ -0,0 +1,21 @@ +// +// OCRService.swift +// UIToolkits +// +// Created by Qiang Huang on 9/17/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import AVFoundation +import UIKit + +public typealias OCRCompletionHandler = (_ text: String?, _ error: Error?) -> Void + +@objc public protocol OCRProtocol: NSObjectProtocol { + func process(buffer: CMSampleBuffer, completion: @escaping OCRCompletionHandler) + func process(image: UIImage, completion: @escaping OCRCompletionHandler) +} + +public class OCRService { + public static var shared: OCRProtocol? +} diff --git a/UIToolkits/UIToolkits/_Prompter/AlertPrompter.swift b/UIToolkits/UIToolkits/_Prompter/AlertPrompter.swift new file mode 100644 index 000000000..0befa8395 --- /dev/null +++ b/UIToolkits/UIToolkits/_Prompter/AlertPrompter.swift @@ -0,0 +1,69 @@ +// +// AlertPrompter.swift +// UIToolkits +// +// Created by Qiang Huang on 6/1/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +open class AlertPrompter: NSObject, PrompterProtocol { + public var title: String? + public var message: String? + public var style: PrompterStyle = .selection + internal var alertController: UIAlertController? + + public func set(title: String?, message: String?, style: PrompterStyle) { + self.title = title + self.message = message + self.style = style + } + + private func alertStyle(style: PrompterActionStyle) -> UIAlertAction.Style { + switch style { + case .cancel: + return .cancel + + case .destructive: + return .destructive + + default: + return .default + } + } + + public func prompt(_ actions: [PrompterAction]) { + if actions.count > 0 { + let alert = UIAlertController(title: title, message: message, preferredStyle: (style == .selection) ? .actionSheet : .alert) + alertController = alert + for action in actions { + let alertAction = UIAlertAction(title: action.title, style: alertStyle(style: action.style)) { [weak self] _ in + self?.dismiss() + action.selection?() + } + alert.addAction(alertAction) + } + + if let barButtonItem = UserInteraction.shared.sender as? UIBarButtonItem { + alert.popoverPresentationController?.barButtonItem = barButtonItem + } else if let view = UserInteraction.shared.sender as? UIView { + alert.popoverPresentationController?.sourceView = view + alert.popoverPresentationController?.sourceRect = view.bounds + } else if let viewController = ViewControllerStack.shared?.topmost(), let view = viewController.view { + alert.popoverPresentationController?.sourceView = view + alert.popoverPresentationController?.sourceRect = view.bounds + } + UserInteraction.shared.sender = nil + DispatchQueue.main.async { + ViewControllerStack.shared?.topmost()?.present(alert, animated: true, completion: nil) + } + } + } + + public func dismiss() { + alertController?.dismiss(nil) + alertController = nil + } +} diff --git a/UIToolkits/UIToolkits/_Prompter/TextEntryPrompter.swift b/UIToolkits/UIToolkits/_Prompter/TextEntryPrompter.swift new file mode 100644 index 000000000..e93d2e753 --- /dev/null +++ b/UIToolkits/UIToolkits/_Prompter/TextEntryPrompter.swift @@ -0,0 +1,21 @@ +// +// TextEntryPrompter.swift +// UIToolkits +// +// Created by Qiang Huang on 6/1/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +open class TextEntryPrompter: AlertPrompter, TextPrompterProtocol { + public var placeholder: String? + public var text: String? + + public func prompt(title: String?, message: String?, text: String?, placeholder: String?, completion: @escaping TextEntrySelection) { + UIAlertController.prompt(title: title, message: message, text: text, placeholder: placeholder) { text, ok in + completion(text, ok) + } + } +} diff --git a/UIToolkits/UIToolkits/_Prompter/UIKitPrompterFactory.swift b/UIToolkits/UIToolkits/_Prompter/UIKitPrompterFactory.swift new file mode 100644 index 000000000..84c5cca17 --- /dev/null +++ b/UIToolkits/UIToolkits/_Prompter/UIKitPrompterFactory.swift @@ -0,0 +1,19 @@ +// +// UIKitPrompterFactory.swift +// UIToolkits +// +// Created by Qiang Huang on 6/1/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Utilities + +open class UIKitPrompterFactory: NSObject, PrompterFactoryProtocol { + public func prompter() -> PrompterProtocol { + return AlertPrompter() + } + + public func textPrompter() -> TextPrompterProtocol { + return TextEntryPrompter() + } +} diff --git a/UIToolkits/UIToolkits/_Protocols/KeyboardAdjustingProtocol.swift b/UIToolkits/UIToolkits/_Protocols/KeyboardAdjustingProtocol.swift new file mode 100644 index 000000000..bb739d669 --- /dev/null +++ b/UIToolkits/UIToolkits/_Protocols/KeyboardAdjustingProtocol.swift @@ -0,0 +1,67 @@ +// +// KeyboardAdjustingProtocol.swift +// UIToolkits +// +// Created by John Huang on 5/14/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +public protocol KeyboardAdjustingProtocol: AnyObject { + var bottom: CGFloat? { get set } + var bottomConstraint: NSLayoutConstraint? { get set } + var keyboardObserver: NotificationToken? { get set } + + func layout(notif: Notification, bottom: CGFloat?) +} + +public extension KeyboardAdjustingProtocol where Self: UIViewController { + func registerKeyboardObserver() { + if keyboardObserver == nil { + keyboardObserver = NotificationCenter.default.observe(notification: UIResponder.keyboardWillChangeFrameNotification, do: { [weak self] (notif: Notification) in + self?.keyboardWillChangeFrame(notif: notif) + }) + } + } + + func keyboardWillChangeFrame(notif: Notification) { + if let bottom = bottom { + layout(notif: notif, bottom: bottom) + } else { + bottom = bottomConstraint?.constant + layout(notif: notif, bottom: nil) + } + } + + func layout(notif: Notification, bottom: CGFloat?, completion: ((Bool) -> Void)?) { + if let start = (notif.userInfo![UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue, let end = (notif.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue, let duration = notif.userInfo![UIResponder.keyboardAnimationDurationUserInfoKey] as? Double, let curve = notif.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt { + let startInFrame = view.convert(start, from: nil) + bottomConstraint?.constant = startInFrame.origin.y - view.bounds.size.height + view.layoutIfNeeded() + + if start.origin.y < end.origin.y, let bottom = bottom { + bottomConstraint?.constant = bottom + } else { + let endInView = view.convert(end, from: nil) + bottomConstraint?.constant = endInView.origin.y - view.bounds.size.height + } + + UIView.animate(withDuration: duration, delay: 0, options: UIView.AnimationOptions(rawValue: curve), animations: { [weak self] in + if let self = self { + self.view.layoutIfNeeded() + self.bringEditingToView() + } + }, completion: completion) + } + } + + func bringEditingToView() { + if let textInput = UIResponder.current as? (UIView & UITextInput) { + if let cell: UITableViewCell = textInput.parent(), let tableView: UITableView = cell.parent(), let indexPath = tableView.indexPath(for: cell) { + tableView.scrollToRow(at: indexPath, at: .bottom, animated: true) + } + } + } +} diff --git a/UIToolkits/UIToolkits/_Protocols/LoadingIndicatorProtocol.swift b/UIToolkits/UIToolkits/_Protocols/LoadingIndicatorProtocol.swift new file mode 100644 index 000000000..31b8595e0 --- /dev/null +++ b/UIToolkits/UIToolkits/_Protocols/LoadingIndicatorProtocol.swift @@ -0,0 +1,13 @@ +// +// LoadingIndicatorProtocol.swift +// UIToolkits +// +// Created by Qiang Huang on 11/20/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +public protocol LoadingIndicatorProtocol { + var status: LoadingStatusProtocol? { get set } +} diff --git a/UIToolkits/UIToolkits/_Protocols/UIViewControllerProtocols.swift b/UIToolkits/UIToolkits/_Protocols/UIViewControllerProtocols.swift new file mode 100644 index 000000000..21e0f7739 --- /dev/null +++ b/UIToolkits/UIToolkits/_Protocols/UIViewControllerProtocols.swift @@ -0,0 +1,44 @@ +// +// UIViewControllerProtocols.swift +// UIToolkits +// +// Created by Qiang Huang on 9/1/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIKit + +@objc public protocol FloatingProtocol: NSObjectProtocol { + @objc var halved: UIViewController? { get } + @objc func half(_ viewController: UIViewController?, animated: Bool) + @objc func dismiss(_ viewController: UIViewController?, animated: Bool) +} + +@objc public protocol EmbeddingProtocol: NSObjectProtocol { + @objc var floated: UIViewController? { get set } + @objc func float(_ viewController: UIViewController?, animated: Bool) + + @objc var embedded: UIViewController? { get set } + @objc func embed(_ viewController: UIViewController?, animated: Bool) + + func installEmbedded() +} + +@objc public protocol UIViewControllerHalfProtocol: NSObjectProtocol { + @objc var floatingManager: FloatingProtocol? { get set } + @objc func dismiss(_ viewController: UIViewController?, animated: Bool) +} + +@objc public protocol UIViewControllerEmbeddingProtocol: NSObjectProtocol { + @objc var floated: UIViewController? { get set } + @objc func embed(_ viewController: UIViewController?, animated: Bool) -> Bool + + @objc var embedded: UIViewController? { get set } + @objc func float(_ viewControler: UIViewController?, animated: Bool) -> Bool +} + +@objc public protocol UIViewControllerDrawerProtocol: NSObjectProtocol { + @objc var left: UIViewController? { get set } + @objc var center: UIViewController? { get set } + @objc var isOpen: Bool { get } +} diff --git a/UIToolkits/UIToolkits/_QRCode/QRCode.swift b/UIToolkits/UIToolkits/_QRCode/QRCode.swift new file mode 100644 index 000000000..05664fcee --- /dev/null +++ b/UIToolkits/UIToolkits/_QRCode/QRCode.swift @@ -0,0 +1,29 @@ +// +// QRCode.swift +// UIToolkits +// +// Created by Qiang Huang on 6/1/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit + +public class QRCode: NSObject { + static public func generate(from string: String, width: Int) -> UIImage? { + let data = string.data(using: String.Encoding.utf16) + + if let filter = CIFilter(name: "CIQRCodeGenerator") { + filter.setValue(data, forKey: "inputMessage") + if let bitmap = filter.outputImage { + let image = UIImage(ciImage: bitmap) + let scale = max(width / Int(image.size.width), 1) + + let transform = CGAffineTransform(scaleX: CGFloat(scale), y: CGFloat(scale)) + let output = bitmap.transformed(by: transform) + return UIImage(ciImage: output) + } + } + + return nil + } +} diff --git a/UIToolkits/UIToolkits/_SVG/SVGCache.swift b/UIToolkits/UIToolkits/_SVG/SVGCache.swift new file mode 100644 index 000000000..83e6247d0 --- /dev/null +++ b/UIToolkits/UIToolkits/_SVG/SVGCache.swift @@ -0,0 +1,40 @@ +// +// SVGCache.swift +// UIToolkits +// +// Created by Qiang Huang on 10/26/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import SVGKit +import UIKit + +public typealias SVGImageFunction = (_ image: UIImage?, _ error: Error?) -> Void +@objc public class SVGCache: NSObject { + public static var shared = SVGCache() + + var cache: [String: UIImage] = [:] + + public func image(url: URL?, completion: @escaping SVGImageFunction) { + if let url = url { + let urlString = url.absoluteString + if let image = cache[urlString] { + completion(image, nil) + } else { + URLSession.shared.dataTask(with: url) { [weak self] data, response, error in + if let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200, let mimeType = response?.mimeType, mimeType.hasPrefix("image"), let data = data, error == nil, let receivedicon: SVGKImage = SVGKImage(data: data), let image = receivedicon.uiImage { + self?.cache[urlString] = image + + DispatchQueue.main.async { + completion(image, nil) + } + } else { + completion(nil, nil) + } + }.resume() + } + } else { + completion(nil, nil) + } + } +} diff --git a/UIToolkits/UIToolkits/_Scan/CompositeScanner.swift b/UIToolkits/UIToolkits/_Scan/CompositeScanner.swift new file mode 100644 index 000000000..300c16933 --- /dev/null +++ b/UIToolkits/UIToolkits/_Scan/CompositeScanner.swift @@ -0,0 +1,62 @@ +// +// CompositeScanner.swift +// UIToolkits +// +// Created by Qiang Huang on 6/1/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import AVFoundation +import UIKit + +public class CompositeScanner: NSObject, ScannerProtocol { + private var scanners = [ScannerProtocol]() + + public func install(scanner: ScannerProtocol) { + scanners.append(scanner) + } + + public func scan(buffer: CMSampleBuffer, completion: @escaping ScanCompletionBlock) { + var completion: ScanCompletionBlock? = completion + var count = 0 + + for scanner in scanners { + scanner.scan(buffer: buffer, completion: { [weak self] strings, error in + if let self = self, let strings = strings { + // successful + if completion != nil { + completion?(strings, error) + completion = nil + } else { + count += 1 + if count == self.scanners.count { + completion?(strings, error) + } + } + } + }) + } + } + + public func scan(image: UIImage, completion: @escaping ScanCompletionBlock) { + var completion: ScanCompletionBlock? = completion + var count = 0 + + for scanner in scanners { + scanner.scan(image: image, completion: { [weak self] strings, error in + if let self = self, let strings = strings { + // successful + if completion != nil { + completion?(strings, error) + completion = nil + } else { + count += 1 + if count == self.scanners.count { + completion?(strings, error) + } + } + } + }) + } + } +} diff --git a/UIToolkits/UIToolkits/_Scan/OCRScanner.swift b/UIToolkits/UIToolkits/_Scan/OCRScanner.swift new file mode 100644 index 000000000..5775e57d6 --- /dev/null +++ b/UIToolkits/UIToolkits/_Scan/OCRScanner.swift @@ -0,0 +1,29 @@ +// +// OCRScanner.swift +// UIToolkits +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import AVFoundation + +public class OCRScanner: NSObject, ScannerProtocol { + public func scan(buffer: CMSampleBuffer, completion: @escaping ScanCompletionBlock) { + completion(nil, nil) + } + + public func scan(image: UIImage, completion: @escaping ScanCompletionBlock) { + if let ocr = OCRService.shared { + ocr.process(image: image) { string, error in + if let string = string { + completion(["text": [string]], error) + } else { + completion(nil, error) + } + } + } else { + completion(nil, nil) + } + } +} diff --git a/UIToolkits/UIToolkits/_Scan/ScannerProtocol.swift b/UIToolkits/UIToolkits/_Scan/ScannerProtocol.swift new file mode 100644 index 000000000..a519d2019 --- /dev/null +++ b/UIToolkits/UIToolkits/_Scan/ScannerProtocol.swift @@ -0,0 +1,17 @@ +// +// ScannerProtocol.swift +// UIToolkits +// +// Created by Qiang Huang on 6/1/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import AVFoundation +import UIKit + +public typealias ScanCompletionBlock = (_ strings: [String: Set]?, _ error: Error?) -> Void + +public protocol ScannerProtocol: NSObjectProtocol { + func scan(buffer: CMSampleBuffer, completion: @escaping ScanCompletionBlock) + func scan(image: UIImage, completion: @escaping ScanCompletionBlock) +} diff --git a/UIToolkits/UIToolkits/_Scan/Scanners.swift b/UIToolkits/UIToolkits/_Scan/Scanners.swift new file mode 100644 index 000000000..66cc85cb5 --- /dev/null +++ b/UIToolkits/UIToolkits/_Scan/Scanners.swift @@ -0,0 +1,24 @@ +// +// Scanners.swift +// UIToolkits +// +// Created by Qiang Huang on 6/1/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +public final class Scanners: NSObject, SingletonProtocol { + public static var shared: Scanners = Scanners() + + private var scanners: [String: ScannerProtocol] = [:] + + public func install(scanner: ScannerProtocol, type: String) { + scanners[type] = scanner + } + + public func scanner(type: String) -> ScannerProtocol? { + return scanners[type] + } +} diff --git a/UIToolkits/UIToolkits/_Shared/UIProtocols.swift b/UIToolkits/UIToolkits/_Shared/UIProtocols.swift new file mode 100644 index 000000000..522dd7ceb --- /dev/null +++ b/UIToolkits/UIToolkits/_Shared/UIProtocols.swift @@ -0,0 +1,80 @@ +// +// UIProtocols.swift +// UIToolkits +// +// Created by Qiang Huang on 12/11/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#if _macOS + + import Cocoa + + public typealias NativeColor = NSColor + public typealias NativeFont = NSFont + public typealias NativeImage = NSImage + +#elseif _watchOS + + import WatchKit + + public typealias NativeColor = UIColor + public typealias NativeFont = UIFont + public typealias NativeImage = UIImage + +#else + + import UIKit + + public typealias NativeColor = UIColor + public typealias NativeFont = UIFont + public typealias NativeImage = UIImage + +#endif + +@objc public protocol ViewProtocol: AnyObject { + @objc var visible: Bool { get set } + @objc var frame: CGRect { get set } + @objc var backgroundColor: NativeColor! { get set } +} + +@objc public protocol SpinnerProtocol: ViewProtocol { + @objc var spinning: Bool { get set } +} + +@objc public protocol LabelProtocol: ViewProtocol { + @objc var attributedText: NSAttributedString? { get set } + @objc var text: String? { get set } + @objc var textColor: NativeColor! { get set } + @objc var font: NativeFont! { get set } + + func formatUrl(text: String?) -> URL? +} + +@objc public protocol ImageViewProtocol: ViewProtocol { + @objc var image: NativeImage? { get set } + @objc optional var imageUrl: String? { get set } +} + +@objc public protocol ControlProtocol: ViewProtocol { + @objc func removeTarget() + @objc func addTarget(_ target: AnyObject?, action: Selector) + @objc func add(target: AnyObject?, action: Selector, for controlEvents: UIControl.Event) +} + +@objc public protocol ButtonProtocol: ControlProtocol { + @objc var buttonTitle: String? { get set } + @objc var buttonImage: NativeImage? { get set } + @objc var buttonChecked: Bool { get set } +} + +@objc public protocol SegmentedProtocol: ControlProtocol { + @objc var numberOfSegments: Int { get } + @objc var selectedIndex: Int { get set } + + @objc func fill(titles: [String]?) +} + +@objc public protocol WaitProtocol: ViewProtocol { + @objc var waiting: Bool { get set } +} diff --git a/UIToolkits/UIToolkits/_View/AlwaysVisibleScrollView.swift b/UIToolkits/UIToolkits/_View/AlwaysVisibleScrollView.swift new file mode 100644 index 000000000..62a57e7c4 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/AlwaysVisibleScrollView.swift @@ -0,0 +1,72 @@ +// +// AlwaysVisibleImageView.swift +// UIToolkits +// +// Created by Qiang Huang on 12/7/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import UIKit +import Utilities + +@objc public class AlwaysOpaqueImageView: UIImageView { + override public var alpha: CGFloat { + didSet { + alpha = 1 + } + } +} + +extension UIView { + fileprivate struct AlwaysVisibleKey { + static var alwaysVisible = "UIScrollView.alwaysVisible" + } + + @IBInspectable var alwaysVisible: Bool { + get { + let isAlwaysVisible: NSNumber? = associatedObject(base: self, key: &AlwaysVisibleKey.alwaysVisible) + if isAlwaysVisible?.boolValue ?? false { + return true + } else { + return false + } + } + set { + if alwaysVisible != newValue { + let isAlwaysVisible: NSNumber = NSNumber(value: newValue) + retainObject(base: self, key: &AlwaysVisibleKey.alwaysVisible, value: isAlwaysVisible) + } + } + } + + override open func awakeFromNib() { + super.awakeFromNib() + if alwaysVisible { + if self is UIScrollView { + for view in subviews { + if view.className().contains("_UIScrollViewScrollIndicator") { + view.alwaysVisible = true + } + } + } + } + } + + public static func classInit() { + if let originalMethod = class_getInstanceMethod(UIView.self, #selector(setter: UIView.alpha)), let swizzledMethod = class_getInstanceMethod(UIView.self, #selector(swizzled_setAlpha)) { + method_exchangeImplementations(originalMethod, swizzledMethod) + } + } + + @objc func swizzled_setAlpha(alpha: CGFloat) { + if let myClassName = classNames().first, myClassName.contains("_UIScrollViewScrollIndicator") { + if alwaysVisible { + swizzled_setAlpha(alpha: 1.0) + } else { + swizzled_setAlpha(alpha: alpha) + } + } else { + swizzled_setAlpha(alpha: alpha) + } + } +} diff --git a/UIToolkits/UIToolkits/_View/ButtonView.swift b/UIToolkits/UIToolkits/_View/ButtonView.swift new file mode 100644 index 000000000..c310bcf00 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/ButtonView.swift @@ -0,0 +1,52 @@ +// +// ButtonView.swift +// UIToolkits +// +// Created by Qiang Huang on 7/20/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import UIKit + +@objc open class ButtonView: UIView, ButtonProtocol { + public func removeTarget() { + button?.removeTarget() + } + + public func addTarget(_ target: AnyObject?, action: Selector) { + button?.addTarget(target, action: action) + } + + public func add(target: AnyObject?, action: Selector, for controlEvents: UIControl.Event) { + button?.add(target: target, action: action, for: controlEvents) + } + + @objc public var buttonTitle: String? { + get { + return button?.buttonTitle + } + set { + button?.buttonTitle = newValue + } + } + + @objc public var buttonImage: NativeImage? { + get { + return button?.buttonImage + } + set { + button?.buttonImage = newValue + } + } + + @objc public var buttonChecked: Bool { + get { + return button?.buttonChecked ?? false + } + set { + button?.buttonChecked = newValue + } + } + + @IBOutlet public var button: UIButton? +} diff --git a/UIToolkits/UIToolkits/_View/CachedImageView.swift b/UIToolkits/UIToolkits/_View/CachedImageView.swift new file mode 100644 index 000000000..3ddef6f87 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/CachedImageView.swift @@ -0,0 +1,63 @@ +// +// CachedImageView.swift +// UIToolkits +// +// Created by Qiang Huang on 10/29/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import SDWebImage +import SVGKit +import UIKit + +// The converted code is limited to 2 KB. +// Upgrade your plan to remove this limitation. +// +open class CachedImageView: UXImageView { + public var imageUrl: URL? { + didSet { + if imageUrl != oldValue { + if let imageUrl = imageUrl { + if imageUrl.absoluteString.lowercased().ends(with: ".svg") { + SVGCache.shared.image(url: imageUrl, completion: { [weak self] image, _ in + DispatchQueue.runInMainThread { [weak self] in + self?.image = image + } + }) + } else { + sd_setImage(with: imageUrl, completed: nil) + } + } else { + image = nil + } + } + } + } + + override open var image: UIImage? { + get { return super.image } + set { + if imageUrl != nil { + self.set(image: newValue, animated: true) + } else { + set(image: newValue) + } + } + } + + open func set(image: UIImage?, animated: Bool) { + DispatchQueue.runInMainThread { [weak self] in + if animated && image != nil && self?.window != nil { + UIView.animate(self, type: .fade, direction: .none, duration: UIView.defaultAnimationDuration, animations: { [weak self] in + self?.set(image: image) + }, completion: nil) + } else { + self?.set(image: image) + } + } + } + + open func set(image: UIImage?) { + super.image = image + } +} diff --git a/UIToolkits/UIToolkits/_View/CircularProgressBar.swift b/UIToolkits/UIToolkits/_View/CircularProgressBar.swift new file mode 100644 index 000000000..f3a170923 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/CircularProgressBar.swift @@ -0,0 +1,58 @@ +// +// CircularProgressBar.swift +// UIToolkits +// +// Created by Qiang Huang on 9/20/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Foundation +import UIKit + +public class CircularProgressBar: CALayer { + private var circularPath: UIBezierPath! + public var innerTrackShapeLayer: CAShapeLayer! + public var outerTrackShapeLayer: CAShapeLayer! + private let rotateTransformation = CATransform3DMakeRotation(-.pi / 2, 0, 0, 1) + public var isUsingAnimation: Bool! + public var progress: CGFloat = 0 { + didSet { + innerTrackShapeLayer.strokeEnd = progress / 100 + } + } + + public init(radius: CGFloat, position: CGPoint, innerTrackColor: UIColor, outerTrackColor: UIColor, lineWidth: CGFloat) { + super.init() + + circularPath = UIBezierPath(arcCenter: .zero, radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true) + outerTrackShapeLayer = CAShapeLayer() + outerTrackShapeLayer.path = circularPath.cgPath + outerTrackShapeLayer.position = position + outerTrackShapeLayer.strokeColor = outerTrackColor.cgColor + outerTrackShapeLayer.fillColor = UIColor.clear.cgColor + outerTrackShapeLayer.lineWidth = lineWidth + outerTrackShapeLayer.strokeEnd = 1 + outerTrackShapeLayer.lineCap = CAShapeLayerLineCap.round + outerTrackShapeLayer.transform = rotateTransformation + addSublayer(outerTrackShapeLayer) + + innerTrackShapeLayer = CAShapeLayer() + innerTrackShapeLayer.strokeColor = innerTrackColor.cgColor + innerTrackShapeLayer.position = position + innerTrackShapeLayer.strokeEnd = progress + innerTrackShapeLayer.lineWidth = lineWidth + innerTrackShapeLayer.lineCap = CAShapeLayerLineCap.round + innerTrackShapeLayer.fillColor = UIColor.clear.cgColor + innerTrackShapeLayer.path = circularPath.cgPath + innerTrackShapeLayer.transform = rotateTransformation + addSublayer(innerTrackShapeLayer) + } + + override public init(layer: Any) { + super.init(layer: layer) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/UIToolkits/UIToolkits/_View/CircularProgressView.swift b/UIToolkits/UIToolkits/_View/CircularProgressView.swift new file mode 100644 index 000000000..65bf583be --- /dev/null +++ b/UIToolkits/UIToolkits/_View/CircularProgressView.swift @@ -0,0 +1,88 @@ +// +// CircularProgressView.swift +// UIToolkits +// +// Created by Qiang Huang on 9/21/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +@objc public class CircularProgressView: UIView { + public var progressLayer: CircularProgressBar? { + didSet { + if progressLayer !== oldValue { + oldValue?.removeFromSuperlayer() + if let progressLayer = progressLayer { + layer.addSublayer(progressLayer) + } + } + } + } + + public var size: CGSize? + + @IBInspectable public var radius: CGFloat = 10.0 + @IBInspectable public var lineWidth: CGFloat = 2.0 + @IBInspectable public var innerTrackColor: UIColor? = nil { + didSet { + if innerTrackColor !== oldValue { + progressLayer?.innerTrackShapeLayer.strokeColor = innerTrackColor?.cgColor + } + } + } + + @IBInspectable public var outerTrackColor: UIColor? = nil { + didSet { + if outerTrackColor !== oldValue { + progressLayer?.outerTrackShapeLayer.strokeColor = outerTrackColor?.cgColor + } + } + } + + private var drawingDebouncer = Debouncer() + + public var progress: CGFloat = 0.0 { + didSet { + drawProgress() + } + } + + override open func awakeFromNib() { + super.awakeFromNib() + redrawSelf() + } + + public override var bounds: CGRect { + didSet { + redrawSelf() + } + } + + public func redraw() { + drawingDebouncer.debounce()?.run({ [weak self] in + self?.redrawSelf() + }, delay: 0.05) + } + + private func redrawSelf() { + let width = size?.width ?? bounds.width + let height = size?.height ?? bounds.height + let xPosition = width / 2.0 + let yPosition = height / 2.0 + let position = CGPoint(x: xPosition, y: yPosition) + progressLayer = CircularProgressBar(radius: min(width, height) / 2.0, position: position, innerTrackColor: innerTrackColor ?? UIColor.label, outerTrackColor: outerTrackColor ?? UIColor.systemBackground, lineWidth: lineWidth) + drawProgress() + } + + private func drawProgress() { + if progress > 1.0 { + progressLayer?.progress = 100.0 + } else if progress < 0.0 { + progressLayer?.progress = 0.0 + } else { + progressLayer?.progress = progress * 100.0 + } + } +} diff --git a/UIToolkits/UIToolkits/_View/CollectionViewGridLayout.swift b/UIToolkits/UIToolkits/_View/CollectionViewGridLayout.swift new file mode 100644 index 000000000..4e117422b --- /dev/null +++ b/UIToolkits/UIToolkits/_View/CollectionViewGridLayout.swift @@ -0,0 +1,13 @@ +// +// CollectionViewGridLayout.swift +// UIToolkits +// +// Created by Qiang Huang on 1/19/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import RDHCollectionViewGridLayout +import UIKit + +public class CollectionViewGridLayout: RDHCollectionViewGridLayout { +} diff --git a/UIToolkits/UIToolkits/_View/DrawingView.swift b/UIToolkits/UIToolkits/_View/DrawingView.swift new file mode 100644 index 000000000..16a03ee19 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/DrawingView.swift @@ -0,0 +1,131 @@ +// +// DrawingView.swift +// UIToolkits +// +// Created by Qiang Huang on 1/14/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +open class DrawingView: UIView { + @IBInspectable public var lineColor: UIColor = UIColor.black + @IBInspectable public var lineWidth: CGFloat = 4.0 + + @objc public dynamic var path: UIBezierPath? { + didSet { + if path !== oldValue { + drawingLayer = buildDrawingLayer(path: path) + } + } + } + + private var drawingLayer: CAShapeLayer? { + didSet { + if drawingLayer !== oldValue { + oldValue?.removeFromSuperlayer() + if let drawingLayer = drawingLayer { + layer.addSublayer(drawingLayer) + } + setNeedsDisplay() + } + } + } + + @objc public dynamic var points: [CGPoint]? + + private var last: CGPoint? { + didSet { + if last != oldValue { + if let last = last { + if points == nil { + points = [] + } + points?.append(last) + if let previous = oldValue { + if let path = path { + path.addLine(to: last) + setNeedsDisplay() + } else { + let path = UIBezierPath() + path.move(to: previous) + path.addLine(to: last) + self.path = path + } + } + } else { + path = nil + points = nil + } + } + } + } + + public override init(frame: CGRect) { + super.init(frame: frame) + setupDrawing() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + setupDrawing() + } + + open override func awakeFromNib() { + super.awakeFromNib() + setupDrawing() + } + + open func setupDrawing() { + clipsToBounds = true + isMultipleTouchEnabled = false + } + + open override func draw(_ rect: CGRect) { + super.draw(rect) + path?.stroke() + } + + open override func touchesBegan(_ touches: Set, with event: UIEvent?) { + if let touch = event?.allTouches?.first { + last = touch.location(in: self) + } + } + + open override func touchesMoved(_ touches: Set, with event: UIEvent?) { + if let touch = event?.allTouches?.first { + last = touch.location(in: self) + } + } + + open override func touchesEnded(_ touches: Set, with event: UIEvent?) { + if let touch = event?.allTouches?.first { + last = touch.location(in: self) + end() + } + } + + open override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + last = nil + } + + private func buildDrawingLayer(path: UIBezierPath?) -> CAShapeLayer? { + if let path = path { + let layer = CAShapeLayer() + layer.path = path.cgPath + layer.strokeColor = lineColor.cgColor + layer.lineWidth = lineWidth + layer.fillColor = UIColor.clear.cgColor + path.lineWidth = lineWidth + Console.shared.log("line width: \(lineWidth)") + return layer + } else { + return nil + } + } + + private func end() { + last = nil + } +} diff --git a/UIToolkits/UIToolkits/_View/GradientSlider.swift b/UIToolkits/UIToolkits/_View/GradientSlider.swift new file mode 100644 index 000000000..8efa29d6d --- /dev/null +++ b/UIToolkits/UIToolkits/_View/GradientSlider.swift @@ -0,0 +1,119 @@ +// +// GradientSlider.swift +// UIToolkits +// +// Created by Qiang Huang on 10/15/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import UIKit +import Utilities + +public class GradientSlider: UISlider { + public var gradientImage: UIImage? + + private var appliedGradientImage: UIImage? { + didSet { + if let appliedGradientImage = appliedGradientImage { + let cappedImage = appliedGradientImage.resizableImage(withCapInsets: .zero) + setMinimumTrackImage(cappedImage, for: .normal) + setMaximumTrackImage(cappedImage, for: .normal) + } else { + setMinimumTrackImage(nil, for: .normal) + setMaximumTrackImage(nil, for: .normal) + } + } + } + + public var leftPercentage: CGFloat = 0.0 { + didSet { + if leftPercentage != oldValue { + calculateGradient() + } + } + } + + public var rightPercentage: CGFloat = 0.0 { + didSet { + if rightPercentage != oldValue { + calculateGradient() + } + } + } + + private var trackSize: CGSize? { + didSet { + if trackSize != oldValue { + calculateGradient() + } + } + } + + private var gradientDebouncer = Debouncer() + + override public func trackRect(forBounds bounds: CGRect) -> CGRect { + let result = super.trackRect(forBounds: bounds) + let origin: CGFloat = 8.0 + let diff = origin - result.origin.x + let modified = CGRect(x: origin, y: result.origin.y, width: result.size.width - diff * 2, height: result.size.height) + trackSize = modified.size + return modified + } + + private func calculateGradient() { + gradientDebouncer.debounce()?.run({ [weak self] in + self?.reallyCalculateGradient() + }, delay: 0.0) + } + + private func reallyCalculateGradient() { + if let trackSize = trackSize, trackSize.width > 0.0, trackSize.height > 0.0 { + appliedGradientImage = gradientImage?.resize(to: trackSize, leftPercentage: leftPercentage, rightPercentage: rightPercentage) + } else { + appliedGradientImage = nil + } + } +} + +extension UIImage { + var isPortrait: Bool { size.height > size.width } + var isLandscape: Bool { size.width > size.height } + var breadth: CGFloat { min(size.width, size.height) } + var breadthSize: CGSize { .init(width: breadth, height: breadth) } + var breadthRect: CGRect { .init(origin: .zero, size: breadthSize) } + func rounded(with color: UIColor, width: CGFloat) -> UIImage? { + guard let cgImage = cgImage?.cropping(to: .init(origin: .init(x: isLandscape ? ((size.width - size.height) / 2).rounded(.down) : .zero, y: isPortrait ? ((size.height - size.width) / 2).rounded(.down) : .zero), size: breadthSize)) else { return nil } + + let bleed = breadthRect.insetBy(dx: -width, dy: -width) + let format = imageRendererFormat + format.opaque = false + + return UIGraphicsImageRenderer(size: bleed.size, format: format).image { context in + UIBezierPath(ovalIn: .init(origin: .zero, size: bleed.size)).addClip() + var strokeRect = breadthRect.insetBy(dx: -width / 2, dy: -width / 2) + strokeRect.origin = .init(x: width / 2, y: width / 2) + UIImage(cgImage: cgImage, scale: 1, orientation: imageOrientation) + .draw(in: strokeRect.insetBy(dx: width / 2, dy: width / 2)) + context.cgContext.setStrokeColor(color.cgColor) + let line: UIBezierPath = .init(ovalIn: strokeRect) + line.lineWidth = width + line.stroke() + } + } +} + +extension UIImage { + func imageWithInsets(insets: UIEdgeInsets) -> UIImage? { + autoreleasepool { + UIGraphicsBeginImageContextWithOptions( + CGSize(width: size.width + insets.left + insets.right, + height: size.height + insets.top + insets.bottom), false, scale) + _ = UIGraphicsGetCurrentContext() + let origin = CGPoint(x: insets.left, y: insets.top) + draw(at: origin) + let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return imageWithInsets + } + } +} diff --git a/UIToolkits/UIToolkits/_View/GradientView.swift b/UIToolkits/UIToolkits/_View/GradientView.swift new file mode 100644 index 000000000..09e86548a --- /dev/null +++ b/UIToolkits/UIToolkits/_View/GradientView.swift @@ -0,0 +1,46 @@ +// +// GradientView.swift +// UIToolkits +// +// Created by Qiang Huang on 9/6/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import UIKit + +@objc public class GradientView: UIView { + override open class var layerClass: AnyClass { + return CAGradientLayer.classForCoder() + } + @IBInspectable var startColor: UIColor? + @IBInspectable var endColor: UIColor? + + private var gradientLayer: CAGradientLayer? { + return layer as? CAGradientLayer + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + public override func awakeFromNib() { + super.awakeFromNib() + gradientLayer?.startPoint = CGPoint(x: 0.0, y: 0.5) + gradientLayer?.endPoint = CGPoint(x: 1.0, y: 0.5) + setupLayer() + } + + public func set(startColor: UIColor?, endColor: UIColor?) { + self.startColor = startColor + self.endColor = endColor + setupLayer() + } + + private func setupLayer() { + if let startColor = startColor, let endColor = endColor { + gradientLayer?.colors = [startColor.cgColor, endColor.cgColor] + } else { + gradientLayer?.colors = nil + } + } +} diff --git a/UIToolkits/UIToolkits/_View/ImageAddCollectionViewCell.swift b/UIToolkits/UIToolkits/_View/ImageAddCollectionViewCell.swift new file mode 100644 index 000000000..e97f3b86e --- /dev/null +++ b/UIToolkits/UIToolkits/_View/ImageAddCollectionViewCell.swift @@ -0,0 +1,30 @@ +// +// ImageAddCollectionViewCell.swift +// UIToolkits +// +// Created by Qiang Huang on 11/28/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit + +open class ImageAddCollectionViewCell: UICollectionViewCell { + @IBOutlet var imageView: UIImageView? + @IBOutlet var spinner: SpinnerProtocol? + + public var spinning: Bool = false { + didSet { + if spinning != oldValue { + if spinning { + spinner?.visible = true + spinner?.spinning = true + imageView?.visible = false + } else { + spinner?.visible = false + spinner?.spinning = false + imageView?.visible = true + } + } + } + } +} diff --git a/UIToolkits/UIToolkits/_View/ImageCollectionViewCell.swift b/UIToolkits/UIToolkits/_View/ImageCollectionViewCell.swift new file mode 100644 index 000000000..e22f5e9c2 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/ImageCollectionViewCell.swift @@ -0,0 +1,34 @@ +// +// ImageCollectionViewCell.swift +// UIToolkits +// +// Created by Qiang Huang on 10/15/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIKit + +open class ImageCollectionViewCell: UICollectionViewCell { + @IBOutlet var imageView: CachedImageView? { + didSet { + imageView?.backgroundColor = UIColor.black + } + } + + public var image: String? { + didSet { + if image != oldValue { + if let image = image, let url = image.components(separatedBy: "@").first { + imageUrl = URL(string:url) + } else { + imageUrl = nil + } + } + } + } + + public var imageUrl: URL? { + get { return imageView?.imageUrl } + set { imageView?.imageUrl = newValue } + } +} diff --git a/UIToolkits/UIToolkits/_View/ImageTableViewCell.swift b/UIToolkits/UIToolkits/_View/ImageTableViewCell.swift new file mode 100644 index 000000000..2e2ea1446 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/ImageTableViewCell.swift @@ -0,0 +1,22 @@ +// +// ImageTableViewCell.swift +// UIToolkits +// +// Created by Qiang Huang on 11/28/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit + +open class ImageTableViewCell: UITableViewCell { + @IBOutlet var imageUrlView: CachedImageView? { + didSet { + imageUrlView?.backgroundColor = UIColor.black + } + } + + public var imageUrl: URL? { + get { return imageUrlView?.imageUrl } + set { imageUrlView?.imageUrl = newValue } + } +} diff --git a/UIToolkits/UIToolkits/_View/LabelView.swift b/UIToolkits/UIToolkits/_View/LabelView.swift new file mode 100644 index 000000000..18ce945ab --- /dev/null +++ b/UIToolkits/UIToolkits/_View/LabelView.swift @@ -0,0 +1,89 @@ +// +// LabelView.swift +// UIToolkits +// +// Created by Qiang Huang on 10/14/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIKit + +@objc open class LabelView: UIView, LabelProtocol { + public var textColor: NativeColor! { + get { + return label?.textColor ?? UIColor.black + } + set { + label?.textColor = newValue + } + } + + public var font: NativeFont! { + get { + return label?.font ?? UIFont.systemFont(ofSize: 14) + } + set { + label?.font = newValue + } + } + + @IBOutlet public var view: UIView? + @IBOutlet public var label: UILabel? + @IBOutlet public var sublabel: UILabel? { + didSet { + if sublabel !== oldValue { + if subtext != nil { + sublabel?.text = subtext + } else { + subtext = sublabel?.text + } + } + } + } + + open var attributedText: NSAttributedString? { + get { + return label?.attributedText + } + set { + label?.attributedText = newValue + updateVisibility() + } + } + + private var _text: String? + + open var text: String? { + get { + return _text + } + set { + label?.text = newValue + _text = newValue + updateVisibility() + } + } + + open var subtext: String? { + didSet { + if subtext != oldValue { + sublabel?.text = subtext + } + } + } + + override open func awakeFromNib() { + super.awakeFromNib() + text = nil + } + + open func updateVisibility() { + visible = (_text != nil) + } + + public func formatUrl(text: String?) -> URL? { + let url = label?.formatUrl(text: text) + updateVisibility() + return url + } +} diff --git a/UIToolkits/UIToolkits/_View/OnOffSwitch.swift b/UIToolkits/UIToolkits/_View/OnOffSwitch.swift new file mode 100644 index 000000000..ac6bb5328 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/OnOffSwitch.swift @@ -0,0 +1,53 @@ +// +// OnOffSwitch.swift +// UIToolkits +// +// Created by Qiang Huang on 11/13/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import UIKit + +@objc public class OnOffSwitch: UISwitch { + @IBInspectable var onColor: UIColor? + @IBInspectable var offColor: UIColor? + + @IBInspectable var onThumbColor: UIColor? + @IBInspectable var offThumbColor: UIColor? + + override public var isOn: Bool { + didSet { + updateThumbColor(animated: true) + } + } + + override public func awakeFromNib() { + super.awakeFromNib() + + /* For on state */ + if let onColor = onColor { + onTintColor = onColor + } + + /* For off state */ + if let offColor = offColor { + tintColor = offColor + layer.cornerRadius = frame.height / 2.0 + layer.backgroundColor = offColor.cgColor + layer.opacity = 1.0 + backgroundColor = offColor + alpha = 1.0 + clipsToBounds = true + } + updateThumbColor(animated: false) + } + + private func updateThumbColor(animated: Bool) { + if let onThumbColor = onThumbColor, let offThumbColor = offThumbColor { + UIView.animate(self, type: animated ? .fade : .none, direction: .none, duration: UIView.defaultAnimationDuration) { [weak self] in + self?.thumbTintColor = (self?.isOn == true) ? onThumbColor : offThumbColor + } completion: { _ in + } + } + } +} diff --git a/UIToolkits/UIToolkits/_View/OutlineLabel.swift b/UIToolkits/UIToolkits/_View/OutlineLabel.swift new file mode 100644 index 000000000..bc49e1c04 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/OutlineLabel.swift @@ -0,0 +1,25 @@ +// +// OutlineLabel.swift +// UIToolkits +// +// Created by Qiang Huang on 8/29/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +class OutlinedLabel: UILabel { + @IBInspectable public var outlineWidth: CGFloat = 1 + @IBInspectable public var outlineColor: UIColor = UIColor.label + + override func drawText(in rect: CGRect) { + let strokeTextAttributes: [NSAttributedString.Key: Any] = [ + NSAttributedString.Key.strokeColor: outlineColor, + NSAttributedString.Key.strokeWidth: -1 * outlineWidth, + ] + + attributedText = NSAttributedString(string: text ?? "", attributes: strokeTextAttributes) + super.drawText(in: rect) + } +} diff --git a/UIToolkits/UIToolkits/_View/OverlayView.swift b/UIToolkits/UIToolkits/_View/OverlayView.swift new file mode 100644 index 000000000..9392b9cc5 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/OverlayView.swift @@ -0,0 +1,119 @@ +// +// OverlayView.swift +// UIToolkits +// +// Created by Qiang Huang on 7/26/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +@objc public enum EOverlayType: Int { + case none + case rect + case cycle +} + +@objc open class OverlayView: UIView { + @IBInspectable var intOverlayType: Int = 0 { + didSet { + if intOverlayType != oldValue { + updateOverlay() + } + } + } + + var overlayType: EOverlayType { + get { return EOverlayType(rawValue: intOverlayType) ?? .none } + set { intOverlayType = newValue.rawValue } + } + + @IBInspectable public var overlayCorner: CGFloat = 0.0 { + didSet { + if overlayCorner != oldValue { + updateOverlay() + } + } + } + + @IBInspectable public var overlayColor: UIColor? { + didSet { + if overlayColor !== oldValue { + updateOverlay() + } + } + } + + open var overlay: CALayer? { + didSet { + if overlay !== oldValue { + oldValue?.removeFromSuperlayer() + if let overlay = overlay { + layer.addSublayer(overlay) + } + } + } + } + + private var updateOverlayDebouncer: Debouncer = Debouncer() + + open func updateOverlay() { + if overlayColor != nil, overlayType != .none, overlayCorner != 0.0 { + let handler = updateOverlayDebouncer.debounce() + handler?.run({ [weak self] in + if let self = self { + if let path = self.path() { + self.overlay = self.layer(path: path) + } else { + self.overlay = nil + } + } + }, delay: 0) + } + } + + open func layer(path: UIBezierPath) -> CALayer? { + if let overlayColor = overlayColor { + let layer = CAShapeLayer() + layer.path = path.cgPath + layer.fillRule = CAShapeLayerFillRule.evenOdd + layer.fillColor = overlayColor.cgColor + layer.opacity = 1.0 + return layer + } + return nil + } + + open func path() -> UIBezierPath? { + if let overlayPath = self.overlayPath() { + let path = UIBezierPath(rect: bounds) + path.append(overlayPath) + return path + } + return nil + } + + open func rectPath() -> UIBezierPath? { + return UIBezierPath(roundedRect: bounds, cornerRadius: overlayCorner) + } + + open func cyclePath() -> UIBezierPath? { + let radius = min(bounds.width, bounds.height) + let circle = CGRect(x: (bounds.width - radius) / 2.0, y: (bounds.height - radius) / 2.0, width: radius, height: radius) + return UIBezierPath(ovalIn: circle) + } + + open func overlayPath() -> UIBezierPath? { + switch overlayType { + case .rect: + return rectPath() + + case .cycle: + return cyclePath() + + default: + return nil + } + } +} diff --git a/UIToolkits/UIToolkits/_View/SelfSizingCollectionView.swift b/UIToolkits/UIToolkits/_View/SelfSizingCollectionView.swift new file mode 100644 index 000000000..9281a62d8 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/SelfSizingCollectionView.swift @@ -0,0 +1,22 @@ +// +// SelfSizingCollectionView.swift +// UIToolkits +// +// Created by Qiang Huang on 9/1/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIKit + +open class SelfSizingCollectionView: UICollectionView { + open override func layoutSubviews() { + super.layoutSubviews() + if !__CGSizeEqualToSize(bounds.size, intrinsicContentSize) { + invalidateIntrinsicContentSize() + } + } + + open override var intrinsicContentSize: CGSize { + return contentSize + } +} diff --git a/UIToolkits/UIToolkits/_View/SelfSizingTableView.swift b/UIToolkits/UIToolkits/_View/SelfSizingTableView.swift new file mode 100644 index 000000000..38544650d --- /dev/null +++ b/UIToolkits/UIToolkits/_View/SelfSizingTableView.swift @@ -0,0 +1,22 @@ +// +// SelfSizingTableView.swift +// UIToolkits +// +// Created by Qiang Huang on 1/19/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit + +open class SelfSizingTableView: UITableView { + open override func layoutSubviews() { + super.layoutSubviews() + if !__CGSizeEqualToSize(bounds.size, intrinsicContentSize) { + invalidateIntrinsicContentSize() + } + } + + open override var intrinsicContentSize: CGSize { + return contentSize + } +} diff --git a/UIToolkits/UIToolkits/_View/SpinImageView.swift b/UIToolkits/UIToolkits/_View/SpinImageView.swift new file mode 100644 index 000000000..bd45e60ec --- /dev/null +++ b/UIToolkits/UIToolkits/_View/SpinImageView.swift @@ -0,0 +1,62 @@ +// +// SpinImageView.swift +// UIToolkits +// +// Created by Qiang Huang on 11/6/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import UIKit + +@objc public class SpinImageView: UIImageView { + public var rotating: Bool = false { + didSet { + if rotating != oldValue { + if rotating { + reallyRotating = true + } else { + } + } + } + } + + private var reallyRotating: Bool = false { + didSet { + if reallyRotating != oldValue { + if reallyRotating { + rotate() + } + } + } + } + + private var upsideDown: Bool = false { + didSet { + if upsideDown != oldValue { + if upsideDown { + rotate() + } else { + if rotating { + rotate() + } else { + reallyRotating = false + } + } + } + } + } + + func rotate() { + let kAnimationDuration = 1.0 + UIView.animate(withDuration: kAnimationDuration, delay: 0, options: .curveLinear) { [weak self] in + if let self = self { + let radians = self.upsideDown ? Double.pi * 2.0 : Double.pi + self.transform = CGAffineTransform(rotationAngle: radians) + } + } completion: { [weak self] _ in + if let self = self { + self.upsideDown = !self.upsideDown + } + } + } +} diff --git a/UIToolkits/UIToolkits/_View/TappableLabel.swift b/UIToolkits/UIToolkits/_View/TappableLabel.swift new file mode 100644 index 000000000..f202c38d6 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/TappableLabel.swift @@ -0,0 +1,13 @@ +// +// TappableLabel.swift +// UIToolkits +// +// Created by Qiang Huang on 11/13/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import UIKit +import ZSWTappableLabel + +@objc public class TappableLabel: ZSWTappableLabel { +} diff --git a/UIToolkits/UIToolkits/_View/TextCollectionViewCell.swift b/UIToolkits/UIToolkits/_View/TextCollectionViewCell.swift new file mode 100644 index 000000000..ef9cbab40 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/TextCollectionViewCell.swift @@ -0,0 +1,73 @@ +// +// TextCollectionViewCell.swift +// UIToolkits +// +// Created by Qiang Huang on 10/15/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIKit + +open class TextCollectionViewCell: UICollectionViewCell { + @IBInspectable var unselectedBackgroundColor: String? + @IBInspectable var selectedBackgroundColor: String? + @IBInspectable var unselectedTextColor: String? + @IBInspectable var selectedTextColor: String? + + @IBOutlet var view: UIView? { + didSet { + updateTextColor() + updateSelected() + } + } + + @IBOutlet var textLabel: LabelProtocol? { + didSet { + updateTextColor() + updateSelected() + } + } + + override open var isSelected: Bool { + didSet { + if isSelected != oldValue { + updateSelected() + } + } + } + + public var text: String? { + get { return textLabel?.text } + set { textLabel?.text = newValue } + } + + open func updateTextColor() { + textLabel?.textColor = view?.borderColor + } + + open func updateSelected() { + if isSelected { + if let selectedTextColor = selectedTextColor { + textLabel?.textColor = ColorPalette.shared.color(system: selectedTextColor) + } else { + view?.backgroundColor = textLabel?.textColor + } + if let selectedBackgroundColor = selectedBackgroundColor { + view?.backgroundColor = ColorPalette.shared.color(system: selectedBackgroundColor) + } else { + textLabel?.textColor = view?.borderColor + } + } else { + if let unselectedTextColor = unselectedTextColor { + textLabel?.textColor = ColorPalette.shared.color(system: unselectedTextColor) + } else { + textLabel?.textColor = view?.backgroundColor + } + if let unselectedBackgroundColor = unselectedBackgroundColor { + view?.backgroundColor = ColorPalette.shared.color(system: unselectedBackgroundColor) + } else { + view?.backgroundColor = view?.borderColor + } + } + } +} diff --git a/UIToolkits/UIToolkits/_View/TextTableViewCell.swift b/UIToolkits/UIToolkits/_View/TextTableViewCell.swift new file mode 100644 index 000000000..95d363f2a --- /dev/null +++ b/UIToolkits/UIToolkits/_View/TextTableViewCell.swift @@ -0,0 +1,54 @@ +// +// TextTableViewCell.swift +// UIToolkits +// +// Created by Qiang Huang on 11/28/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit + +open class TextTableViewCell: UITableViewCell { + @IBOutlet var view: UIView? { + didSet { + updateTextColor() + } + } + + @IBOutlet var titleLabel: LabelProtocol? { + didSet { + updateTextColor() + } + } + + @IBOutlet var checkmark: UIImageView? + + override open var isSelected: Bool { + didSet { + updateSelected() + } + } + + public var title: String? { + get { return titleLabel?.text } + set { titleLabel?.text = newValue } + } + + open func updateTextColor() { + titleLabel?.textColor = view?.borderColor + } + + open func updateSelected() { + if let checkmark = checkmark { + checkmark.visible = isSelected + } else { + if isSelected { + titleLabel?.textColor = view?.backgroundColor + view?.backgroundColor = view?.borderColor + } else { + view?.backgroundColor = titleLabel?.textColor + titleLabel?.textColor = view?.borderColor + } + } + } +} diff --git a/UIToolkits/UIToolkits/_View/ThinlineView.swift b/UIToolkits/UIToolkits/_View/ThinlineView.swift new file mode 100644 index 000000000..90b7d8922 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/ThinlineView.swift @@ -0,0 +1,18 @@ +// +// ThinlineView.swift +// UIToolkits +// +// Created by Qiang Huang on 11/9/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +public class ThinlineView: UIView { + @IBOutlet var height: NSLayoutConstraint? { + didSet { + if height !== oldValue { + let scale = UIScreen.main.scale + height?.constant = 1.0 / scale + } + } + } +} diff --git a/UIToolkits/UIToolkits/_View/TooltipManager.swift b/UIToolkits/UIToolkits/_View/TooltipManager.swift new file mode 100644 index 000000000..9c5e1834d --- /dev/null +++ b/UIToolkits/UIToolkits/_View/TooltipManager.swift @@ -0,0 +1,26 @@ +// +// TooltipManager.swift +// UIToolkits +// +// Created by John Huang on 1/19/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import EasyTipView +import UIKit + +@objc public class TooltipManager: NSObject { + public static var shared = TooltipManager() + + public var preferences: EasyTipView.Preferences = { + var pref = EasyTipView.Preferences() + pref.drawing.arrowPosition = EasyTipView.ArrowPosition.top + return pref + }() + + public func show(from: UIView?, superview: UIView? = nil, text: String?) { + if let from = from, let text = text { + EasyTipView.show(forView: from, withinSuperview: superview, text: text, preferences: preferences, delegate: nil) + } + } +} diff --git a/UIToolkits/UIToolkits/_View/UXImageView.swift b/UIToolkits/UIToolkits/_View/UXImageView.swift new file mode 100644 index 000000000..cc2738ee2 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/UXImageView.swift @@ -0,0 +1,28 @@ +// +// UXImageView.swift +// UIToolkits +// +// Created by Qiang Huang on 9/16/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +open class UXImageView: UIImageView { + private var changed: Bool = false + + open override var image: UIImage? { + didSet { + changed = true + } + } + + open override func awakeFromNib() { + let changed = changed + super.awakeFromNib() + if !changed { + image = nil + } + } +} diff --git a/UIToolkits/UIToolkits/_View/UXLabel.swift b/UIToolkits/UIToolkits/_View/UXLabel.swift new file mode 100644 index 000000000..08fc09cbb --- /dev/null +++ b/UIToolkits/UIToolkits/_View/UXLabel.swift @@ -0,0 +1,36 @@ +// +// UXLabel.swift +// UIToolkits +// +// Created by Qiang Huang on 1/19/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +open class UXLabel: UILabel { + @IBInspectable public var italic: Bool = false + @IBInspectable public var placeOlder: String? + + private var changed: Bool = false + + open override var text: String? { + didSet { + changed = true + } + } + + open override func awakeFromNib() { + let changed = changed + super.awakeFromNib() + if italic { + if let font = self.font { + self.font = font.italic() + } + } + if !changed { + text = placeOlder + } + } +} diff --git a/UIToolkits/UIToolkits/_View/UXSearchBar.swift b/UIToolkits/UIToolkits/_View/UXSearchBar.swift new file mode 100644 index 000000000..452768129 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/UXSearchBar.swift @@ -0,0 +1,15 @@ +// +// UXSearchBar.swift +// UIToolkits +// +// Created by Qiang Huang on 10/19/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIKit + +open class UXSearchBar: UISearchBar { + open override var intrinsicContentSize: CGSize { + return CGSize(width: bounds.size.width, height: 44) + } +} diff --git a/UIToolkits/UIToolkits/_View/UXSegmentedControl.swift b/UIToolkits/UIToolkits/_View/UXSegmentedControl.swift new file mode 100644 index 000000000..01ba0b870 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/UXSegmentedControl.swift @@ -0,0 +1,50 @@ +// +// UXSegmentedControl.swift +// UXSegmentedControl +// +// Created by Qiang Huang on 7/28/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import HMSegmentedControl +import UIKit + +@objc public class UXSegmentedControl: HMSegmentedControl { + override public var sectionTitles: [String]? { + didSet { + selectedSegmentIndex = HMSegmentedControlNoSegment + } + } + + override public func awakeFromNib() { + super.awakeFromNib() + backgroundColor = UIColor.systemBackground + segmentWidthStyle = .dynamic + selectionIndicatorBoxColor = UIColor.link + selectionStyle = .fullWidthStripe + selectionIndicatorHeight = 2 + selectionIndicatorLocation = .bottom + selectionIndicatorBoxOpacity = 0.1 + titleTextAttributes = titleAttributes + selectedTitleTextAttributes = selectedTitleAttributes + selectedSegmentIndex = HMSegmentedControlNoSegment + } + + var titleAttributes: [NSAttributedString.Key: Any] { + let font = UIFont.systemFont(ofSize: 14, weight: .medium) + let attributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: UIColor.secondaryLabel, + ] + return attributes + } + + var selectedTitleAttributes: [NSAttributedString.Key: Any] { + let font = UIFont.systemFont(ofSize: 14, weight: .medium) + let attributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: UIColor.link, + ] + return attributes + } +} diff --git a/UIToolkits/UIToolkits/_View/UXTableView.swift b/UIToolkits/UIToolkits/_View/UXTableView.swift new file mode 100644 index 000000000..67312b0ed --- /dev/null +++ b/UIToolkits/UIToolkits/_View/UXTableView.swift @@ -0,0 +1,81 @@ +// +// UXTableView.swift +// UIToolkits +// +// Created by John Huang on 10/19/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +open class UXTableView: UITableView { + @IBInspectable var clearFooter: Bool = false { + didSet { + if clearFooter != oldValue { + if clearFooter { + if tableFooterView == nil { + tableFooterView = clearView + } + } else { + if tableFooterView == clearView { + tableFooterView = nil + } + } + } + } + } + + private var clearView: UIView = UIView() + + private var debouncer: Debouncer = Debouncer() + + var rowDataBounds: CGRect { + if numberOfSections <= 0 { + return CGRect(x: 0, y: 0, width: frame.width, height: 0) + } else { + let minRect = rect(forSection: 0) + let maxRect = rect(forSection: numberOfSections - 1) + return maxRect.union(minRect) + } + } + + fileprivate func resizeFooterView() { + if let footerView = tableFooterView { + var newHeight: CGFloat = 0 + let tableFrame = self.frame + + if #available(iOS 10, *) { + newHeight = tableFrame.size.height - rowDataBounds.height - self.contentInset.bottom - self.contentInset.top + } else { + newHeight = tableFrame.size.height - contentSize.height + } + if newHeight < 0 { + newHeight = 0 + } + let frame = footerView.frame + if newHeight != frame.height { + footerView.frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: newHeight) + } + } + } + + public func updateLayout() { + if let handler = debouncer.debounce() { + handler.run({ [weak self] in + if let self = self { + if !self.isDragging { + self.beginUpdates() + self.endUpdates() + } + } + }, delay: 0.02) + } + } + + override open func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + beginUpdates() + endUpdates() + } +} diff --git a/UIToolkits/UIToolkits/_View/UserInteraction.swift b/UIToolkits/UIToolkits/_View/UserInteraction.swift new file mode 100644 index 000000000..ed2d2f1db --- /dev/null +++ b/UIToolkits/UIToolkits/_View/UserInteraction.swift @@ -0,0 +1,16 @@ +// +// UserInteraction.swift +// UIToolkits +// +// Created by Qiang Huang on 1/19/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Utilities + +public final class UserInteraction: NSObject, SingletonProtocol { + public static var shared: UserInteraction = UserInteraction() + + public var sender: Any? + public var rect: CGRect? +} diff --git a/UIToolkits/UIToolkits/_View/_Segments/CollectionViewSegmentedControl.swift b/UIToolkits/UIToolkits/_View/_Segments/CollectionViewSegmentedControl.swift new file mode 100644 index 000000000..7cc122f29 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/_Segments/CollectionViewSegmentedControl.swift @@ -0,0 +1,165 @@ +// +// CollectionViewSegmentedControl.swift +// CollectionViewSegmentedControl +// +// Created by Qiang Huang on 8/25/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import UIKit + +@objc open class CollectionViewSegmentedControl: CustomSegmentedControl, UICollectionViewDataSource, UICollectionViewDelegate, UIScrollViewDelegate { + @IBInspectable var rightAligned: Bool = false { + didSet { + if rightAligned != oldValue { + orientCollecitonView() + } + } + } + + @IBOutlet var collectionView: UICollectionView? { + didSet { + oldValue?.dataSource = nil + oldValue?.delegate = nil + collectionView?.dataSource = self + collectionView?.delegate = self + collectionView?.allowsSelection = true + collectionView?.allowsMultipleSelection = false + orientCollecitonView() + + if let cellXib = cellXib, let nib = UINib.safeLoad(xib: cellXib, bundles: Bundle.particles) { + collectionView?.register(nib, forCellWithReuseIdentifier: "cell") + } + } + } + + public static func segments(with titles: [String]) -> SegmentedProtocol { + let control = CollectionViewSegmentedControl() + let collectionView = UICollectionView() + control.collectionView = collectionView + control.fill(titles: titles) + return control + } + + public static func segments(segments: [ControlSegment]) -> SegmentedProtocol { + let control = CollectionViewSegmentedControl() + let collectionView = UICollectionView() + control.collectionView = collectionView + control.fill(segments: segments) + return control + } + + open override func didSetSegments(oldValue: [ControlSegment]?) { + collectionView?.reloadData() + } + + open override func didSetSelectedIndex(oldValue: Int) { + super.didSetSelectedIndex(oldValue: oldValue) + if selectedIndex != -1 { + collectionView?.selectItem(at: indexPath(from: selectedIndex), animated: true, scrollPosition: .centeredHorizontally) + } else { + if oldValue != -1 { + collectionView?.deselectItem(at: indexPath(from: oldValue), animated: true) + } + } + } + + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) + if let textCell = cell as? SegmentCollectionViewCell { + let index = index(from: indexPath) + textCell.text = segments?[index].text + textCell.image = segments?[index].image + textCell.selectedImage = segments?[index].selectedImage + return cell + } + return cell + } + + open func index(from indexPath: IndexPath) -> Int { + if rightAligned { + return (segments?.count ?? 0) - indexPath.row - 1 + } else { + return indexPath.row + } + } + + open func indexPath(from index: Int) -> IndexPath { + if rightAligned { + return IndexPath(row: (segments?.count ?? 0) - index - 1, section: 0) + } else { + return IndexPath(row: index, section: 0) + } + } + + open func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + if rightAligned { + cell.contentView.transform = CGAffineTransform(scaleX: -1, y: 1) + } + } + + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return segments?.count ?? 0 + } + + open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + userInteracting = true + selectedIndex = index(from: indexPath) + userInteracting = false + } + + open func orientCollecitonView() { + if rightAligned { + collectionView?.transform = CGAffineTransform(scaleX: -1, y: 1) + } + } +} + +@objc open class SnapCollectionViewSegmentedControl: CollectionViewSegmentedControl, UICollectionViewDelegateFlowLayout { + @IBInspectable public var stretchItems: Bool = false + @IBInspectable public var itemSpacing: CGFloat = 8.0 + + override public var collectionView: UICollectionView? { + didSet { + if stretchItems { + let layout = CollectionViewGridLayout() + layout.itemSpacing = itemSpacing + layout.lineSize = collectionView?.frame.height ?? 44 + collectionView?.collectionViewLayout = layout + } + } + } + + override var segments: [ControlSegment]? { + didSet { + (collectionView?.collectionViewLayout as? CollectionViewGridLayout)?.lineItemCount = UInt(segments?.count ?? 1) + } + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let height = collectionView.bounds.size.height + if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout { + let width = collectionView.bounds.size.width + let contentWidth: CGFloat = (width - flowLayout.sectionInset.left - flowLayout.sectionInset.right + flowLayout.minimumLineSpacing) + let itemWidth = contentWidth / CGFloat(segments?.count ?? 1) - flowLayout.minimumLineSpacing + return CGSize(width: itemWidth, height: height) + } else { + assertionFailure("only support flow layout here") + return CGSize(width: 120, height: height) + } + } +} + +@objc open class ScrollableCollectionViewSegmentedControl: CollectionViewSegmentedControl { + public override var collectionView: UICollectionView? { + didSet { + changeObservation(from: oldValue, to: collectionView, keyPath: #keyPath(UICollectionView.collectionViewLayout)) { [weak self] _, _, _, _ in + if let self = self { + if let flowLayout = self.collectionView?.collectionViewLayout as? UICollectionViewFlowLayout { + flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + } + } + } + } + } +} diff --git a/UIToolkits/UIToolkits/_View/_Segments/CustomSegmentedControl.swift b/UIToolkits/UIToolkits/_View/_Segments/CustomSegmentedControl.swift new file mode 100644 index 000000000..7f3a6069a --- /dev/null +++ b/UIToolkits/UIToolkits/_View/_Segments/CustomSegmentedControl.swift @@ -0,0 +1,102 @@ +// +// CustomSegmentedControl.swift +// UIToolkits +// +// Created by Qiang Huang on 12/16/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import UIKit + +@objc open class ControlSegment: NSObject { + @objc public dynamic var text: String? + @objc public dynamic var image: String? + @objc public dynamic var selectedImage: String? + + public init(text: String?, image: String?, selectedImage: String? = nil) { + super.init() + self.text = text + self.image = image + self.selectedImage = selectedImage + } +} + +@objc open class CustomSegmentedControl: UIControl, SegmentedProtocol { + internal var segments: [ControlSegment]? { + didSet { + didSetSegments(oldValue: oldValue) + } + } + + internal var selectedSegment: ControlSegment? + + internal var userInteracting: Bool = false + + public var numberOfSegments: Int { + return segments?.count ?? 0 + } + + open var selectedIndex: Int { + get { + if let selectedSegment = selectedSegment { + return segments?.firstIndex(of: selectedSegment) ?? -1 + } else { + return -1 + } + } + set(newValue) { + if newValue < numberOfSegments { + let oldValue = selectedIndex + if newValue != oldValue { + if newValue != -1 { + selectedSegment = segments?[newValue] + } else { + selectedSegment = nil + } + + didSetSelectedIndex(oldValue: oldValue) + } + } + } + } + + @IBInspectable internal var cellXib: String? + + open func didSetSegments(oldValue: [ControlSegment]?) { + } + + open func didSetSelectedIndex(oldValue: Int) { + if userInteracting, selectedIndex != oldValue { + sendActions(for: .valueChanged) + } + } + + open func fill(titles: [String]?) { + if let titles = titles { + let segments = titles.map { text -> ControlSegment in + let segment = ControlSegment(text: text, image: nil, selectedImage: nil) + return segment + } + self.segments = segments + } else { + segments = nil + } + } + + open func fill(titles: [String]?, images: [String]?) { + if let titles = titles, let images = images, titles.count == images.count { + var segments = [ControlSegment]() + for i in 0 ..< titles.count { + let segment = ControlSegment(text: titles[i], image: images[i], selectedImage: nil) + segments.append(segment) + } + self.segments = segments + } else { + segments = nil + } + } + + open func fill(segments: [ControlSegment]?) { + self.segments = segments + } +} diff --git a/UIToolkits/UIToolkits/_View/_Segments/SegmentCollectionViewCell.swift b/UIToolkits/UIToolkits/_View/_Segments/SegmentCollectionViewCell.swift new file mode 100644 index 000000000..cea8c3173 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/_Segments/SegmentCollectionViewCell.swift @@ -0,0 +1,173 @@ +// +// TextCollectionViewCell.swift +// UIToolkits +// +// Created by Qiang Huang on 10/15/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import SwiftMessages +import UIKit + +open class SegmentCollectionViewCell: UICollectionViewCell { + @IBInspectable public var unselectedBackgroundColor: UIColor? + @IBInspectable public var selectedBackgroundColor: UIColor? + @IBInspectable public var unselectedTextColor: UIColor? + @IBInspectable public var selectedTextColor: UIColor? + @IBInspectable public var unselectedBorderColor: UIColor? + @IBInspectable public var selectedBorderColor: UIColor? + + @IBOutlet var view: UIView? { + didSet { + updateSelected(animated: false) + } + } + + @IBOutlet var selectionBar: UIView? { + didSet { + updateSelected(animated: false) + } + } + + @IBOutlet var textLabel: LabelProtocol? { + didSet { + updateSelected(animated: false) + } + } + + @IBOutlet var textLabel2: LabelProtocol? { + didSet { + textLabel2?.text = nil + } + } + + @IBOutlet var imageView: UIImageView? + + override open var isSelected: Bool { + didSet { + if isSelected != oldValue { + updateSelected(animated: true) + } + } + } + + public var text: String? { + get { + if let text = textLabel?.text { + if let text2 = textLabel2?.text { + return [text, text2].joined(separator: "\n") + } else { + return textLabel?.text + } + } else { + return nil + } + } + set { + let lines = newValue?.components(separatedBy: "\n") + textLabel?.text = lines?.first + if lines?.count == 2 { + textLabel2?.text = lines?.last + } else { + textLabel2?.text = nil + } + updateImage() + } + } + + public var image: String? { + didSet { + if image != oldValue { + if let image = image { + imageObj = UIImage.named(image, bundles: Bundle.particles) + } else { + imageObj = nil + } + } + } + } + + public var selectedImage: String? { + didSet { + if selectedImage != oldValue { + if let selectedImage = selectedImage { + selectedImageObj = UIImage.named(selectedImage, bundles: Bundle.particles) + } else { + selectedImageObj = nil + } + } + } + } + + private var imageObj: UIImage? { + didSet { + if imageObj !== oldValue { + updateImage() + } + } + } + + private var selectedImageObj: UIImage? { + didSet { + if selectedImageObj !== oldValue { + updateImage() + } + } + } + + open func updateTextColor() { + textLabel?.textColor = view?.borderColor + } + + open func updateSelected(animated: Bool) { + if isSelected { + if let selectedTextColor = selectedTextColor { + textLabel?.textColor = selectedTextColor + imageView?.tintColor = selectedTextColor + } + if let selectedBackgroundColor = selectedBackgroundColor { + if let selectionBar = selectionBar { + selectionBar.backgroundColor = selectedBackgroundColor + } else { + view?.backgroundColor = selectedBackgroundColor + } + } + if let selectedBorderColor = selectedBorderColor { + view?.borderColor = selectedBorderColor + } + } else { + if let unselectedTextColor = unselectedTextColor { + textLabel?.textColor = unselectedTextColor + imageView?.tintColor = unselectedTextColor + } + if let unselectedBackgroundColor = unselectedBackgroundColor { + if let selectionBar = selectionBar { + selectionBar.backgroundColor = unselectedBackgroundColor + } else { + view?.backgroundColor = unselectedBackgroundColor + } + } + if let unselectedBorderColor = unselectedBorderColor { + view?.borderColor = unselectedBorderColor + } + } + updateImage() + } + + open func updateImage() { + if imageObj != nil { + imageView?.visible = true + if isSelected { + if let selectedImageObj = selectedImageObj { + imageView?.image = selectedImageObj + } + } else { + if imageView?.image !== imageObj { + imageView?.image = imageObj + } + } + } else { + imageView?.visible = false + } + } +} diff --git a/UIToolkits/UIToolkits/_View/_Segments/StackViewSegmentedControl.swift b/UIToolkits/UIToolkits/_View/_Segments/StackViewSegmentedControl.swift new file mode 100644 index 000000000..ffbf19953 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/_Segments/StackViewSegmentedControl.swift @@ -0,0 +1,108 @@ +// +// StackViewSegmentedControl.swift +// UIToolkits +// +// Created by Qiang Huang on 12/16/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import UIKit +import Utilities + +@objc open class StackViewSegmentedControl: CustomSegmentedControl { + @IBOutlet var scrollView: UIScrollView? + @IBInspectable var unselectedBackgroundColor: UIColor? + @IBInspectable var unselectedTintColor: UIColor? + @IBInspectable var selectedBackgroundColor: UIColor? + @IBInspectable var selectedTintColor: UIColor? + + @IBOutlet var stackView: UIStackView? { + didSet { + reload() + } + } + + public static func segments(with titles: [String]) -> SegmentedProtocol { + let control = StackViewSegmentedControl() + let stackView = UIStackView() + control.stackView = stackView + control.fill(titles: titles) + return control + } + + public static func segments(segments: [ControlSegment]) -> SegmentedProtocol { + let control = StackViewSegmentedControl() + let stackView = UIStackView() + control.stackView = stackView + control.fill(segments: segments) + return control + } + + override open func didSetSegments(oldValue: [ControlSegment]?) { + reload() + } + + private func reload() { + if let views = stackView?.arrangedSubviews { + for view in views { + if let button = view as? UIButton { + button.removeTarget() + } + stackView?.removeArrangedSubview(view) + view.removeFromSuperview() + } + } + if let segments = segments, let cellXib = cellXib { + for i in 0 ..< segments.count { + let segment = segments[i] + let button: UIButton? = XibLoader.load(from: cellXib) + if let button = button { + button.buttonTitle = segment.text + button.addTarget(self, action: #selector(tap(_:))) + stackView?.addArrangedSubview(button) + select(button: button, selected: i == selectedIndex) + } + } + layoutIfNeeded() + } + } + + override open func didSetSelectedIndex(oldValue: Int) { + super.didSetSelectedIndex(oldValue: oldValue) + if let buttons = stackView?.arrangedSubviews as? [UIButton] { + if oldValue < buttons.count && oldValue != -1 { + let button = buttons[oldValue] + select(button: button, selected: false) + } + if selectedIndex < buttons.count && selectedIndex != -1 { + let button = buttons[selectedIndex] + select(button: button, selected: true) + } + } + scroll() + } + + private func select(button: UIButton, selected: Bool) { + button.backgroundColor = selected ? selectedBackgroundColor : unselectedBackgroundColor + button.buttonTitleColor = selected ? selectedTintColor : unselectedTintColor + button.tintColor = selected ? selectedTintColor : unselectedTintColor + } + + @IBAction func tap(_ sender: Any?) { + if let button = sender as? UIButton { + if let buttons = stackView?.arrangedSubviews as? [UIButton] { + userInteracting = true + selectedIndex = buttons.firstIndex(of: button) ?? -1 + userInteracting = false + } + } + } + + private func scroll() { + if let scrollView = scrollView, let subviews = stackView?.arrangedSubviews, subviews.count > selectedIndex { + let view = subviews[selectedIndex] + let viewRect = scrollView.convert(view.bounds, from: view) + scrollView.scrollRectToVisible(viewRect, animated: true) + } + } +} diff --git a/UIToolkits/UIToolkits/_View/_Segments/TabSegmentCollectionViewCell.swift b/UIToolkits/UIToolkits/_View/_Segments/TabSegmentCollectionViewCell.swift new file mode 100644 index 000000000..6634c97f3 --- /dev/null +++ b/UIToolkits/UIToolkits/_View/_Segments/TabSegmentCollectionViewCell.swift @@ -0,0 +1,26 @@ +// +// TabSegmentCollectionViewCell.swift +// UIToolkits +// +// Created by Qiang Huang on 10/30/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import UIKit + +open class TabSegmentCollectionViewCell: SegmentCollectionViewCell { + override open func updateSelected(animated: Bool) { + super.updateSelected(animated: animated) + let collectionView: UICollectionView? = parent() + if let textLabel = textLabel { + if let collectionView = collectionView, animated { + collectionView.performBatchUpdates { + textLabel.visible = isSelected + } completion: { _ in + } + } else { + textLabel.visible = isSelected + } + } + } +} diff --git a/UIToolkits/UIToolkits/_ViewController/KeyboardAdjustingViewController.swift b/UIToolkits/UIToolkits/_ViewController/KeyboardAdjustingViewController.swift new file mode 100644 index 000000000..a1e693ecb --- /dev/null +++ b/UIToolkits/UIToolkits/_ViewController/KeyboardAdjustingViewController.swift @@ -0,0 +1,32 @@ +// +// KeyboardAdjustingViewController.swift +// UIToolkits +// +// Created by John Huang on 5/14/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Utilities + +open class KeyboardAdjustingViewController: UIViewController, KeyboardAdjustingProtocol { + public var bottom: CGFloat? + + @IBOutlet public var bottomConstraint: NSLayoutConstraint? + public var keyboardObserver: NotificationToken? + + override open func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if bottomConstraint != nil { + registerKeyboardObserver() + } + } + + override open func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) +// keyboardObserver = nil + } + + open func layout(notif: Notification, bottom: CGFloat?) { + layout(notif: notif, bottom: bottom, completion: nil) + } +} diff --git a/UIToolkits/UIToolkits/_ViewController/LoadingNavigationController.swift b/UIToolkits/UIToolkits/_ViewController/LoadingNavigationController.swift new file mode 100644 index 000000000..d50b2ff2b --- /dev/null +++ b/UIToolkits/UIToolkits/_ViewController/LoadingNavigationController.swift @@ -0,0 +1,36 @@ +// +// LoadingNavigationController.swift +// UIToolkits +// +// Created by Qiang Huang on 11/20/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIKit +import Utilities + +open class LoadingNavigationController: UXNavigationController, LoadingIndicatorProtocol { + public var status: LoadingStatusProtocol? { + didSet { + changeObservation(from: oldValue, to: status, keyPath: #keyPath(LoadingStatusProtocol.running)) { [weak self] _, _, _, _ in + self?.update() + } + } + } + + override public var preferredStatusBarStyle: UIStatusBarStyle { + return topViewController?.preferredStatusBarStyle ?? .lightContent + } + + open override func awakeFromNib() { + super.awakeFromNib() + status = LoadingStatus.shared + } + + open override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + + open func update() { + } +} diff --git a/UIToolkits/UIToolkits/_ViewController/NoteViewController.swift b/UIToolkits/UIToolkits/_ViewController/NoteViewController.swift new file mode 100644 index 000000000..44d31d89f --- /dev/null +++ b/UIToolkits/UIToolkits/_ViewController/NoteViewController.swift @@ -0,0 +1,69 @@ +// +// NoteViewController.swift +// UIToolkits +// +// Created by Qiang Huang on 11/28/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UIKit + +public protocol NoteViewControllerDelegate: NSObjectProtocol { + func entered(_: NoteViewController, note: String?) +} + +public class NoteViewController: KeyboardAdjustingViewController { + public weak var delegate: NoteViewControllerDelegate? + public var text: String? + + @IBOutlet var textView: UITextView? + @IBOutlet var doneButton: ButtonProtocol? { + didSet { + if doneButton !== oldValue { + oldValue?.removeTarget() + doneButton?.addTarget(self, action: #selector(done(_:))) + } + } + } + + @IBOutlet var cancelButton: ButtonProtocol? { + didSet { + if cancelButton !== oldValue { + oldValue?.removeTarget() + cancelButton?.addTarget(self, action: #selector(dismiss(_:))) + } + } + } + + static public func note(delegate: NoteViewControllerDelegate, text: String?) { + if let vc = UIViewController.load(storyboard: "NoteEntry") as? NoteViewController { + vc.text = text + vc.delegate = delegate + let nav = UIViewController.navigation(with: vc) + ViewControllerStack.shared?.topmost()?.present(nav, animated: true, completion: nil) + } + } + + override public func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + textView?.text = text + let doneItem = doneButton as? UIBarButtonItem + let cancelItem = cancelButton as? UIBarButtonItem + if let doneItem = doneItem { + navigationItem.rightBarButtonItem = doneItem + if let cancelItem = cancelItem { + navigationItem.leftBarButtonItem = cancelItem + } + } else { + if let cancelItem = cancelItem { + navigationItem.rightBarButtonItem = cancelItem + } + } + textView?.becomeFirstResponder() + } + + @IBAction func done(_ sender: Any?) { + delegate?.entered(self, note: textView?.text) + dismiss(sender) + } +} diff --git a/UIToolkits/UIToolkits/_ViewController/UIViewController+App.swift b/UIToolkits/UIToolkits/_ViewController/UIViewController+App.swift new file mode 100644 index 000000000..efc430dea --- /dev/null +++ b/UIToolkits/UIToolkits/_ViewController/UIViewController+App.swift @@ -0,0 +1,56 @@ +// +// UIViewController+App.swift +// UIAppToolkits +// +// Created by Qiang Huang on 1/25/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import PanModal +import UIKit + +public extension UIViewController { + func bringEditingToView() { + if let textInput = UIResponder.current as? (UIView & UITextInput) { + if let cell: UITableViewCell = textInput.parent(), let tableView: UITableView = cell.parent(), let indexPath = tableView.indexPath(for: cell) { + tableView.scrollToRow(at: indexPath, at: .bottom, animated: true) + } + } + } + + func scrollToTop() { + func scrollToTop(view: UIView?) -> Bool { + if let view = view { + if let tableView = view as? UITableView { + if hasData(tableView: tableView) { + tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true) + return true + } else { + return false + } + } else { + var scrolled = false + for subView in view.subviews { + if !scrolled { + scrolled = scrollToTop(view: subView) + } + } + return scrolled + } + } else { + return false + } + } + + _ = scrollToTop(view: view) + } + + func hasData(tableView: UITableView?) -> Bool { + if let tableView = tableView, let dataSource = tableView.dataSource { + if (dataSource.numberOfSections?(in: tableView) ?? 1) >= 1 { + return dataSource.tableView(tableView, numberOfRowsInSection: 0) >= 1 + } + } + return false + } +} diff --git a/UIToolkits/UIToolkits/_ViewController/UIViewController+Helper.swift b/UIToolkits/UIToolkits/_ViewController/UIViewController+Helper.swift new file mode 100644 index 000000000..f520f7d73 --- /dev/null +++ b/UIToolkits/UIToolkits/_ViewController/UIViewController+Helper.swift @@ -0,0 +1,49 @@ +// +// UIViewController+Helper.swift +// UIToolkits +// +// Created by Qiang Huang on 10/23/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIKit + +extension UIViewController { + public var topParent: UIViewController { + if let parent = parent { + return parent.topParent + } else { + return self + } + } + + public func halfParent(of viewController: UIViewController) -> UIViewController? { + if ((self as AnyObject) as? UIViewControllerHalfProtocol)?.floatingManager?.halved == viewController { + return self + } else { + return parent?.halfParent(of: viewController) + } + } + + @IBAction open func dismiss(_ sender: Any?) { + if let presenting = presentingViewController ?? navigationController?.presentingViewController { + presenting.dismiss(animated: true, completion: nil) + } else { + let halfParent = parent?.halfParent(of: self) + if halfParent !== self { + ((halfParent as AnyObject) as? UIViewControllerHalfProtocol)?.dismiss(self, animated: true) + } else { + dismiss(animated: true, completion: nil) + } + } + } + + public var usableNavigationItem: UINavigationItem { + if let embedding = parentViewControllerConforming(protocol: UIViewControllerEmbeddingProtocol.self) { + if (embedding as? UIViewControllerEmbeddingProtocol)?.embedded === self { + return embedding.navigationItem + } + } + return (parent ?? self).navigationItem + } +} diff --git a/UIToolkits/UIToolkits/_ViewController/UXNavigationController.swift b/UIToolkits/UIToolkits/_ViewController/UXNavigationController.swift new file mode 100644 index 000000000..5423fee1a --- /dev/null +++ b/UIToolkits/UIToolkits/_ViewController/UXNavigationController.swift @@ -0,0 +1,36 @@ +// +// UXNavigationController.swift +// UIToolkits +// +// Created by Qiang Huang on 9/1/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import UIKit + +@objc public protocol UXNavigationPopProtocol: NSObjectProtocol { + @objc func shouldPop() -> Bool +} + +open class UXNavigationController: UINavigationController, UINavigationBarDelegate { + open override func viewDidLoad() { + super.viewDidLoad() +// navigationBar.barStyle = .black +// navigationBar.barTintColor = UIColor(named: "Base") + navigationBar.shadowImage = UIImage() + } + + public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool { + var shouldPop = true + let viewController = viewControllers.last + if item == viewController?.navigationItem { + shouldPop = (viewController as? UXNavigationPopProtocol)?.shouldPop() ?? true + } + if #available(iOS 13.0, *) { + // do nothing + } else if shouldPop { + popViewController(animated: true) + } + return shouldPop + } +} diff --git a/UIToolkits/UIToolkits/_ViewController/ViewControllerStack.swift b/UIToolkits/UIToolkits/_ViewController/ViewControllerStack.swift new file mode 100644 index 000000000..e66382256 --- /dev/null +++ b/UIToolkits/UIToolkits/_ViewController/ViewControllerStack.swift @@ -0,0 +1,69 @@ +// +// UIViewControllerStack.swift +// UIToolkits +// +// Created by Qiang Huang on 12/28/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Utilities + +public protocol ViewControllerStackProtocol { + func didShow(viewController: UIViewController) + func root() -> UIViewController? + func topmost() -> UIViewController? +} + +public extension ViewControllerStackProtocol { + func topParent() -> UIViewController? { + if let topmost = topmost() { + return topParent(of: topmost) + } + return nil + } + + func topParent(of viewController: UIViewController) -> UIViewController { + if viewController is UIViewControllerDrawerProtocol || viewController is UIViewControllerEmbeddingProtocol { + return viewController + } + + if let parent = viewController.parent { + let grandParent = parent.parent + if (grandParent is UITabBarController || grandParent is UISplitViewController || grandParent == nil) && parent is UINavigationController { + return viewController + } else { + return topParent(of: parent) + } + } else { + return viewController + } + } +} + +public class ViewControllerStack { + public static var shared: ViewControllerStackProtocol? +} + +public protocol ExtensionRootViewControllerProtocol { + var embedded: UIViewController? { get set } +} + +public class UIKitExtensionViewControllerStack: NSObject, ViewControllerStackProtocol { + public var extensionRoot: (UIViewController & ExtensionRootViewControllerProtocol)? + private var stack: [Weak] = [] + + public func didShow(viewController: UIViewController) { + stack.append(Weak(viewController)) + } + + public func root() -> UIViewController? { + return extensionRoot + } + + public func topmost() -> UIViewController? { + stack.removeAll { (boxed: Weak) -> Bool in + boxed.object == nil + } + return stack.last?.object ?? root() + } +} diff --git a/UIToolkits/UIToolkits/_iOS/UISearchBar+Icons.swift b/UIToolkits/UIToolkits/_iOS/UISearchBar+Icons.swift new file mode 100644 index 000000000..b3ba7014c --- /dev/null +++ b/UIToolkits/UIToolkits/_iOS/UISearchBar+Icons.swift @@ -0,0 +1,33 @@ +// +// UISearchBar+Icons.swift +// UIToolkits +// +// Created by Qiang Huang on 10/30/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import UIKit + +public extension UISearchBar { + #if _iOS + var bookmarkIcon: UIImage? { + get { return image(for: .bookmark, state: .normal) } + set { setImage(newValue, for: .bookmark, state: .normal) } + } + + var clearIcon: UIImage? { + get { return image(for: .clear, state: .normal) } + set { setImage(newValue, for: .clear, state: .normal) } + } + + var resultsIcon: UIImage? { + get { return image(for: .resultsList, state: .normal) } + set { setImage(newValue, for: .resultsList, state: .normal) } + } + #endif + + var searchIcon: UIImage? { + get { return image(for: .search, state: .normal) } + set { setImage(newValue, for: .search, state: .normal) } + } +} diff --git a/UIToolkits/UIToolkitsAppleTV/Info.plist b/UIToolkits/UIToolkitsAppleTV/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/UIToolkits/UIToolkitsAppleTV/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/UIToolkits/UIToolkitsAppleTV/UIToolkits.h b/UIToolkits/UIToolkitsAppleTV/UIToolkits.h new file mode 100644 index 000000000..db4a9c008 --- /dev/null +++ b/UIToolkits/UIToolkitsAppleTV/UIToolkits.h @@ -0,0 +1,17 @@ +// +// UIToolkitsAppleTV.h +// UIToolkitsAppleTV +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for UIToolkitsAppleTV. +FOUNDATION_EXPORT double UIToolkitsAppleTVVersionNumber; + +//! Project version string for UIToolkitsAppleTV. +FOUNDATION_EXPORT const unsigned char UIToolkitsAppleTVVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/UIToolkits/UIToolkitsAppleTVTests/Info.plist b/UIToolkits/UIToolkitsAppleTVTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/UIToolkits/UIToolkitsAppleTVTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/UIToolkits/UIToolkitsAppleTVTests/UIToolkitsAppleTVTests.swift b/UIToolkits/UIToolkitsAppleTVTests/UIToolkitsAppleTVTests.swift new file mode 100644 index 000000000..0800cac06 --- /dev/null +++ b/UIToolkits/UIToolkitsAppleTVTests/UIToolkitsAppleTVTests.swift @@ -0,0 +1,32 @@ +// +// UIToolkitsAppleTVTests.swift +// UIToolkitsAppleTVTests +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +@testable import UIToolkits +import XCTest + +class UIToolkitsAppleTVTests: XCTestCase { + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. + } + } +} diff --git a/UIToolkits/UIToolkitsAppleWatch/Extensions/WKInterfaceImage+ImageUrl.swift b/UIToolkits/UIToolkitsAppleWatch/Extensions/WKInterfaceImage+ImageUrl.swift new file mode 100644 index 000000000..37e79e5ab --- /dev/null +++ b/UIToolkits/UIToolkitsAppleWatch/Extensions/WKInterfaceImage+ImageUrl.swift @@ -0,0 +1,31 @@ +// +// WKInterfaceImage+ImageUrl.swift +// UIToolkitsAppleWatch +// +// Created by John Huang on 12/8/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import WatchKit + +public extension WKInterfaceImage { + func setImage(url: String?) { + if let url = url { + let asyncQueue = DispatchQueue(label: "backgroundImage") + asyncQueue.async { + do { + if let url = URL(string: url) { + let data = try Data(contentsOf: url) + if let image = UIImage(data: data) { + self.setImage(image) + } + } + } catch let error { + Console.shared.log("Could not set backgroundImage for WKInterfaceImage: \(error.localizedDescription)") + } + } + } else { + setImage(nil) + } + } +} diff --git a/UIToolkits/UIToolkitsAppleWatch/Info.plist b/UIToolkits/UIToolkitsAppleWatch/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/UIToolkits/UIToolkitsAppleWatch/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/UIToolkits/UIToolkitsAppleWatch/UIToolkits.h b/UIToolkits/UIToolkitsAppleWatch/UIToolkits.h new file mode 100644 index 000000000..8c84b9440 --- /dev/null +++ b/UIToolkits/UIToolkitsAppleWatch/UIToolkits.h @@ -0,0 +1,19 @@ +// +// UIToolkitsAppleWatch.h +// UIToolkitsAppleWatch +// +// Created by Qiang Huang on 12/7/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for UIToolkitsAppleWatch. +FOUNDATION_EXPORT double UIToolkitsAppleWatchVersionNumber; + +//! Project version string for UIToolkitsAppleWatch. +FOUNDATION_EXPORT const unsigned char UIToolkitsAppleWatchVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/UIToolkits/UIToolkitsExtensions/Info.plist b/UIToolkits/UIToolkitsExtensions/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/UIToolkits/UIToolkitsExtensions/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/UIToolkits/UIToolkitsExtensions/UIToolkitsExtensions.h b/UIToolkits/UIToolkitsExtensions/UIToolkitsExtensions.h new file mode 100644 index 000000000..b30126ed3 --- /dev/null +++ b/UIToolkits/UIToolkitsExtensions/UIToolkitsExtensions.h @@ -0,0 +1,19 @@ +// +// UIToolkitsExtensions.h +// UIToolkitsExtensions +// +// Created by Qiang Huang on 12/21/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for UIToolkitsExtensions. +FOUNDATION_EXPORT double UIToolkitsExtensionsVersionNumber; + +//! Project version string for UIToolkitsExtensions. +FOUNDATION_EXPORT const unsigned char UIToolkitsExtensionsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/UIToolkits/UIToolkitsTests/Info.plist b/UIToolkits/UIToolkitsTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/UIToolkits/UIToolkitsTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/UIToolkits/UIToolkitsTests/UIToolkitsTests.swift b/UIToolkits/UIToolkitsTests/UIToolkitsTests.swift new file mode 100644 index 000000000..1eb82f4ab --- /dev/null +++ b/UIToolkits/UIToolkitsTests/UIToolkitsTests.swift @@ -0,0 +1,34 @@ +// +// UIToolkitsTests.swift +// UIToolkitsTests +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import XCTest +@testable import UIToolkits + +class UIToolkitsTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Utilities/Utilities.xcodeproj/project.pbxproj b/Utilities/Utilities.xcodeproj/project.pbxproj new file mode 100644 index 000000000..55ca5d9d6 --- /dev/null +++ b/Utilities/Utilities.xcodeproj/project.pbxproj @@ -0,0 +1,1917 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 020F758C298806F600DA2D87 /* AsyncEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020F758B298806F600DA2D87 /* AsyncEvent.swift */; }; + 020F758E2988072800DA2D87 /* Combine+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020F758D2988072800DA2D87 /* Combine+Ext.swift */; }; + 023FA97028A2C5B4008352E3 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023FA96F28A2C5B4008352E3 /* UIColor+Hex.swift */; }; + 023FA97128A2C5B4008352E3 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023FA96F28A2C5B4008352E3 /* UIColor+Hex.swift */; }; + 023FA97228A2C5B4008352E3 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023FA96F28A2C5B4008352E3 /* UIColor+Hex.swift */; }; + 0253CEC22A9FFF8D0033F064 /* CachedFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0253CEC12A9FFF8D0033F064 /* CachedFileLoader.swift */; }; + 0253CEC42AA009D70033F064 /* DebugEnabled.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0253CEC32AA009D70033F064 /* DebugEnabled.swift */; }; + 025B752B28A703E800A4AC98 /* ObjectLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025B752A28A703E800A4AC98 /* ObjectLoader.swift */; }; + 025B752D28A7041600A4AC98 /* ClassLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025B752C28A7041600A4AC98 /* ClassLoader.swift */; }; + 026734EC2BF2B6BB00DBF51A /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026734EB2BF2B6BB00DBF51A /* Logging.swift */; }; + 027E1EE329CA0F790098666F /* KeyValueStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027E1EE229CA0F790098666F /* KeyValueStoreProtocol.swift */; }; + 027E1EE529CA100B0098666F /* UserDefaultsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027E1EE429CA100B0098666F /* UserDefaultsStore.swift */; }; + 027E1EE929CA1E7A0098666F /* SettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027E1EE829CA1E7A0098666F /* SettingsStore.swift */; }; + 0288D4132824A83600B5DBD4 /* CombineObserving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0288D4122824A83600B5DBD4 /* CombineObserving.swift */; }; + 02ABF37D2AD72314005D799B /* AttributedString+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02ABF37C2AD72314005D799B /* AttributedString+Ext.swift */; }; + 02C083F32875F9E700D4270E /* NSAttributedString+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C083F22875F9E700D4270E /* NSAttributedString+Utils.swift */; }; + 02E5003929F3026D00BB55D2 /* Publisher+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E5003829F3026D00BB55D2 /* Publisher+Ext.swift */; }; + 02E90C5C29D74395004E2311 /* FeatureFlagsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E90C5B29D74395004E2311 /* FeatureFlagsStore.swift */; }; + 02E90C5E29D7444B004E2311 /* DebugSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E90C5D29D7444B004E2311 /* DebugSettingsStore.swift */; }; + 02EE8FE12828307900225B56 /* CombineObservingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EE8FE02828307900225B56 /* CombineObservingTests.swift */; }; + 02EE90552829A95600225B56 /* NSObject+ObservingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EE90542829A95600225B56 /* NSObject+ObservingTests.swift */; }; + 02F958032A18278B00828F9A /* SecureStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F958022A18278B00828F9A /* SecureStore.swift */; }; + 02F958052A182BC400828F9A /* SecureStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F958042A182BC400828F9A /* SecureStoreProtocol.swift */; }; + 279B87992B97CD4C00466392 /* PointsRating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279B87972B97CD4C00466392 /* PointsRating.swift */; }; + 279B879A2B97CD4C00466392 /* RatingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279B87982B97CD4C00466392 /* RatingService.swift */; }; + 27E31E5B2BE40E4000580E59 /* SynchronizedLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E31E5A2BE40E4000580E59 /* SynchronizedLock.swift */; }; + 3101F94725112C4100AC4010 /* AuthProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F94625112C4100AC4010 /* AuthProtocol.swift */; }; + 3101F94A25112C5900AC4010 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101F94925112C5900AC4010 /* AuthService.swift */; }; + 310E61E9216C0F910043BB33 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 310E61E8216C0F910043BB33 /* Security.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 3111F42B2637572300EE3D38 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3111F42A2637572300EE3D38 /* AppState.swift */; }; + 3112B53B25264670009D19B6 /* NotificationBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3112B53A25264670009D19B6 /* NotificationBridge.swift */; }; + 311CF2332739D27500C9D780 /* Throttler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311CF2322739D27500C9D780 /* Throttler.swift */; }; + 312CE38C2630A38700C519C0 /* JavascriptRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312CE38B2630A38700C519C0 /* JavascriptRunner.swift */; }; + 312CE38D2630A38700C519C0 /* JavascriptRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312CE38B2630A38700C519C0 /* JavascriptRunner.swift */; }; + 312CE38E2630A38700C519C0 /* JavascriptRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312CE38B2630A38700C519C0 /* JavascriptRunner.swift */; }; + 312CE39A2630A48B00C519C0 /* StringLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312CE3992630A48B00C519C0 /* StringLoader.swift */; }; + 312CE39B2630A48B00C519C0 /* StringLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312CE3992630A48B00C519C0 /* StringLoader.swift */; }; + 312CE39C2630A48B00C519C0 /* StringLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312CE3992630A48B00C519C0 /* StringLoader.swift */; }; + 31331DDC255347FE0050D74C /* LocalNotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31331DDB255347FE0050D74C /* LocalNotificationService.swift */; }; + 313A536E21B9805F00A92D62 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313A536521B9805F00A92D62 /* Utilities.framework */; }; + 313A537321B9805F00A92D62 /* UtilitiesAppleTVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313A537221B9805F00A92D62 /* UtilitiesAppleTVTests.swift */; }; + 313A537521B9805F00A92D62 /* Utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 313A536721B9805F00A92D62 /* Utilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 313B1AA725CB08CE006D369F /* Sequence+Reduce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313B1AA625CB08CE006D369F /* Sequence+Reduce.swift */; }; + 313B1AA825CB08CE006D369F /* Sequence+Reduce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313B1AA625CB08CE006D369F /* Sequence+Reduce.swift */; }; + 313B1AA925CB08CE006D369F /* Sequence+Reduce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313B1AA625CB08CE006D369F /* Sequence+Reduce.swift */; }; + 31471F7024BA2F1E00057221 /* UserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471F6F24BA2F1E00057221 /* UserAgent.swift */; }; + 31471F7124BA2F1E00057221 /* UserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471F6F24BA2F1E00057221 /* UserAgent.swift */; }; + 31471F7224BA2F1E00057221 /* UserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471F6F24BA2F1E00057221 /* UserAgent.swift */; }; + 314B63E823DCCF0200139EB3 /* Directory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B638823DCCF0100139EB3 /* Directory.swift */; }; + 314B63E923DCCF0200139EB3 /* Directory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B638823DCCF0100139EB3 /* Directory.swift */; }; + 314B63EA23DCCF0200139EB3 /* Directory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B638823DCCF0100139EB3 /* Directory.swift */; }; + 314B63EC23DCCF0200139EB3 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B638923DCCF0100139EB3 /* File.swift */; }; + 314B63ED23DCCF0200139EB3 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B638923DCCF0100139EB3 /* File.swift */; }; + 314B63EE23DCCF0200139EB3 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B638923DCCF0100139EB3 /* File.swift */; }; + 314B63F023DCCF0200139EB3 /* UrlHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B638B23DCCF0100139EB3 /* UrlHandler.swift */; }; + 314B63F123DCCF0200139EB3 /* UrlHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B638B23DCCF0100139EB3 /* UrlHandler.swift */; }; + 314B63F223DCCF0200139EB3 /* UrlHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B638B23DCCF0100139EB3 /* UrlHandler.swift */; }; + 314B63F423DCCF0200139EB3 /* ErrorInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B638D23DCCF0100139EB3 /* ErrorInfo.swift */; }; + 314B63F523DCCF0200139EB3 /* ErrorInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B638D23DCCF0100139EB3 /* ErrorInfo.swift */; }; + 314B63F623DCCF0200139EB3 /* ErrorInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B638D23DCCF0100139EB3 /* ErrorInfo.swift */; }; + 314B63FC23DCCF0200139EB3 /* MapPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B639023DCCF0100139EB3 /* MapPoint.swift */; }; + 314B63FD23DCCF0200139EB3 /* MapPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B639023DCCF0100139EB3 /* MapPoint.swift */; }; + 314B63FE23DCCF0200139EB3 /* MapPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B639023DCCF0100139EB3 /* MapPoint.swift */; }; + 314B640023DCCF0200139EB3 /* MapArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B639123DCCF0100139EB3 /* MapArea.swift */; }; + 314B640123DCCF0200139EB3 /* MapArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B639123DCCF0100139EB3 /* MapArea.swift */; }; + 314B640223DCCF0200139EB3 /* MapArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B639123DCCF0100139EB3 /* MapArea.swift */; }; + 314B640423DCCF0200139EB3 /* UserInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B639323DCCF0100139EB3 /* UserInterface.swift */; }; + 314B640523DCCF0200139EB3 /* UserInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B639323DCCF0100139EB3 /* UserInterface.swift */; }; + 314B640623DCCF0200139EB3 /* UserInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B639323DCCF0100139EB3 /* UserInterface.swift */; }; + 314B642423DCCF0200139EB3 /* RegionMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B639E23DCCF0100139EB3 /* RegionMonitor.swift */; }; + 314B642523DCCF0200139EB3 /* RegionMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B639E23DCCF0100139EB3 /* RegionMonitor.swift */; }; + 314B642623DCCF0200139EB3 /* RegionMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B639E23DCCF0100139EB3 /* RegionMonitor.swift */; }; + 314B643423DCCF0200139EB3 /* JsonLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A323DCCF0100139EB3 /* JsonLoader.swift */; }; + 314B643523DCCF0200139EB3 /* JsonLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A323DCCF0100139EB3 /* JsonLoader.swift */; }; + 314B643623DCCF0200139EB3 /* JsonLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A323DCCF0100139EB3 /* JsonLoader.swift */; }; + 314B643823DCCF0200139EB3 /* DebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A423DCCF0100139EB3 /* DebugSettings.swift */; }; + 314B643923DCCF0200139EB3 /* DebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A423DCCF0100139EB3 /* DebugSettings.swift */; }; + 314B643A23DCCF0200139EB3 /* DebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A423DCCF0100139EB3 /* DebugSettings.swift */; }; + 314B643C23DCCF0200139EB3 /* CompositeFeatureFlagsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A623DCCF0100139EB3 /* CompositeFeatureFlagsProvider.swift */; }; + 314B643D23DCCF0200139EB3 /* CompositeFeatureFlagsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A623DCCF0100139EB3 /* CompositeFeatureFlagsProvider.swift */; }; + 314B643E23DCCF0200139EB3 /* CompositeFeatureFlagsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A623DCCF0100139EB3 /* CompositeFeatureFlagsProvider.swift */; }; + 314B644023DCCF0200139EB3 /* FeatureService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A723DCCF0100139EB3 /* FeatureService.swift */; }; + 314B644123DCCF0200139EB3 /* FeatureService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A723DCCF0100139EB3 /* FeatureService.swift */; }; + 314B644223DCCF0200139EB3 /* FeatureService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A723DCCF0100139EB3 /* FeatureService.swift */; }; + 314B644423DCCF0200139EB3 /* LoadingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A823DCCF0100139EB3 /* LoadingStatus.swift */; }; + 314B644523DCCF0200139EB3 /* LoadingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A823DCCF0100139EB3 /* LoadingStatus.swift */; }; + 314B644623DCCF0200139EB3 /* LoadingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A823DCCF0100139EB3 /* LoadingStatus.swift */; }; + 314B644823DCCF0200139EB3 /* NotificationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A923DCCF0100139EB3 /* NotificationToken.swift */; }; + 314B644923DCCF0200139EB3 /* NotificationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A923DCCF0100139EB3 /* NotificationToken.swift */; }; + 314B644A23DCCF0200139EB3 /* NotificationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63A923DCCF0100139EB3 /* NotificationToken.swift */; }; + 314B645023DCCF0200139EB3 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63AC23DCCF0100139EB3 /* Parser.swift */; }; + 314B645123DCCF0200139EB3 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63AC23DCCF0100139EB3 /* Parser.swift */; }; + 314B645223DCCF0200139EB3 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63AC23DCCF0100139EB3 /* Parser.swift */; }; + 314B645423DCCF0200139EB3 /* DebugParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63AD23DCCF0100139EB3 /* DebugParser.swift */; }; + 314B645523DCCF0200139EB3 /* DebugParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63AD23DCCF0100139EB3 /* DebugParser.swift */; }; + 314B645623DCCF0200139EB3 /* DebugParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63AD23DCCF0100139EB3 /* DebugParser.swift */; }; + 314B645C23DCCF0200139EB3 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63AF23DCCF0100139EB3 /* Debouncer.swift */; }; + 314B645D23DCCF0200139EB3 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63AF23DCCF0100139EB3 /* Debouncer.swift */; }; + 314B645E23DCCF0200139EB3 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63AF23DCCF0100139EB3 /* Debouncer.swift */; }; + 314B646023DCCF0200139EB3 /* JsonWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B023DCCF0100139EB3 /* JsonWriter.swift */; }; + 314B646123DCCF0200139EB3 /* JsonWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B023DCCF0100139EB3 /* JsonWriter.swift */; }; + 314B646223DCCF0200139EB3 /* JsonWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B023DCCF0100139EB3 /* JsonWriter.swift */; }; + 314B646423DCCF0200139EB3 /* Installation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B123DCCF0100139EB3 /* Installation.swift */; }; + 314B646523DCCF0200139EB3 /* Installation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B123DCCF0100139EB3 /* Installation.swift */; }; + 314B646623DCCF0200139EB3 /* Installation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B123DCCF0100139EB3 /* Installation.swift */; }; + 314B646823DCCF0200139EB3 /* NetworkConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B223DCCF0100139EB3 /* NetworkConnection.swift */; }; + 314B646C23DCCF0200139EB3 /* XibLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B323DCCF0100139EB3 /* XibLoader.swift */; }; + 314B646D23DCCF0200139EB3 /* XibLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B323DCCF0100139EB3 /* XibLoader.swift */; }; + 314B646E23DCCF0200139EB3 /* XibLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B323DCCF0100139EB3 /* XibLoader.swift */; }; + 314B647023DCCF0200139EB3 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B423DCCF0100139EB3 /* Weak.swift */; }; + 314B647123DCCF0200139EB3 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B423DCCF0100139EB3 /* Weak.swift */; }; + 314B647223DCCF0200139EB3 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B423DCCF0100139EB3 /* Weak.swift */; }; + 314B647423DCCF0200139EB3 /* NSObject+Class.h in Headers */ = {isa = PBXBuildFile; fileRef = 314B63B623DCCF0100139EB3 /* NSObject+Class.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 314B647523DCCF0200139EB3 /* NSObject+Class.h in Headers */ = {isa = PBXBuildFile; fileRef = 314B63B623DCCF0100139EB3 /* NSObject+Class.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 314B647623DCCF0200139EB3 /* NSObject+Class.h in Headers */ = {isa = PBXBuildFile; fileRef = 314B63B623DCCF0100139EB3 /* NSObject+Class.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 314B647823DCCF0200139EB3 /* URL+Params.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B723DCCF0100139EB3 /* URL+Params.swift */; }; + 314B647923DCCF0200139EB3 /* URL+Params.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B723DCCF0100139EB3 /* URL+Params.swift */; }; + 314B647A23DCCF0200139EB3 /* URL+Params.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B723DCCF0100139EB3 /* URL+Params.swift */; }; + 314B648023DCCF0200139EB3 /* String+Security.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63B923DCCF0100139EB3 /* String+Security.swift */; }; + 314B648423DCCF0200139EB3 /* TimeIntervale+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BA23DCCF0100139EB3 /* TimeIntervale+String.swift */; }; + 314B648523DCCF0200139EB3 /* TimeIntervale+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BA23DCCF0100139EB3 /* TimeIntervale+String.swift */; }; + 314B648623DCCF0200139EB3 /* TimeIntervale+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BA23DCCF0100139EB3 /* TimeIntervale+String.swift */; }; + 314B648823DCCF0200139EB3 /* Array+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BB23DCCF0100139EB3 /* Array+Compare.swift */; }; + 314B648923DCCF0200139EB3 /* Array+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BB23DCCF0100139EB3 /* Array+Compare.swift */; }; + 314B648A23DCCF0200139EB3 /* Array+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BB23DCCF0100139EB3 /* Array+Compare.swift */; }; + 314B648C23DCCF0200139EB3 /* NSObject+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BC23DCCF0100139EB3 /* NSObject+Observing.swift */; }; + 314B648D23DCCF0200139EB3 /* NSObject+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BC23DCCF0100139EB3 /* NSObject+Observing.swift */; }; + 314B648E23DCCF0200139EB3 /* NSObject+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BC23DCCF0100139EB3 /* NSObject+Observing.swift */; }; + 314B649023DCCF0200139EB3 /* Bundle+UIBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BD23DCCF0100139EB3 /* Bundle+UIBundle.swift */; }; + 314B649123DCCF0200139EB3 /* Bundle+UIBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BD23DCCF0100139EB3 /* Bundle+UIBundle.swift */; }; + 314B649223DCCF0200139EB3 /* Bundle+UIBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BD23DCCF0100139EB3 /* Bundle+UIBundle.swift */; }; + 314B649423DCCF0200139EB3 /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BE23DCCF0100139EB3 /* String+Localized.swift */; }; + 314B649523DCCF0200139EB3 /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BE23DCCF0100139EB3 /* String+Localized.swift */; }; + 314B649623DCCF0200139EB3 /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BE23DCCF0100139EB3 /* String+Localized.swift */; }; + 314B649823DCCF0200139EB3 /* Int+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BF23DCCF0100139EB3 /* Int+Utils.swift */; }; + 314B649923DCCF0200139EB3 /* Int+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BF23DCCF0100139EB3 /* Int+Utils.swift */; }; + 314B649A23DCCF0200139EB3 /* Int+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63BF23DCCF0100139EB3 /* Int+Utils.swift */; }; + 314B649C23DCCF0200139EB3 /* DispatchQueue+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C023DCCF0100139EB3 /* DispatchQueue+Utils.swift */; }; + 314B649D23DCCF0200139EB3 /* DispatchQueue+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C023DCCF0100139EB3 /* DispatchQueue+Utils.swift */; }; + 314B649E23DCCF0200139EB3 /* DispatchQueue+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C023DCCF0100139EB3 /* DispatchQueue+Utils.swift */; }; + 314B64A023DCCF0200139EB3 /* Collection+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C123DCCF0100139EB3 /* Collection+Random.swift */; }; + 314B64A123DCCF0200139EB3 /* Collection+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C123DCCF0100139EB3 /* Collection+Random.swift */; }; + 314B64A223DCCF0200139EB3 /* Collection+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C123DCCF0100139EB3 /* Collection+Random.swift */; }; + 314B64A423DCCF0200139EB3 /* Dictionary+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C223DCCF0100139EB3 /* Dictionary+Utils.swift */; }; + 314B64A523DCCF0200139EB3 /* Dictionary+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C223DCCF0100139EB3 /* Dictionary+Utils.swift */; }; + 314B64A623DCCF0200139EB3 /* Dictionary+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C223DCCF0100139EB3 /* Dictionary+Utils.swift */; }; + 314B64AC23DCCF0200139EB3 /* URLComponents+Params.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C423DCCF0100139EB3 /* URLComponents+Params.swift */; }; + 314B64AD23DCCF0200139EB3 /* URLComponents+Params.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C423DCCF0100139EB3 /* URLComponents+Params.swift */; }; + 314B64AE23DCCF0200139EB3 /* URLComponents+Params.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C423DCCF0100139EB3 /* URLComponents+Params.swift */; }; + 314B64B023DCCF0200139EB3 /* NSObject+Class.m in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C523DCCF0100139EB3 /* NSObject+Class.m */; }; + 314B64B123DCCF0200139EB3 /* NSObject+Class.m in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C523DCCF0100139EB3 /* NSObject+Class.m */; }; + 314B64B223DCCF0200139EB3 /* NSObject+Class.m in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C523DCCF0100139EB3 /* NSObject+Class.m */; }; + 314B64BC23DCCF0200139EB3 /* Int+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C823DCCF0100139EB3 /* Int+Random.swift */; }; + 314B64BD23DCCF0200139EB3 /* Int+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C823DCCF0100139EB3 /* Int+Random.swift */; }; + 314B64BE23DCCF0200139EB3 /* Int+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C823DCCF0100139EB3 /* Int+Random.swift */; }; + 314B64C023DCCF0200139EB3 /* NSObject+Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C923DCCF0100139EB3 /* NSObject+Association.swift */; }; + 314B64C123DCCF0200139EB3 /* NSObject+Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C923DCCF0100139EB3 /* NSObject+Association.swift */; }; + 314B64C223DCCF0200139EB3 /* NSObject+Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63C923DCCF0100139EB3 /* NSObject+Association.swift */; }; + 314B64C423DCCF0200139EB3 /* String+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63CA23DCCF0100139EB3 /* String+Utils.swift */; }; + 314B64C523DCCF0200139EB3 /* String+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63CA23DCCF0100139EB3 /* String+Utils.swift */; }; + 314B64C623DCCF0200139EB3 /* String+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63CA23DCCF0100139EB3 /* String+Utils.swift */; }; + 314B64C823DCCF0200139EB3 /* Date+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63CB23DCCF0100139EB3 /* Date+Utils.swift */; }; + 314B64C923DCCF0200139EB3 /* Date+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63CB23DCCF0100139EB3 /* Date+Utils.swift */; }; + 314B64CA23DCCF0200139EB3 /* Date+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63CB23DCCF0100139EB3 /* Date+Utils.swift */; }; + 314B64D023DCCF0200139EB3 /* TimeCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63CF23DCCF0100139EB3 /* TimeCounter.swift */; }; + 314B64D223DCCF0200139EB3 /* TimeCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63CF23DCCF0100139EB3 /* TimeCounter.swift */; }; + 314B64D423DCCF0200139EB3 /* TimeCounterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D023DCCF0100139EB3 /* TimeCounterProtocol.swift */; }; + 314B64D523DCCF0200139EB3 /* TimeCounterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D023DCCF0100139EB3 /* TimeCounterProtocol.swift */; }; + 314B64D623DCCF0200139EB3 /* TimeCounterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D023DCCF0100139EB3 /* TimeCounterProtocol.swift */; }; + 314B64D823DCCF0200139EB3 /* Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D323DCCF0100139EB3 /* Tracking.swift */; }; + 314B64D923DCCF0200139EB3 /* Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D323DCCF0100139EB3 /* Tracking.swift */; }; + 314B64DA23DCCF0200139EB3 /* Tracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D323DCCF0100139EB3 /* Tracking.swift */; }; + 314B64DC23DCCF0200139EB3 /* CompositeTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D423DCCF0100139EB3 /* CompositeTracking.swift */; }; + 314B64DD23DCCF0200139EB3 /* CompositeTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D423DCCF0100139EB3 /* CompositeTracking.swift */; }; + 314B64DE23DCCF0200139EB3 /* CompositeTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D423DCCF0100139EB3 /* CompositeTracking.swift */; }; + 314B64E023DCCF0200139EB3 /* DebugTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D523DCCF0100139EB3 /* DebugTracking.swift */; }; + 314B64E123DCCF0200139EB3 /* DebugTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D523DCCF0100139EB3 /* DebugTracking.swift */; }; + 314B64E223DCCF0200139EB3 /* DebugTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D523DCCF0100139EB3 /* DebugTracking.swift */; }; + 314B64E423DCCF0200139EB3 /* Localizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D723DCCF0100139EB3 /* Localizer.swift */; }; + 314B64E523DCCF0200139EB3 /* Localizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D723DCCF0100139EB3 /* Localizer.swift */; }; + 314B64E623DCCF0200139EB3 /* Localizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D723DCCF0100139EB3 /* Localizer.swift */; }; + 314B64E823DCCF0200139EB3 /* ProgressProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D923DCCF0100139EB3 /* ProgressProtocol.swift */; }; + 314B64E923DCCF0200139EB3 /* ProgressProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D923DCCF0100139EB3 /* ProgressProtocol.swift */; }; + 314B64EA23DCCF0200139EB3 /* ProgressProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63D923DCCF0100139EB3 /* ProgressProtocol.swift */; }; + 314B64EC23DCCF0200139EB3 /* ParsingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63DA23DCCF0100139EB3 /* ParsingProtocol.swift */; }; + 314B64ED23DCCF0200139EB3 /* ParsingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63DA23DCCF0100139EB3 /* ParsingProtocol.swift */; }; + 314B64EE23DCCF0200139EB3 /* ParsingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63DA23DCCF0100139EB3 /* ParsingProtocol.swift */; }; + 314B64F023DCCF0200139EB3 /* SingletonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63DB23DCCF0100139EB3 /* SingletonProtocol.swift */; }; + 314B64F123DCCF0200139EB3 /* SingletonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63DB23DCCF0100139EB3 /* SingletonProtocol.swift */; }; + 314B64F223DCCF0200139EB3 /* SingletonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63DB23DCCF0100139EB3 /* SingletonProtocol.swift */; }; + 314B676F23DCEC9B00139EB3 /* TimeCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B63CF23DCCF0100139EB3 /* TimeCounter.swift */; }; + 3162B55126E18183000209E1 /* WebCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162B55026E18182000209E1 /* WebCrypto.swift */; }; + 3162C00624C377E000DE648C /* PrompterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C00524C377E000DE648C /* PrompterProtocol.swift */; }; + 3162C00724C377E000DE648C /* PrompterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C00524C377E000DE648C /* PrompterProtocol.swift */; }; + 3162C00824C377E000DE648C /* PrompterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C00524C377E000DE648C /* PrompterProtocol.swift */; }; + 3162C00A24C377EF00DE648C /* PrompterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C00924C377EF00DE648C /* PrompterFactory.swift */; }; + 3162C00B24C377EF00DE648C /* PrompterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C00924C377EF00DE648C /* PrompterFactory.swift */; }; + 3162C00C24C377EF00DE648C /* PrompterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C00924C377EF00DE648C /* PrompterFactory.swift */; }; + 3162C00F24C3783000DE648C /* PrivacyPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C00E24C3783000DE648C /* PrivacyPermission.swift */; }; + 3162C01024C3783000DE648C /* PrivacyPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C00E24C3783000DE648C /* PrivacyPermission.swift */; }; + 3162C01124C3783000DE648C /* PrivacyPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C00E24C3783000DE648C /* PrivacyPermission.swift */; }; + 3162C01324C3784800DE648C /* PhotoAlbumsPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C01224C3784800DE648C /* PhotoAlbumsPermission.swift */; }; + 3162C01724C3786D00DE648C /* NotificationPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C01624C3786D00DE648C /* NotificationPermission.swift */; }; + 3162C01824C3786D00DE648C /* NotificationPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C01624C3786D00DE648C /* NotificationPermission.swift */; }; + 3162C01924C3786D00DE648C /* NotificationPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C01624C3786D00DE648C /* NotificationPermission.swift */; }; + 3162C01F24C3788700DE648C /* CameraPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C01E24C3788700DE648C /* CameraPermission.swift */; }; + 3162C02324C3789200DE648C /* CalendarPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C02224C3789200DE648C /* CalendarPermission.swift */; }; + 3162C02824C3791300DE648C /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C02724C3791300DE648C /* NotificationService.swift */; }; + 3162C02924C3791300DE648C /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C02724C3791300DE648C /* NotificationService.swift */; }; + 3162C02A24C3791300DE648C /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3162C02724C3791300DE648C /* NotificationService.swift */; }; + 31755736262A0CED00D76C4C /* NSNumber+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31755735262A0CED00D76C4C /* NSNumber+Format.swift */; }; + 31755737262A0CED00D76C4C /* NSNumber+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31755735262A0CED00D76C4C /* NSNumber+Format.swift */; }; + 31755738262A0CED00D76C4C /* NSNumber+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31755735262A0CED00D76C4C /* NSNumber+Format.swift */; }; + 31762166245B304300C9F2FB /* UINib+Safeload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31762165245B304300C9F2FB /* UINib+Safeload.swift */; }; + 317F16CE2572CE2800D178B8 /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16CD2572CE2800D178B8 /* AppConfiguration.swift */; }; + 317F16CF2572CE2800D178B8 /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16CD2572CE2800D178B8 /* AppConfiguration.swift */; }; + 317F16D02572CE2800D178B8 /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16CD2572CE2800D178B8 /* AppConfiguration.swift */; }; + 317F16D72572CEC500D178B8 /* ImageUploaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16D62572CEC500D178B8 /* ImageUploaderProtocol.swift */; }; + 317F16D82572CEC500D178B8 /* ImageUploaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16D62572CEC500D178B8 /* ImageUploaderProtocol.swift */; }; + 317F16D92572CEC500D178B8 /* ImageUploaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16D62572CEC500D178B8 /* ImageUploaderProtocol.swift */; }; + 317F16E02572CF5B00D178B8 /* Double+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16DF2572CF5900D178B8 /* Double+String.swift */; }; + 317F16E12572CF5B00D178B8 /* Double+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16DF2572CF5900D178B8 /* Double+String.swift */; }; + 317F16E22572CF5B00D178B8 /* Double+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F16DF2572CF5900D178B8 /* Double+String.swift */; }; + 318A5C07272DD357000DA46C /* HapticFeedbackProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318A5C06272DD357000DA46C /* HapticFeedbackProtocol.swift */; }; + 318A5D7027307764000DA46C /* GraphingAnchorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318A5D6F27307764000DA46C /* GraphingAnchorProtocol.swift */; }; + 318CADC22769241F00DD2A6C /* FolderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318CADC12769241F00DD2A6C /* FolderService.swift */; }; + 3196823B21B791CF00AE0F28 /* Utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 3196823921B791CF00AE0F28 /* Utilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 31984E4C26617371008AD96F /* Decimal+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31984E4B26617371008AD96F /* Decimal+Utils.swift */; }; + 31984E4E26619A03008AD96F /* NSDecimalNumber+Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31984E4D26619A03008AD96F /* NSDecimalNumber+Rounding.swift */; }; + 31984E4F26619A03008AD96F /* NSDecimalNumber+Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31984E4D26619A03008AD96F /* NSDecimalNumber+Rounding.swift */; }; + 31984E5026619A03008AD96F /* NSDecimalNumber+Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31984E4D26619A03008AD96F /* NSDecimalNumber+Rounding.swift */; }; + 319BB728242928D800DC9E97 /* ConditionalParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319BB727242928D700DC9E97 /* ConditionalParser.swift */; }; + 319BB729242928D800DC9E97 /* ConditionalParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319BB727242928D700DC9E97 /* ConditionalParser.swift */; }; + 319BB72A242928D800DC9E97 /* ConditionalParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319BB727242928D700DC9E97 /* ConditionalParser.swift */; }; + 319BB72C2429291E00DC9E97 /* Console.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319BB72B2429291E00DC9E97 /* Console.swift */; }; + 319BB72D2429291E00DC9E97 /* Console.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319BB72B2429291E00DC9E97 /* Console.swift */; }; + 319BB72E2429291E00DC9E97 /* Console.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319BB72B2429291E00DC9E97 /* Console.swift */; }; + 31A7B0D7273E18750089C713 /* RandomAccessCollection+Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A7B0D6273E18750089C713 /* RandomAccessCollection+Sort.swift */; }; + 31AD393326598ADA00EBE339 /* DataLocalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AD393226598AD900EBE339 /* DataLocalizer.swift */; }; + 31AD393426598ADA00EBE339 /* DataLocalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AD393226598AD900EBE339 /* DataLocalizer.swift */; }; + 31AD393526598ADA00EBE339 /* DataLocalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AD393226598AD900EBE339 /* DataLocalizer.swift */; }; + 31B4E5AA273CDC8300342F31 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B4E5A9273CDC8300342F31 /* Throttle.swift */; }; + 31CB2B53256DB261008A26A7 /* LocalizerBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CB2B52256DB261008A26A7 /* LocalizerBuffer.swift */; }; + 31DEFEAC277543E8009BEBF6 /* ExportProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DEFEAB277543E8009BEBF6 /* ExportProtocol.swift */; }; + 31E65AC3216BC9C9008ABEE9 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31E65AB9216BC9C9008ABEE9 /* Utilities.framework */; }; + 31E65ACA216BC9C9008ABEE9 /* Utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 31E65ABC216BC9C9008ABEE9 /* Utilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 35EE5B244960774B9B6D4D55 /* Pods_iOS_UtilitiesTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF030595A23E85168A3AD589 /* Pods_iOS_UtilitiesTests.framework */; }; + 4EDE0B13DB89059A45D982F2 /* Pods_iOS_Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A32AA5C7AC3D4879568CFBD /* Pods_iOS_Utilities.framework */; }; + 6476164127E23FBC008ACFD9 /* LocalAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6476164027E23FBC008ACFD9 /* LocalAuthenticator.swift */; }; + 648327442AF5665600012B75 /* NumericUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648327432AF5665600012B75 /* NumericUtils.swift */; }; + 64D34AC62792345000127C1B /* Clock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D34AC52792345000127C1B /* Clock.swift */; }; + 64FA07B7298AEF8D00626BF0 /* CosmoJavascript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FA07B6298AEF8D00626BF0 /* CosmoJavascript.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 313A536F21B9805F00A92D62 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31E65AB0216BC9C9008ABEE9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 313A536421B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 31E65AC4216BC9C9008ABEE9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31E65AB0216BC9C9008ABEE9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 020F758B298806F600DA2D87 /* AsyncEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncEvent.swift; sourceTree = ""; }; + 020F758D2988072800DA2D87 /* Combine+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Combine+Ext.swift"; sourceTree = ""; }; + 023FA96F28A2C5B4008352E3 /* UIColor+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Hex.swift"; sourceTree = ""; }; + 0253CEC12A9FFF8D0033F064 /* CachedFileLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedFileLoader.swift; sourceTree = ""; }; + 0253CEC32AA009D70033F064 /* DebugEnabled.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugEnabled.swift; sourceTree = ""; }; + 025B752A28A703E800A4AC98 /* ObjectLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectLoader.swift; sourceTree = ""; }; + 025B752C28A7041600A4AC98 /* ClassLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassLoader.swift; sourceTree = ""; }; + 026734EB2BF2B6BB00DBF51A /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + 027E1EE229CA0F790098666F /* KeyValueStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueStoreProtocol.swift; sourceTree = ""; }; + 027E1EE429CA100B0098666F /* UserDefaultsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsStore.swift; sourceTree = ""; }; + 027E1EE829CA1E7A0098666F /* SettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsStore.swift; sourceTree = ""; }; + 0288D4122824A83600B5DBD4 /* CombineObserving.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineObserving.swift; sourceTree = ""; }; + 02ABF37C2AD72314005D799B /* AttributedString+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Ext.swift"; sourceTree = ""; }; + 02C083F22875F9E700D4270E /* NSAttributedString+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Utils.swift"; sourceTree = ""; }; + 02E5003829F3026D00BB55D2 /* Publisher+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+Ext.swift"; sourceTree = ""; }; + 02E90C5B29D74395004E2311 /* FeatureFlagsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagsStore.swift; sourceTree = ""; }; + 02E90C5D29D7444B004E2311 /* DebugSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugSettingsStore.swift; sourceTree = ""; }; + 02EE8FE02828307900225B56 /* CombineObservingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineObservingTests.swift; sourceTree = ""; }; + 02EE90542829A95600225B56 /* NSObject+ObservingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSObject+ObservingTests.swift"; sourceTree = ""; }; + 02F958022A18278B00828F9A /* SecureStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStore.swift; sourceTree = ""; }; + 02F958042A182BC400828F9A /* SecureStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStoreProtocol.swift; sourceTree = ""; }; + 279B87972B97CD4C00466392 /* PointsRating.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PointsRating.swift; sourceTree = ""; }; + 279B87982B97CD4C00466392 /* RatingService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RatingService.swift; sourceTree = ""; }; + 27E31E5A2BE40E4000580E59 /* SynchronizedLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizedLock.swift; sourceTree = ""; }; + 3101F94625112C4100AC4010 /* AuthProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthProtocol.swift; sourceTree = ""; }; + 3101F94925112C5900AC4010 /* AuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + 310E61E8216C0F910043BB33 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 3111F42A2637572300EE3D38 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; + 3112B53A25264670009D19B6 /* NotificationBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationBridge.swift; sourceTree = ""; }; + 311CF2322739D27500C9D780 /* Throttler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Throttler.swift; sourceTree = ""; }; + 312CE38B2630A38700C519C0 /* JavascriptRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavascriptRunner.swift; sourceTree = ""; }; + 312CE3992630A48B00C519C0 /* StringLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringLoader.swift; sourceTree = ""; }; + 31331DDB255347FE0050D74C /* LocalNotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationService.swift; sourceTree = ""; }; + 313A536521B9805F00A92D62 /* Utilities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Utilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 313A536721B9805F00A92D62 /* Utilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = ""; }; + 313A536821B9805F00A92D62 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313A536D21B9805F00A92D62 /* UtilitiesAppleTVTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UtilitiesAppleTVTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 313A537221B9805F00A92D62 /* UtilitiesAppleTVTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilitiesAppleTVTests.swift; sourceTree = ""; }; + 313A537421B9805F00A92D62 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 313B1AA625CB08CE006D369F /* Sequence+Reduce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sequence+Reduce.swift"; sourceTree = ""; }; + 31471F6F24BA2F1E00057221 /* UserAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgent.swift; sourceTree = ""; }; + 314B638823DCCF0100139EB3 /* Directory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Directory.swift; sourceTree = ""; }; + 314B638923DCCF0100139EB3 /* File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; }; + 314B638B23DCCF0100139EB3 /* UrlHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UrlHandler.swift; sourceTree = ""; }; + 314B638D23DCCF0100139EB3 /* ErrorInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorInfo.swift; sourceTree = ""; }; + 314B639023DCCF0100139EB3 /* MapPoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapPoint.swift; sourceTree = ""; }; + 314B639123DCCF0100139EB3 /* MapArea.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapArea.swift; sourceTree = ""; }; + 314B639323DCCF0100139EB3 /* UserInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInterface.swift; sourceTree = ""; }; + 314B639E23DCCF0100139EB3 /* RegionMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegionMonitor.swift; sourceTree = ""; }; + 314B63A323DCCF0100139EB3 /* JsonLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonLoader.swift; sourceTree = ""; }; + 314B63A423DCCF0100139EB3 /* DebugSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugSettings.swift; sourceTree = ""; }; + 314B63A623DCCF0100139EB3 /* CompositeFeatureFlagsProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompositeFeatureFlagsProvider.swift; sourceTree = ""; }; + 314B63A723DCCF0100139EB3 /* FeatureService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureService.swift; sourceTree = ""; }; + 314B63A823DCCF0100139EB3 /* LoadingStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingStatus.swift; sourceTree = ""; }; + 314B63A923DCCF0100139EB3 /* NotificationToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationToken.swift; sourceTree = ""; }; + 314B63AC23DCCF0100139EB3 /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = ""; }; + 314B63AD23DCCF0100139EB3 /* DebugParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugParser.swift; sourceTree = ""; }; + 314B63AF23DCCF0100139EB3 /* Debouncer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = ""; }; + 314B63B023DCCF0100139EB3 /* JsonWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonWriter.swift; sourceTree = ""; }; + 314B63B123DCCF0100139EB3 /* Installation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Installation.swift; sourceTree = ""; }; + 314B63B223DCCF0100139EB3 /* NetworkConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkConnection.swift; sourceTree = ""; }; + 314B63B323DCCF0100139EB3 /* XibLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibLoader.swift; sourceTree = ""; }; + 314B63B423DCCF0100139EB3 /* Weak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Weak.swift; sourceTree = ""; }; + 314B63B623DCCF0100139EB3 /* NSObject+Class.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+Class.h"; sourceTree = ""; }; + 314B63B723DCCF0100139EB3 /* URL+Params.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Params.swift"; sourceTree = ""; }; + 314B63B923DCCF0100139EB3 /* String+Security.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Security.swift"; sourceTree = ""; }; + 314B63BA23DCCF0100139EB3 /* TimeIntervale+String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TimeIntervale+String.swift"; sourceTree = ""; }; + 314B63BB23DCCF0100139EB3 /* Array+Compare.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Compare.swift"; sourceTree = ""; }; + 314B63BC23DCCF0100139EB3 /* NSObject+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Observing.swift"; sourceTree = ""; }; + 314B63BD23DCCF0100139EB3 /* Bundle+UIBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+UIBundle.swift"; sourceTree = ""; }; + 314B63BE23DCCF0100139EB3 /* String+Localized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Localized.swift"; sourceTree = ""; }; + 314B63BF23DCCF0100139EB3 /* Int+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int+Utils.swift"; sourceTree = ""; }; + 314B63C023DCCF0100139EB3 /* DispatchQueue+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Utils.swift"; sourceTree = ""; }; + 314B63C123DCCF0100139EB3 /* Collection+Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection+Random.swift"; sourceTree = ""; }; + 314B63C223DCCF0100139EB3 /* Dictionary+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Utils.swift"; sourceTree = ""; }; + 314B63C423DCCF0100139EB3 /* URLComponents+Params.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URLComponents+Params.swift"; sourceTree = ""; }; + 314B63C523DCCF0100139EB3 /* NSObject+Class.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Class.m"; sourceTree = ""; }; + 314B63C823DCCF0100139EB3 /* Int+Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int+Random.swift"; sourceTree = ""; }; + 314B63C923DCCF0100139EB3 /* NSObject+Association.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Association.swift"; sourceTree = ""; }; + 314B63CA23DCCF0100139EB3 /* String+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Utils.swift"; sourceTree = ""; }; + 314B63CB23DCCF0100139EB3 /* Date+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Utils.swift"; sourceTree = ""; }; + 314B63CF23DCCF0100139EB3 /* TimeCounter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeCounter.swift; sourceTree = ""; }; + 314B63D023DCCF0100139EB3 /* TimeCounterProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeCounterProtocol.swift; sourceTree = ""; }; + 314B63D323DCCF0100139EB3 /* Tracking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tracking.swift; sourceTree = ""; }; + 314B63D423DCCF0100139EB3 /* CompositeTracking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompositeTracking.swift; sourceTree = ""; }; + 314B63D523DCCF0100139EB3 /* DebugTracking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugTracking.swift; sourceTree = ""; }; + 314B63D723DCCF0100139EB3 /* Localizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localizer.swift; sourceTree = ""; }; + 314B63D923DCCF0100139EB3 /* ProgressProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressProtocol.swift; sourceTree = ""; }; + 314B63DA23DCCF0100139EB3 /* ParsingProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsingProtocol.swift; sourceTree = ""; }; + 314B63DB23DCCF0100139EB3 /* SingletonProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingletonProtocol.swift; sourceTree = ""; }; + 3162B55026E18182000209E1 /* WebCrypto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebCrypto.swift; sourceTree = ""; }; + 3162C00524C377E000DE648C /* PrompterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrompterProtocol.swift; sourceTree = ""; }; + 3162C00924C377EF00DE648C /* PrompterFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrompterFactory.swift; sourceTree = ""; }; + 3162C00E24C3783000DE648C /* PrivacyPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyPermission.swift; sourceTree = ""; }; + 3162C01224C3784800DE648C /* PhotoAlbumsPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoAlbumsPermission.swift; sourceTree = ""; }; + 3162C01624C3786D00DE648C /* NotificationPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermission.swift; sourceTree = ""; }; + 3162C01E24C3788700DE648C /* CameraPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPermission.swift; sourceTree = ""; }; + 3162C02224C3789200DE648C /* CalendarPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarPermission.swift; sourceTree = ""; }; + 3162C02724C3791300DE648C /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 31755735262A0CED00D76C4C /* NSNumber+Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNumber+Format.swift"; sourceTree = ""; }; + 31762165245B304300C9F2FB /* UINib+Safeload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINib+Safeload.swift"; sourceTree = ""; }; + 317F16CD2572CE2800D178B8 /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = ""; }; + 317F16D62572CEC500D178B8 /* ImageUploaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploaderProtocol.swift; sourceTree = ""; }; + 317F16DF2572CF5900D178B8 /* Double+String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+String.swift"; sourceTree = ""; }; + 318A5C06272DD357000DA46C /* HapticFeedbackProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedbackProtocol.swift; sourceTree = ""; }; + 318A5D6F27307764000DA46C /* GraphingAnchorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphingAnchorProtocol.swift; sourceTree = ""; }; + 318CADC12769241F00DD2A6C /* FolderService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderService.swift; sourceTree = ""; }; + 3196823721B791CF00AE0F28 /* Utilities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Utilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3196823921B791CF00AE0F28 /* Utilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = ""; }; + 3196823A21B791CF00AE0F28 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31984E4B26617371008AD96F /* Decimal+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+Utils.swift"; sourceTree = ""; }; + 31984E4D26619A03008AD96F /* NSDecimalNumber+Rounding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDecimalNumber+Rounding.swift"; sourceTree = ""; }; + 319BB727242928D700DC9E97 /* ConditionalParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConditionalParser.swift; sourceTree = ""; }; + 319BB72B2429291E00DC9E97 /* Console.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Console.swift; sourceTree = ""; }; + 31A7B0D6273E18750089C713 /* RandomAccessCollection+Sort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RandomAccessCollection+Sort.swift"; sourceTree = ""; }; + 31AD393226598AD900EBE339 /* DataLocalizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataLocalizer.swift; sourceTree = ""; }; + 31B4E5A9273CDC8300342F31 /* Throttle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Throttle.swift; sourceTree = ""; }; + 31CB2B52256DB261008A26A7 /* LocalizerBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizerBuffer.swift; sourceTree = ""; }; + 31DEFEAB277543E8009BEBF6 /* ExportProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportProtocol.swift; sourceTree = ""; }; + 31E65AB9216BC9C9008ABEE9 /* Utilities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Utilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31E65ABC216BC9C9008ABEE9 /* Utilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Utilities.h; sourceTree = ""; }; + 31E65ABD216BC9C9008ABEE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31E65AC2216BC9C9008ABEE9 /* UtilitiesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UtilitiesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 31E65AC9216BC9C9008ABEE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3A32AA5C7AC3D4879568CFBD /* Pods_iOS_Utilities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_Utilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 439FD4525821BD140CEE8DB1 /* Pods-iOS-UtilitiesTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-UtilitiesTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-UtilitiesTests/Pods-iOS-UtilitiesTests.release.xcconfig"; sourceTree = ""; }; + 6476164027E23FBC008ACFD9 /* LocalAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticator.swift; sourceTree = ""; }; + 648327432AF5665600012B75 /* NumericUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericUtils.swift; sourceTree = ""; }; + 64D34AC52792345000127C1B /* Clock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clock.swift; sourceTree = ""; }; + 64FA07B6298AEF8D00626BF0 /* CosmoJavascript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CosmoJavascript.swift; sourceTree = ""; }; + 81539FD394D7847D50BD4749 /* Pods-iOS-UtilitiesTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-UtilitiesTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-UtilitiesTests/Pods-iOS-UtilitiesTests.debug.xcconfig"; sourceTree = ""; }; + BF030595A23E85168A3AD589 /* Pods_iOS_UtilitiesTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_UtilitiesTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D3D9E31E155FDD76DDDF56C9 /* Pods-iOS-Utilities.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-Utilities.debug.xcconfig"; path = "Target Support Files/Pods-iOS-Utilities/Pods-iOS-Utilities.debug.xcconfig"; sourceTree = ""; }; + D93995CCA6E58B36CA45B212 /* Pods-iOS-Utilities.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-Utilities.release.xcconfig"; path = "Target Support Files/Pods-iOS-Utilities/Pods-iOS-Utilities.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 313A536221B9805F00A92D62 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313A536A21B9805F00A92D62 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 313A536E21B9805F00A92D62 /* Utilities.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3196823421B791CF00AE0F28 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AB6216BC9C9008ABEE9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 310E61E9216C0F910043BB33 /* Security.framework in Frameworks */, + 4EDE0B13DB89059A45D982F2 /* Pods_iOS_Utilities.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65ABF216BC9C9008ABEE9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31E65AC3216BC9C9008ABEE9 /* Utilities.framework in Frameworks */, + 35EE5B244960774B9B6D4D55 /* Pods_iOS_UtilitiesTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 020F758A298806E700DA2D87 /* _AsyncStep */ = { + isa = PBXGroup; + children = ( + 020F758B298806F600DA2D87 /* AsyncEvent.swift */, + ); + path = _AsyncStep; + sourceTree = ""; + }; + 026734EA2BF2B69E00DBF51A /* _Logging */ = { + isa = PBXGroup; + children = ( + 026734EB2BF2B6BB00DBF51A /* Logging.swift */, + ); + path = _Logging; + sourceTree = ""; + }; + 027E1EE129CA0F630098666F /* _Store */ = { + isa = PBXGroup; + children = ( + 027E1EE229CA0F790098666F /* KeyValueStoreProtocol.swift */, + 027E1EE429CA100B0098666F /* UserDefaultsStore.swift */, + 027E1EE829CA1E7A0098666F /* SettingsStore.swift */, + 02E90C5B29D74395004E2311 /* FeatureFlagsStore.swift */, + 02E90C5D29D7444B004E2311 /* DebugSettingsStore.swift */, + 02F958022A18278B00828F9A /* SecureStore.swift */, + 02F958042A182BC400828F9A /* SecureStoreProtocol.swift */, + ); + path = _Store; + sourceTree = ""; + }; + 0288D40F28247D2900B5DBD4 /* _Observing */ = { + isa = PBXGroup; + children = ( + 0288D4122824A83600B5DBD4 /* CombineObserving.swift */, + 02E5003829F3026D00BB55D2 /* Publisher+Ext.swift */, + ); + path = _Observing; + sourceTree = ""; + }; + 02EE8FDF2828305C00225B56 /* _Observing */ = { + isa = PBXGroup; + children = ( + 02EE8FE02828307900225B56 /* CombineObservingTests.swift */, + ); + path = _Observing; + sourceTree = ""; + }; + 02EE90532829A93400225B56 /* _Extensions */ = { + isa = PBXGroup; + children = ( + 02EE90542829A95600225B56 /* NSObject+ObservingTests.swift */, + ); + path = _Extensions; + sourceTree = ""; + }; + 279B87962B97CCCC00466392 /* _Rating */ = { + isa = PBXGroup; + children = ( + 279B87972B97CD4C00466392 /* PointsRating.swift */, + 279B87982B97CD4C00466392 /* RatingService.swift */, + ); + path = _Rating; + sourceTree = ""; + }; + 3101F94525112C2700AC4010 /* _Auth */ = { + isa = PBXGroup; + children = ( + 3101F94625112C4100AC4010 /* AuthProtocol.swift */, + 3101F94925112C5900AC4010 /* AuthService.swift */, + ); + path = _Auth; + sourceTree = ""; + }; + 3111F4292637570A00EE3D38 /* _App */ = { + isa = PBXGroup; + children = ( + 3111F42A2637572300EE3D38 /* AppState.swift */, + ); + path = _App; + sourceTree = ""; + }; + 312CE38A2630A37400C519C0 /* _Javascript */ = { + isa = PBXGroup; + children = ( + 312CE38B2630A38700C519C0 /* JavascriptRunner.swift */, + 64FA07B6298AEF8D00626BF0 /* CosmoJavascript.swift */, + ); + path = _Javascript; + sourceTree = ""; + }; + 313A536621B9805F00A92D62 /* UtilitiesAppleTV */ = { + isa = PBXGroup; + children = ( + 313A536721B9805F00A92D62 /* Utilities.h */, + 313A536821B9805F00A92D62 /* Info.plist */, + ); + path = UtilitiesAppleTV; + sourceTree = ""; + }; + 313A537121B9805F00A92D62 /* UtilitiesAppleTVTests */ = { + isa = PBXGroup; + children = ( + 313A537221B9805F00A92D62 /* UtilitiesAppleTVTests.swift */, + 313A537421B9805F00A92D62 /* Info.plist */, + ); + path = UtilitiesAppleTVTests; + sourceTree = ""; + }; + 31471F6E24BA2F0500057221 /* _UserAgent */ = { + isa = PBXGroup; + children = ( + 31471F6F24BA2F1E00057221 /* UserAgent.swift */, + ); + path = _UserAgent; + sourceTree = ""; + }; + 314B638723DCCF0100139EB3 /* _Files */ = { + isa = PBXGroup; + children = ( + 314B638823DCCF0100139EB3 /* Directory.swift */, + 314B638923DCCF0100139EB3 /* File.swift */, + 318CADC12769241F00DD2A6C /* FolderService.swift */, + ); + path = _Files; + sourceTree = ""; + }; + 314B638A23DCCF0100139EB3 /* _URL */ = { + isa = PBXGroup; + children = ( + 314B638B23DCCF0100139EB3 /* UrlHandler.swift */, + ); + path = _URL; + sourceTree = ""; + }; + 314B638C23DCCF0100139EB3 /* _Error */ = { + isa = PBXGroup; + children = ( + 319BB72B2429291E00DC9E97 /* Console.swift */, + 314B638D23DCCF0100139EB3 /* ErrorInfo.swift */, + ); + path = _Error; + sourceTree = ""; + }; + 314B638E23DCCF0100139EB3 /* _Map */ = { + isa = PBXGroup; + children = ( + 314B639123DCCF0100139EB3 /* MapArea.swift */, + 314B639023DCCF0100139EB3 /* MapPoint.swift */, + ); + path = _Map; + sourceTree = ""; + }; + 314B639223DCCF0100139EB3 /* _UserInterface */ = { + isa = PBXGroup; + children = ( + 314B639323DCCF0100139EB3 /* UserInterface.swift */, + ); + path = _UserInterface; + sourceTree = ""; + }; + 314B639D23DCCF0100139EB3 /* _Location */ = { + isa = PBXGroup; + children = ( + 314B639E23DCCF0100139EB3 /* RegionMonitor.swift */, + ); + path = _Location; + sourceTree = ""; + }; + 314B63A123DCCF0100139EB3 /* _Utils */ = { + isa = PBXGroup; + children = ( + 314B63A523DCCF0100139EB3 /* _FeatureFlags */, + 314B63AA23DCCF0100139EB3 /* _Parser */, + 314B63AF23DCCF0100139EB3 /* Debouncer.swift */, + 314B63A423DCCF0100139EB3 /* DebugSettings.swift */, + 0253CEC32AA009D70033F064 /* DebugEnabled.swift */, + 314B63B123DCCF0100139EB3 /* Installation.swift */, + 314B63A323DCCF0100139EB3 /* JsonLoader.swift */, + 314B63B023DCCF0100139EB3 /* JsonWriter.swift */, + 314B63A823DCCF0100139EB3 /* LoadingStatus.swift */, + 314B63B223DCCF0100139EB3 /* NetworkConnection.swift */, + 314B63A923DCCF0100139EB3 /* NotificationToken.swift */, + 312CE3992630A48B00C519C0 /* StringLoader.swift */, + 311CF2322739D27500C9D780 /* Throttler.swift */, + 314B63B423DCCF0100139EB3 /* Weak.swift */, + 3162B55026E18182000209E1 /* WebCrypto.swift */, + 314B63B323DCCF0100139EB3 /* XibLoader.swift */, + 31B4E5A9273CDC8300342F31 /* Throttle.swift */, + 025B752A28A703E800A4AC98 /* ObjectLoader.swift */, + 025B752C28A7041600A4AC98 /* ClassLoader.swift */, + 0253CEC12A9FFF8D0033F064 /* CachedFileLoader.swift */, + 648327432AF5665600012B75 /* NumericUtils.swift */, + 27E31E5A2BE40E4000580E59 /* SynchronizedLock.swift */, + ); + path = _Utils; + sourceTree = ""; + }; + 314B63A523DCCF0100139EB3 /* _FeatureFlags */ = { + isa = PBXGroup; + children = ( + 314B63A623DCCF0100139EB3 /* CompositeFeatureFlagsProvider.swift */, + 314B63A723DCCF0100139EB3 /* FeatureService.swift */, + ); + path = _FeatureFlags; + sourceTree = ""; + }; + 314B63AA23DCCF0100139EB3 /* _Parser */ = { + isa = PBXGroup; + children = ( + 319BB727242928D700DC9E97 /* ConditionalParser.swift */, + 314B63AD23DCCF0100139EB3 /* DebugParser.swift */, + 314B63AC23DCCF0100139EB3 /* Parser.swift */, + 317F16CD2572CE2800D178B8 /* AppConfiguration.swift */, + ); + path = _Parser; + sourceTree = ""; + }; + 314B63B523DCCF0100139EB3 /* _Extensions */ = { + isa = PBXGroup; + children = ( + 314B63BB23DCCF0100139EB3 /* Array+Compare.swift */, + 314B63BD23DCCF0100139EB3 /* Bundle+UIBundle.swift */, + 314B63C123DCCF0100139EB3 /* Collection+Random.swift */, + 314B63CB23DCCF0100139EB3 /* Date+Utils.swift */, + 31984E4B26617371008AD96F /* Decimal+Utils.swift */, + 314B63C223DCCF0100139EB3 /* Dictionary+Utils.swift */, + 314B63C023DCCF0100139EB3 /* DispatchQueue+Utils.swift */, + 317F16DF2572CF5900D178B8 /* Double+String.swift */, + 314B63C823DCCF0100139EB3 /* Int+Random.swift */, + 314B63BF23DCCF0100139EB3 /* Int+Utils.swift */, + 31984E4D26619A03008AD96F /* NSDecimalNumber+Rounding.swift */, + 31755735262A0CED00D76C4C /* NSNumber+Format.swift */, + 314B63C923DCCF0100139EB3 /* NSObject+Association.swift */, + 314B63B623DCCF0100139EB3 /* NSObject+Class.h */, + 314B63C523DCCF0100139EB3 /* NSObject+Class.m */, + 314B63BC23DCCF0100139EB3 /* NSObject+Observing.swift */, + 31A7B0D6273E18750089C713 /* RandomAccessCollection+Sort.swift */, + 313B1AA625CB08CE006D369F /* Sequence+Reduce.swift */, + 314B63BE23DCCF0100139EB3 /* String+Localized.swift */, + 314B63B923DCCF0100139EB3 /* String+Security.swift */, + 314B63CA23DCCF0100139EB3 /* String+Utils.swift */, + 314B63BA23DCCF0100139EB3 /* TimeIntervale+String.swift */, + 31762165245B304300C9F2FB /* UINib+Safeload.swift */, + 314B63B723DCCF0100139EB3 /* URL+Params.swift */, + 314B63C423DCCF0100139EB3 /* URLComponents+Params.swift */, + 02C083F22875F9E700D4270E /* NSAttributedString+Utils.swift */, + 023FA96F28A2C5B4008352E3 /* UIColor+Hex.swift */, + 020F758D2988072800DA2D87 /* Combine+Ext.swift */, + 02ABF37C2AD72314005D799B /* AttributedString+Ext.swift */, + ); + path = _Extensions; + sourceTree = ""; + }; + 314B63CE23DCCF0100139EB3 /* _Time */ = { + isa = PBXGroup; + children = ( + 314B63CF23DCCF0100139EB3 /* TimeCounter.swift */, + 314B63D023DCCF0100139EB3 /* TimeCounterProtocol.swift */, + 64D34AC52792345000127C1B /* Clock.swift */, + ); + path = _Time; + sourceTree = ""; + }; + 314B63D123DCCF0100139EB3 /* _Tracker */ = { + isa = PBXGroup; + children = ( + 314B63D223DCCF0100139EB3 /* _Shared */, + ); + path = _Tracker; + sourceTree = ""; + }; + 314B63D223DCCF0100139EB3 /* _Shared */ = { + isa = PBXGroup; + children = ( + 314B63D423DCCF0100139EB3 /* CompositeTracking.swift */, + 314B63D523DCCF0100139EB3 /* DebugTracking.swift */, + 314B63D323DCCF0100139EB3 /* Tracking.swift */, + ); + path = _Shared; + sourceTree = ""; + }; + 314B63D623DCCF0100139EB3 /* _Localization */ = { + isa = PBXGroup; + children = ( + 31AD393226598AD900EBE339 /* DataLocalizer.swift */, + 314B63D723DCCF0100139EB3 /* Localizer.swift */, + 31CB2B52256DB261008A26A7 /* LocalizerBuffer.swift */, + ); + path = _Localization; + sourceTree = ""; + }; + 314B63D823DCCF0100139EB3 /* _Protocols */ = { + isa = PBXGroup; + children = ( + 314B63DA23DCCF0100139EB3 /* ParsingProtocol.swift */, + 314B63D923DCCF0100139EB3 /* ProgressProtocol.swift */, + 314B63DB23DCCF0100139EB3 /* SingletonProtocol.swift */, + 317F16D62572CEC500D178B8 /* ImageUploaderProtocol.swift */, + 318A5C06272DD357000DA46C /* HapticFeedbackProtocol.swift */, + 318A5D6F27307764000DA46C /* GraphingAnchorProtocol.swift */, + 6476164027E23FBC008ACFD9 /* LocalAuthenticator.swift */, + ); + path = _Protocols; + sourceTree = ""; + }; + 3162C00424C377BF00DE648C /* _Prompter */ = { + isa = PBXGroup; + children = ( + 3162C00924C377EF00DE648C /* PrompterFactory.swift */, + 3162C00524C377E000DE648C /* PrompterProtocol.swift */, + ); + path = _Prompter; + sourceTree = ""; + }; + 3162C00D24C3781D00DE648C /* _Permissions */ = { + isa = PBXGroup; + children = ( + 3162C02224C3789200DE648C /* CalendarPermission.swift */, + 3162C01E24C3788700DE648C /* CameraPermission.swift */, + 3162C01624C3786D00DE648C /* NotificationPermission.swift */, + 3162C01224C3784800DE648C /* PhotoAlbumsPermission.swift */, + 3162C00E24C3783000DE648C /* PrivacyPermission.swift */, + ); + path = _Permissions; + sourceTree = ""; + }; + 3162C02624C378FD00DE648C /* _Notification */ = { + isa = PBXGroup; + children = ( + 3162C02724C3791300DE648C /* NotificationService.swift */, + 3112B53A25264670009D19B6 /* NotificationBridge.swift */, + 31331DDB255347FE0050D74C /* LocalNotificationService.swift */, + ); + path = _Notification; + sourceTree = ""; + }; + 3196823821B791CF00AE0F28 /* UtilitiesAppleWatch */ = { + isa = PBXGroup; + children = ( + 3196823921B791CF00AE0F28 /* Utilities.h */, + 3196823A21B791CF00AE0F28 /* Info.plist */, + ); + path = UtilitiesAppleWatch; + sourceTree = ""; + }; + 31DEFEAA277543C8009BEBF6 /* _Export */ = { + isa = PBXGroup; + children = ( + 31DEFEAB277543E8009BEBF6 /* ExportProtocol.swift */, + ); + path = _Export; + sourceTree = ""; + }; + 31E65AAF216BC9C9008ABEE9 = { + isa = PBXGroup; + children = ( + 8073B624F1440813737063F6 /* Frameworks */, + 31E65ABA216BC9C9008ABEE9 /* Products */, + 31E65ABB216BC9C9008ABEE9 /* Utilities */, + 313A536621B9805F00A92D62 /* UtilitiesAppleTV */, + 313A537121B9805F00A92D62 /* UtilitiesAppleTVTests */, + 3196823821B791CF00AE0F28 /* UtilitiesAppleWatch */, + 31E65AC6216BC9C9008ABEE9 /* UtilitiesTests */, + E3D0EC0C6D6656565C64BC16 /* Pods */, + ); + sourceTree = ""; + }; + 31E65ABA216BC9C9008ABEE9 /* Products */ = { + isa = PBXGroup; + children = ( + 31E65AB9216BC9C9008ABEE9 /* Utilities.framework */, + 31E65AC2216BC9C9008ABEE9 /* UtilitiesTests.xctest */, + 3196823721B791CF00AE0F28 /* Utilities.framework */, + 313A536521B9805F00A92D62 /* Utilities.framework */, + 313A536D21B9805F00A92D62 /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31E65ABB216BC9C9008ABEE9 /* Utilities */ = { + isa = PBXGroup; + children = ( + 279B87962B97CCCC00466392 /* _Rating */, + 027E1EE129CA0F630098666F /* _Store */, + 020F758A298806E700DA2D87 /* _AsyncStep */, + 0288D40F28247D2900B5DBD4 /* _Observing */, + 3111F4292637570A00EE3D38 /* _App */, + 3101F94525112C2700AC4010 /* _Auth */, + 314B638C23DCCF0100139EB3 /* _Error */, + 31DEFEAA277543C8009BEBF6 /* _Export */, + 314B63B523DCCF0100139EB3 /* _Extensions */, + 314B638723DCCF0100139EB3 /* _Files */, + 312CE38A2630A37400C519C0 /* _Javascript */, + 314B63D623DCCF0100139EB3 /* _Localization */, + 314B639D23DCCF0100139EB3 /* _Location */, + 026734EA2BF2B69E00DBF51A /* _Logging */, + 314B638E23DCCF0100139EB3 /* _Map */, + 3162C02624C378FD00DE648C /* _Notification */, + 3162C00D24C3781D00DE648C /* _Permissions */, + 3162C00424C377BF00DE648C /* _Prompter */, + 314B63D823DCCF0100139EB3 /* _Protocols */, + 314B63CE23DCCF0100139EB3 /* _Time */, + 314B63D123DCCF0100139EB3 /* _Tracker */, + 314B638A23DCCF0100139EB3 /* _URL */, + 31471F6E24BA2F0500057221 /* _UserAgent */, + 314B639223DCCF0100139EB3 /* _UserInterface */, + 314B63A123DCCF0100139EB3 /* _Utils */, + 31E65ABD216BC9C9008ABEE9 /* Info.plist */, + 31E65ABC216BC9C9008ABEE9 /* Utilities.h */, + ); + path = Utilities; + sourceTree = ""; + }; + 31E65AC6216BC9C9008ABEE9 /* UtilitiesTests */ = { + isa = PBXGroup; + children = ( + 02EE90532829A93400225B56 /* _Extensions */, + 02EE8FDF2828305C00225B56 /* _Observing */, + ); + path = UtilitiesTests; + sourceTree = ""; + }; + 8073B624F1440813737063F6 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 310E61E8216C0F910043BB33 /* Security.framework */, + 3A32AA5C7AC3D4879568CFBD /* Pods_iOS_Utilities.framework */, + BF030595A23E85168A3AD589 /* Pods_iOS_UtilitiesTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E3D0EC0C6D6656565C64BC16 /* Pods */ = { + isa = PBXGroup; + children = ( + 31E65AC9216BC9C9008ABEE9 /* Info.plist */, + D3D9E31E155FDD76DDDF56C9 /* Pods-iOS-Utilities.debug.xcconfig */, + D93995CCA6E58B36CA45B212 /* Pods-iOS-Utilities.release.xcconfig */, + 81539FD394D7847D50BD4749 /* Pods-iOS-UtilitiesTests.debug.xcconfig */, + 439FD4525821BD140CEE8DB1 /* Pods-iOS-UtilitiesTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 313A536021B9805F00A92D62 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 313A537521B9805F00A92D62 /* Utilities.h in Headers */, + 314B647623DCCF0200139EB3 /* NSObject+Class.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3196823221B791CF00AE0F28 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 3196823B21B791CF00AE0F28 /* Utilities.h in Headers */, + 314B647523DCCF0200139EB3 /* NSObject+Class.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AB4216BC9C9008ABEE9 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 31E65ACA216BC9C9008ABEE9 /* Utilities.h in Headers */, + 314B647423DCCF0200139EB3 /* NSObject+Class.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 313A536421B9805F00A92D62 /* UtilitiesAppleTV */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313A537A21B9805F00A92D62 /* Build configuration list for PBXNativeTarget "UtilitiesAppleTV" */; + buildPhases = ( + 313A536021B9805F00A92D62 /* Headers */, + 313A536121B9805F00A92D62 /* Sources */, + 313A536221B9805F00A92D62 /* Frameworks */, + 313A536321B9805F00A92D62 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = UtilitiesAppleTV; + productName = UtilitiesAppleTV; + productReference = 313A536521B9805F00A92D62 /* Utilities.framework */; + productType = "com.apple.product-type.framework"; + }; + 313A536C21B9805F00A92D62 /* UtilitiesAppleTVTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 313A537B21B9805F00A92D62 /* Build configuration list for PBXNativeTarget "UtilitiesAppleTVTests" */; + buildPhases = ( + 313A536921B9805F00A92D62 /* Sources */, + 313A536A21B9805F00A92D62 /* Frameworks */, + 313A536B21B9805F00A92D62 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 313A537021B9805F00A92D62 /* PBXTargetDependency */, + ); + name = UtilitiesAppleTVTests; + productName = UtilitiesAppleTVTests; + productReference = 313A536D21B9805F00A92D62 /* UtilitiesAppleTVTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 3196823621B791CF00AE0F28 /* UtilitiesAppleWatch */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3196823E21B791CF00AE0F28 /* Build configuration list for PBXNativeTarget "UtilitiesAppleWatch" */; + buildPhases = ( + 3196823221B791CF00AE0F28 /* Headers */, + 3196823321B791CF00AE0F28 /* Sources */, + 3196823421B791CF00AE0F28 /* Frameworks */, + 3196823521B791CF00AE0F28 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = UtilitiesAppleWatch; + productName = UtilitiesAppleWatch; + productReference = 3196823721B791CF00AE0F28 /* Utilities.framework */; + productType = "com.apple.product-type.framework"; + }; + 31E65AB8216BC9C9008ABEE9 /* Utilities */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31E65ACD216BC9C9008ABEE9 /* Build configuration list for PBXNativeTarget "Utilities" */; + buildPhases = ( + C4AE1B01FCD415A3B086A427 /* [CP] Check Pods Manifest.lock */, + 31E65AB4216BC9C9008ABEE9 /* Headers */, + 31E65AB5216BC9C9008ABEE9 /* Sources */, + 31E65AB6216BC9C9008ABEE9 /* Frameworks */, + 31E65AB7216BC9C9008ABEE9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Utilities; + productName = Utilities; + productReference = 31E65AB9216BC9C9008ABEE9 /* Utilities.framework */; + productType = "com.apple.product-type.framework"; + }; + 31E65AC1216BC9C9008ABEE9 /* UtilitiesTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31E65AD0216BC9C9008ABEE9 /* Build configuration list for PBXNativeTarget "UtilitiesTests" */; + buildPhases = ( + 1096C7EFE814E7860FABFC4C /* [CP] Check Pods Manifest.lock */, + 31E65ABE216BC9C9008ABEE9 /* Sources */, + 31E65ABF216BC9C9008ABEE9 /* Frameworks */, + 31E65AC0216BC9C9008ABEE9 /* Resources */, + 160F8B73951F762A10CB5455 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 31E65AC5216BC9C9008ABEE9 /* PBXTargetDependency */, + ); + name = UtilitiesTests; + productName = UtilitiesTests; + productReference = 31E65AC2216BC9C9008ABEE9 /* UtilitiesTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 31E65AB0216BC9C9008ABEE9 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "dYdX Trading Inc"; + TargetAttributes = { + 313A536421B9805F00A92D62 = { + CreatedOnToolsVersion = 10.1; + }; + 313A536C21B9805F00A92D62 = { + CreatedOnToolsVersion = 10.1; + }; + 3196823621B791CF00AE0F28 = { + CreatedOnToolsVersion = 10.1; + }; + 31E65AB8216BC9C9008ABEE9 = { + CreatedOnToolsVersion = 10.0; + LastSwiftMigration = 1020; + }; + 31E65AC1216BC9C9008ABEE9 = { + CreatedOnToolsVersion = 10.0; + }; + }; + }; + buildConfigurationList = 31E65AB3216BC9C9008ABEE9 /* Build configuration list for PBXProject "Utilities" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 31E65AAF216BC9C9008ABEE9; + packageReferences = ( + ); + productRefGroup = 31E65ABA216BC9C9008ABEE9 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 31E65AB8216BC9C9008ABEE9 /* Utilities */, + 3196823621B791CF00AE0F28 /* UtilitiesAppleWatch */, + 313A536421B9805F00A92D62 /* UtilitiesAppleTV */, + 31E65AC1216BC9C9008ABEE9 /* UtilitiesTests */, + 313A536C21B9805F00A92D62 /* UtilitiesAppleTVTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 313A536321B9805F00A92D62 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313A536B21B9805F00A92D62 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3196823521B791CF00AE0F28 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AB7216BC9C9008ABEE9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AC0216BC9C9008ABEE9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1096C7EFE814E7860FABFC4C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-UtilitiesTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 160F8B73951F762A10CB5455 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-UtilitiesTests/Pods-iOS-UtilitiesTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-UtilitiesTests/Pods-iOS-UtilitiesTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-UtilitiesTests/Pods-iOS-UtilitiesTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C4AE1B01FCD415A3B086A427 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-Utilities-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 313A536121B9805F00A92D62 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B64D223DCCF0200139EB3 /* TimeCounter.swift in Sources */, + 314B64B223DCCF0200139EB3 /* NSObject+Class.m in Sources */, + 314B645E23DCCF0200139EB3 /* Debouncer.swift in Sources */, + 317F16D92572CEC500D178B8 /* ImageUploaderProtocol.swift in Sources */, + 31471F7224BA2F1E00057221 /* UserAgent.swift in Sources */, + 314B64E223DCCF0200139EB3 /* DebugTracking.swift in Sources */, + 317F16D02572CE2800D178B8 /* AppConfiguration.swift in Sources */, + 314B643623DCCF0200139EB3 /* JsonLoader.swift in Sources */, + 314B648623DCCF0200139EB3 /* TimeIntervale+String.swift in Sources */, + 3162C00824C377E000DE648C /* PrompterProtocol.swift in Sources */, + 31AD393526598ADA00EBE339 /* DataLocalizer.swift in Sources */, + 314B649623DCCF0200139EB3 /* String+Localized.swift in Sources */, + 314B64E623DCCF0200139EB3 /* Localizer.swift in Sources */, + 31755738262A0CED00D76C4C /* NSNumber+Format.swift in Sources */, + 314B64DA23DCCF0200139EB3 /* Tracking.swift in Sources */, + 314B64A223DCCF0200139EB3 /* Collection+Random.swift in Sources */, + 314B647223DCCF0200139EB3 /* Weak.swift in Sources */, + 314B649E23DCCF0200139EB3 /* DispatchQueue+Utils.swift in Sources */, + 3162C00C24C377EF00DE648C /* PrompterFactory.swift in Sources */, + 314B646223DCCF0200139EB3 /* JsonWriter.swift in Sources */, + 3162C01124C3783000DE648C /* PrivacyPermission.swift in Sources */, + 319BB72E2429291E00DC9E97 /* Console.swift in Sources */, + 314B649A23DCCF0200139EB3 /* Int+Utils.swift in Sources */, + 314B642623DCCF0200139EB3 /* RegionMonitor.swift in Sources */, + 023FA97228A2C5B4008352E3 /* UIColor+Hex.swift in Sources */, + 314B648A23DCCF0200139EB3 /* Array+Compare.swift in Sources */, + 314B63FE23DCCF0200139EB3 /* MapPoint.swift in Sources */, + 314B64DE23DCCF0200139EB3 /* CompositeTracking.swift in Sources */, + 314B640223DCCF0200139EB3 /* MapArea.swift in Sources */, + 314B644A23DCCF0200139EB3 /* NotificationToken.swift in Sources */, + 317F16E22572CF5B00D178B8 /* Double+String.swift in Sources */, + 314B646E23DCCF0200139EB3 /* XibLoader.swift in Sources */, + 314B63EE23DCCF0200139EB3 /* File.swift in Sources */, + 314B640623DCCF0200139EB3 /* UserInterface.swift in Sources */, + 314B64AE23DCCF0200139EB3 /* URLComponents+Params.swift in Sources */, + 314B645223DCCF0200139EB3 /* Parser.swift in Sources */, + 314B644223DCCF0200139EB3 /* FeatureService.swift in Sources */, + 314B644623DCCF0200139EB3 /* LoadingStatus.swift in Sources */, + 313B1AA925CB08CE006D369F /* Sequence+Reduce.swift in Sources */, + 312CE38E2630A38700C519C0 /* JavascriptRunner.swift in Sources */, + 314B64EA23DCCF0200139EB3 /* ProgressProtocol.swift in Sources */, + 314B64CA23DCCF0200139EB3 /* Date+Utils.swift in Sources */, + 314B649223DCCF0200139EB3 /* Bundle+UIBundle.swift in Sources */, + 314B64A623DCCF0200139EB3 /* Dictionary+Utils.swift in Sources */, + 314B63F223DCCF0200139EB3 /* UrlHandler.swift in Sources */, + 31984E5026619A03008AD96F /* NSDecimalNumber+Rounding.swift in Sources */, + 314B64C623DCCF0200139EB3 /* String+Utils.swift in Sources */, + 314B646623DCCF0200139EB3 /* Installation.swift in Sources */, + 314B648E23DCCF0200139EB3 /* NSObject+Observing.swift in Sources */, + 314B64C223DCCF0200139EB3 /* NSObject+Association.swift in Sources */, + 319BB72A242928D800DC9E97 /* ConditionalParser.swift in Sources */, + 314B63F623DCCF0200139EB3 /* ErrorInfo.swift in Sources */, + 314B645623DCCF0200139EB3 /* DebugParser.swift in Sources */, + 314B643A23DCCF0200139EB3 /* DebugSettings.swift in Sources */, + 314B64D623DCCF0200139EB3 /* TimeCounterProtocol.swift in Sources */, + 314B647A23DCCF0200139EB3 /* URL+Params.swift in Sources */, + 314B64F223DCCF0200139EB3 /* SingletonProtocol.swift in Sources */, + 314B643E23DCCF0200139EB3 /* CompositeFeatureFlagsProvider.swift in Sources */, + 3162C02A24C3791300DE648C /* NotificationService.swift in Sources */, + 3162C01924C3786D00DE648C /* NotificationPermission.swift in Sources */, + 314B63EA23DCCF0200139EB3 /* Directory.swift in Sources */, + 314B64BE23DCCF0200139EB3 /* Int+Random.swift in Sources */, + 314B64EE23DCCF0200139EB3 /* ParsingProtocol.swift in Sources */, + 312CE39C2630A48B00C519C0 /* StringLoader.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 313A536921B9805F00A92D62 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 313A537321B9805F00A92D62 /* UtilitiesAppleTVTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3196823321B791CF00AE0F28 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314B64B123DCCF0200139EB3 /* NSObject+Class.m in Sources */, + 314B645D23DCCF0200139EB3 /* Debouncer.swift in Sources */, + 314B64E123DCCF0200139EB3 /* DebugTracking.swift in Sources */, + 31471F7124BA2F1E00057221 /* UserAgent.swift in Sources */, + 317F16D82572CEC500D178B8 /* ImageUploaderProtocol.swift in Sources */, + 31984E4F26619A03008AD96F /* NSDecimalNumber+Rounding.swift in Sources */, + 314B643523DCCF0200139EB3 /* JsonLoader.swift in Sources */, + 314B648523DCCF0200139EB3 /* TimeIntervale+String.swift in Sources */, + 3162C00724C377E000DE648C /* PrompterProtocol.swift in Sources */, + 314B649523DCCF0200139EB3 /* String+Localized.swift in Sources */, + 314B64E523DCCF0200139EB3 /* Localizer.swift in Sources */, + 314B64D923DCCF0200139EB3 /* Tracking.swift in Sources */, + 314B64A123DCCF0200139EB3 /* Collection+Random.swift in Sources */, + 314B647123DCCF0200139EB3 /* Weak.swift in Sources */, + 314B649D23DCCF0200139EB3 /* DispatchQueue+Utils.swift in Sources */, + 314B646123DCCF0200139EB3 /* JsonWriter.swift in Sources */, + 3162C00B24C377EF00DE648C /* PrompterFactory.swift in Sources */, + 314B676F23DCEC9B00139EB3 /* TimeCounter.swift in Sources */, + 3162C01024C3783000DE648C /* PrivacyPermission.swift in Sources */, + 319BB72D2429291E00DC9E97 /* Console.swift in Sources */, + 314B649923DCCF0200139EB3 /* Int+Utils.swift in Sources */, + 314B642523DCCF0200139EB3 /* RegionMonitor.swift in Sources */, + 312CE39B2630A48B00C519C0 /* StringLoader.swift in Sources */, + 314B648923DCCF0200139EB3 /* Array+Compare.swift in Sources */, + 023FA97128A2C5B4008352E3 /* UIColor+Hex.swift in Sources */, + 314B63FD23DCCF0200139EB3 /* MapPoint.swift in Sources */, + 314B64DD23DCCF0200139EB3 /* CompositeTracking.swift in Sources */, + 312CE38D2630A38700C519C0 /* JavascriptRunner.swift in Sources */, + 314B640123DCCF0200139EB3 /* MapArea.swift in Sources */, + 314B644923DCCF0200139EB3 /* NotificationToken.swift in Sources */, + 314B646D23DCCF0200139EB3 /* XibLoader.swift in Sources */, + 314B63ED23DCCF0200139EB3 /* File.swift in Sources */, + 314B640523DCCF0200139EB3 /* UserInterface.swift in Sources */, + 314B64AD23DCCF0200139EB3 /* URLComponents+Params.swift in Sources */, + 314B645123DCCF0200139EB3 /* Parser.swift in Sources */, + 314B644123DCCF0200139EB3 /* FeatureService.swift in Sources */, + 314B644523DCCF0200139EB3 /* LoadingStatus.swift in Sources */, + 317F16E12572CF5B00D178B8 /* Double+String.swift in Sources */, + 314B64E923DCCF0200139EB3 /* ProgressProtocol.swift in Sources */, + 314B64C923DCCF0200139EB3 /* Date+Utils.swift in Sources */, + 314B649123DCCF0200139EB3 /* Bundle+UIBundle.swift in Sources */, + 314B64A523DCCF0200139EB3 /* Dictionary+Utils.swift in Sources */, + 313B1AA825CB08CE006D369F /* Sequence+Reduce.swift in Sources */, + 314B63F123DCCF0200139EB3 /* UrlHandler.swift in Sources */, + 314B64C523DCCF0200139EB3 /* String+Utils.swift in Sources */, + 314B646523DCCF0200139EB3 /* Installation.swift in Sources */, + 314B648D23DCCF0200139EB3 /* NSObject+Observing.swift in Sources */, + 314B64C123DCCF0200139EB3 /* NSObject+Association.swift in Sources */, + 317F16CF2572CE2800D178B8 /* AppConfiguration.swift in Sources */, + 31AD393426598ADA00EBE339 /* DataLocalizer.swift in Sources */, + 319BB729242928D800DC9E97 /* ConditionalParser.swift in Sources */, + 314B63F523DCCF0200139EB3 /* ErrorInfo.swift in Sources */, + 314B645523DCCF0200139EB3 /* DebugParser.swift in Sources */, + 314B643923DCCF0200139EB3 /* DebugSettings.swift in Sources */, + 314B64D523DCCF0200139EB3 /* TimeCounterProtocol.swift in Sources */, + 314B647923DCCF0200139EB3 /* URL+Params.swift in Sources */, + 314B64F123DCCF0200139EB3 /* SingletonProtocol.swift in Sources */, + 314B643D23DCCF0200139EB3 /* CompositeFeatureFlagsProvider.swift in Sources */, + 3162C02924C3791300DE648C /* NotificationService.swift in Sources */, + 31755737262A0CED00D76C4C /* NSNumber+Format.swift in Sources */, + 3162C01824C3786D00DE648C /* NotificationPermission.swift in Sources */, + 314B63E923DCCF0200139EB3 /* Directory.swift in Sources */, + 314B64BD23DCCF0200139EB3 /* Int+Random.swift in Sources */, + 314B64ED23DCCF0200139EB3 /* ParsingProtocol.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65AB5216BC9C9008ABEE9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 02E90C5E29D7444B004E2311 /* DebugSettingsStore.swift in Sources */, + 314B64D023DCCF0200139EB3 /* TimeCounter.swift in Sources */, + 314B64B023DCCF0200139EB3 /* NSObject+Class.m in Sources */, + 314B645C23DCCF0200139EB3 /* Debouncer.swift in Sources */, + 314B64E023DCCF0200139EB3 /* DebugTracking.swift in Sources */, + 314B643423DCCF0200139EB3 /* JsonLoader.swift in Sources */, + 314B648423DCCF0200139EB3 /* TimeIntervale+String.swift in Sources */, + 313B1AA725CB08CE006D369F /* Sequence+Reduce.swift in Sources */, + 317F16E02572CF5B00D178B8 /* Double+String.swift in Sources */, + 3162C02324C3789200DE648C /* CalendarPermission.swift in Sources */, + 64D34AC62792345000127C1B /* Clock.swift in Sources */, + 3111F42B2637572300EE3D38 /* AppState.swift in Sources */, + 314B649423DCCF0200139EB3 /* String+Localized.swift in Sources */, + 318A5D7027307764000DA46C /* GraphingAnchorProtocol.swift in Sources */, + 314B64E423DCCF0200139EB3 /* Localizer.swift in Sources */, + 025B752D28A7041600A4AC98 /* ClassLoader.swift in Sources */, + 314B64D823DCCF0200139EB3 /* Tracking.swift in Sources */, + 314B64A023DCCF0200139EB3 /* Collection+Random.swift in Sources */, + 648327442AF5665600012B75 /* NumericUtils.swift in Sources */, + 312CE38C2630A38700C519C0 /* JavascriptRunner.swift in Sources */, + 31CB2B53256DB261008A26A7 /* LocalizerBuffer.swift in Sources */, + 31755736262A0CED00D76C4C /* NSNumber+Format.swift in Sources */, + 0253CEC22A9FFF8D0033F064 /* CachedFileLoader.swift in Sources */, + 314B647023DCCF0200139EB3 /* Weak.swift in Sources */, + 3162B55126E18183000209E1 /* WebCrypto.swift in Sources */, + 3112B53B25264670009D19B6 /* NotificationBridge.swift in Sources */, + 314B649C23DCCF0200139EB3 /* DispatchQueue+Utils.swift in Sources */, + 02ABF37D2AD72314005D799B /* AttributedString+Ext.swift in Sources */, + 023FA97028A2C5B4008352E3 /* UIColor+Hex.swift in Sources */, + 314B646023DCCF0200139EB3 /* JsonWriter.swift in Sources */, + 025B752B28A703E800A4AC98 /* ObjectLoader.swift in Sources */, + 314B649823DCCF0200139EB3 /* Int+Utils.swift in Sources */, + 314B642423DCCF0200139EB3 /* RegionMonitor.swift in Sources */, + 31A7B0D7273E18750089C713 /* RandomAccessCollection+Sort.swift in Sources */, + 314B648823DCCF0200139EB3 /* Array+Compare.swift in Sources */, + 319BB728242928D800DC9E97 /* ConditionalParser.swift in Sources */, + 314B63FC23DCCF0200139EB3 /* MapPoint.swift in Sources */, + 314B64DC23DCCF0200139EB3 /* CompositeTracking.swift in Sources */, + 3101F94A25112C5900AC4010 /* AuthService.swift in Sources */, + 026734EC2BF2B6BB00DBF51A /* Logging.swift in Sources */, + 020F758E2988072800DA2D87 /* Combine+Ext.swift in Sources */, + 3162C00624C377E000DE648C /* PrompterProtocol.swift in Sources */, + 3162C01324C3784800DE648C /* PhotoAlbumsPermission.swift in Sources */, + 317F16D72572CEC500D178B8 /* ImageUploaderProtocol.swift in Sources */, + 31471F7024BA2F1E00057221 /* UserAgent.swift in Sources */, + 314B640023DCCF0200139EB3 /* MapArea.swift in Sources */, + 6476164127E23FBC008ACFD9 /* LocalAuthenticator.swift in Sources */, + 314B644823DCCF0200139EB3 /* NotificationToken.swift in Sources */, + 27E31E5B2BE40E4000580E59 /* SynchronizedLock.swift in Sources */, + 3162C00A24C377EF00DE648C /* PrompterFactory.swift in Sources */, + 314B646C23DCCF0200139EB3 /* XibLoader.swift in Sources */, + 279B879A2B97CD4C00466392 /* RatingService.swift in Sources */, + 02E90C5C29D74395004E2311 /* FeatureFlagsStore.swift in Sources */, + 31331DDC255347FE0050D74C /* LocalNotificationService.swift in Sources */, + 314B63EC23DCCF0200139EB3 /* File.swift in Sources */, + 31DEFEAC277543E8009BEBF6 /* ExportProtocol.swift in Sources */, + 31AD393326598ADA00EBE339 /* DataLocalizer.swift in Sources */, + 31B4E5AA273CDC8300342F31 /* Throttle.swift in Sources */, + 31984E4C26617371008AD96F /* Decimal+Utils.swift in Sources */, + 314B640423DCCF0200139EB3 /* UserInterface.swift in Sources */, + 314B64AC23DCCF0200139EB3 /* URLComponents+Params.swift in Sources */, + 279B87992B97CD4C00466392 /* PointsRating.swift in Sources */, + 3162C02824C3791300DE648C /* NotificationService.swift in Sources */, + 314B645023DCCF0200139EB3 /* Parser.swift in Sources */, + 31984E4E26619A03008AD96F /* NSDecimalNumber+Rounding.swift in Sources */, + 314B644023DCCF0200139EB3 /* FeatureService.swift in Sources */, + 3162C01F24C3788700DE648C /* CameraPermission.swift in Sources */, + 319BB72C2429291E00DC9E97 /* Console.swift in Sources */, + 31762166245B304300C9F2FB /* UINib+Safeload.swift in Sources */, + 02F958032A18278B00828F9A /* SecureStore.swift in Sources */, + 314B644423DCCF0200139EB3 /* LoadingStatus.swift in Sources */, + 0253CEC42AA009D70033F064 /* DebugEnabled.swift in Sources */, + 312CE39A2630A48B00C519C0 /* StringLoader.swift in Sources */, + 020F758C298806F600DA2D87 /* AsyncEvent.swift in Sources */, + 318A5C07272DD357000DA46C /* HapticFeedbackProtocol.swift in Sources */, + 314B64E823DCCF0200139EB3 /* ProgressProtocol.swift in Sources */, + 027E1EE929CA1E7A0098666F /* SettingsStore.swift in Sources */, + 314B64C823DCCF0200139EB3 /* Date+Utils.swift in Sources */, + 64FA07B7298AEF8D00626BF0 /* CosmoJavascript.swift in Sources */, + 0288D4132824A83600B5DBD4 /* CombineObserving.swift in Sources */, + 314B649023DCCF0200139EB3 /* Bundle+UIBundle.swift in Sources */, + 314B64A423DCCF0200139EB3 /* Dictionary+Utils.swift in Sources */, + 314B63F023DCCF0200139EB3 /* UrlHandler.swift in Sources */, + 314B64C423DCCF0200139EB3 /* String+Utils.swift in Sources */, + 314B646423DCCF0200139EB3 /* Installation.swift in Sources */, + 314B648C23DCCF0200139EB3 /* NSObject+Observing.swift in Sources */, + 311CF2332739D27500C9D780 /* Throttler.swift in Sources */, + 314B64C023DCCF0200139EB3 /* NSObject+Association.swift in Sources */, + 314B63F423DCCF0200139EB3 /* ErrorInfo.swift in Sources */, + 027E1EE529CA100B0098666F /* UserDefaultsStore.swift in Sources */, + 02F958052A182BC400828F9A /* SecureStoreProtocol.swift in Sources */, + 317F16CE2572CE2800D178B8 /* AppConfiguration.swift in Sources */, + 02E5003929F3026D00BB55D2 /* Publisher+Ext.swift in Sources */, + 314B645423DCCF0200139EB3 /* DebugParser.swift in Sources */, + 3162C00F24C3783000DE648C /* PrivacyPermission.swift in Sources */, + 318CADC22769241F00DD2A6C /* FolderService.swift in Sources */, + 314B643823DCCF0200139EB3 /* DebugSettings.swift in Sources */, + 314B648023DCCF0200139EB3 /* String+Security.swift in Sources */, + 314B64D423DCCF0200139EB3 /* TimeCounterProtocol.swift in Sources */, + 027E1EE329CA0F790098666F /* KeyValueStoreProtocol.swift in Sources */, + 314B647823DCCF0200139EB3 /* URL+Params.swift in Sources */, + 02C083F32875F9E700D4270E /* NSAttributedString+Utils.swift in Sources */, + 3162C01724C3786D00DE648C /* NotificationPermission.swift in Sources */, + 314B64F023DCCF0200139EB3 /* SingletonProtocol.swift in Sources */, + 314B643C23DCCF0200139EB3 /* CompositeFeatureFlagsProvider.swift in Sources */, + 314B646823DCCF0200139EB3 /* NetworkConnection.swift in Sources */, + 314B63E823DCCF0200139EB3 /* Directory.swift in Sources */, + 3101F94725112C4100AC4010 /* AuthProtocol.swift in Sources */, + 314B64BC23DCCF0200139EB3 /* Int+Random.swift in Sources */, + 314B64EC23DCCF0200139EB3 /* ParsingProtocol.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31E65ABE216BC9C9008ABEE9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 02EE90552829A95600225B56 /* NSObject+ObservingTests.swift in Sources */, + 02EE8FE12828307900225B56 /* CombineObservingTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 313A537021B9805F00A92D62 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 313A536421B9805F00A92D62 /* UtilitiesAppleTV */; + targetProxy = 313A536F21B9805F00A92D62 /* PBXContainerItemProxy */; + }; + 31E65AC5216BC9C9008ABEE9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31E65AB8216BC9C9008ABEE9 /* Utilities */; + targetProxy = 31E65AC4216BC9C9008ABEE9 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 313A537621B9805F00A92D62 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = UtilitiesAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UtilitiesAppleTV; + PRODUCT_NAME = Utilities; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _tvOS"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313A537721B9805F00A92D62 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = UtilitiesAppleTV/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UtilitiesAppleTV; + PRODUCT_NAME = Utilities; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _tvOS"; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 313A537821B9805F00A92D62 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = UtilitiesAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UtilitiesAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 313A537921B9805F00A92D62 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = UtilitiesAppleTVTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UtilitiesAppleTVTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; + 3196823C21B791CF00AE0F28 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = UtilitiesAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UtilitiesAppleWatch; + PRODUCT_NAME = Utilities; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _watchOS"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 4.0; + }; + name = Debug; + }; + 3196823D21B791CF00AE0F28 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = UtilitiesAppleWatch/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UtilitiesAppleWatch; + PRODUCT_NAME = Utilities; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _watchOS"; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 4.0; + }; + name = Release; + }; + 31E65ACB216BC9C9008ABEE9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 31E65ACC216BC9C9008ABEE9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 31E65ACE216BC9C9008ABEE9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D3D9E31E155FDD76DDDF56C9 /* Pods-iOS-Utilities.debug.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Utilities/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.Utilities; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _iOS"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31E65ACF216BC9C9008ABEE9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D93995CCA6E58B36CA45B212 /* Pods-iOS-Utilities.release.xcconfig */; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Utilities/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.Utilities; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_UIKITFORMAC = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) _iOS"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 31E65AD1216BC9C9008ABEE9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 81539FD394D7847D50BD4749 /* Pods-iOS-UtilitiesTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = UtilitiesTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UtilitiesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31E65AD2216BC9C9008ABEE9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 439FD4525821BD140CEE8DB1 /* Pods-iOS-UtilitiesTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = UtilitiesTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.UtilitiesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 313A537A21B9805F00A92D62 /* Build configuration list for PBXNativeTarget "UtilitiesAppleTV" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313A537621B9805F00A92D62 /* Debug */, + 313A537721B9805F00A92D62 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 313A537B21B9805F00A92D62 /* Build configuration list for PBXNativeTarget "UtilitiesAppleTVTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 313A537821B9805F00A92D62 /* Debug */, + 313A537921B9805F00A92D62 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3196823E21B791CF00AE0F28 /* Build configuration list for PBXNativeTarget "UtilitiesAppleWatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3196823C21B791CF00AE0F28 /* Debug */, + 3196823D21B791CF00AE0F28 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31E65AB3216BC9C9008ABEE9 /* Build configuration list for PBXProject "Utilities" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31E65ACB216BC9C9008ABEE9 /* Debug */, + 31E65ACC216BC9C9008ABEE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31E65ACD216BC9C9008ABEE9 /* Build configuration list for PBXNativeTarget "Utilities" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31E65ACE216BC9C9008ABEE9 /* Debug */, + 31E65ACF216BC9C9008ABEE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31E65AD0216BC9C9008ABEE9 /* Build configuration list for PBXNativeTarget "UtilitiesTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31E65AD1216BC9C9008ABEE9 /* Debug */, + 31E65AD2216BC9C9008ABEE9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 31E65AB0216BC9C9008ABEE9 /* Project object */; +} diff --git a/Utilities/Utilities.xcodeproj/xcshareddata/xcschemes/Utilities.xcscheme b/Utilities/Utilities.xcodeproj/xcshareddata/xcschemes/Utilities.xcscheme new file mode 100644 index 000000000..64677264d --- /dev/null +++ b/Utilities/Utilities.xcodeproj/xcshareddata/xcschemes/Utilities.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Utilities/Utilities/Info.plist b/Utilities/Utilities/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/Utilities/Utilities/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/Utilities/Utilities/Utilities.h b/Utilities/Utilities/Utilities.h new file mode 100644 index 000000000..7c7ae143b --- /dev/null +++ b/Utilities/Utilities/Utilities.h @@ -0,0 +1,20 @@ +// +// Utilities.h +// Utilities +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import +#import +#import + +//! Project version number for Utilities. +FOUNDATION_EXPORT double UtilitiesVersionNumber; + +//! Project version string for Utilities. +FOUNDATION_EXPORT const unsigned char UtilitiesVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import +#import diff --git a/Utilities/Utilities/_App/AppState.swift b/Utilities/Utilities/_App/AppState.swift new file mode 100644 index 000000000..8f6e5749b --- /dev/null +++ b/Utilities/Utilities/_App/AppState.swift @@ -0,0 +1,55 @@ +// +// AppState.swift +// Utilities +// +// Created by Qiang Huang on 4/26/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Foundation + +public typealias ForegroundTask = ()->Void + +public final class AppState: NSObject, SingletonProtocol { + public static var shared: AppState = AppState() + + @objc public private(set) dynamic var background: Bool = false { + didSet { + didSetBackground(oldValue: oldValue) + } + } + + private var foregroundToken: NotificationToken? + private var backgroundToken: NotificationToken? + + private var foregroundTasks: [ForegroundTask] = [] + + override public init() { + super.init() + backgroundToken = NotificationCenter.default.observe(notification: UIApplication.didEnterBackgroundNotification, do: { [weak self] _ in + self?.background = true + }) + foregroundToken = NotificationCenter.default.observe(notification: UIApplication.willEnterForegroundNotification, do: { [weak self] _ in + self?.background = false + }) + } + + public func runForegrounding(task: @escaping ForegroundTask) { + if background { + foregroundTasks.append(task) + } else { + task() + } + } + + private func didSetBackground(oldValue: Bool) { + if background != oldValue { + if !background { + for task in foregroundTasks { + task() + } + foregroundTasks = [] + } + } + } +} diff --git a/Utilities/Utilities/_AsyncStep/AsyncEvent.swift b/Utilities/Utilities/_AsyncStep/AsyncEvent.swift new file mode 100644 index 000000000..6d86f5a37 --- /dev/null +++ b/Utilities/Utilities/_AsyncStep/AsyncEvent.swift @@ -0,0 +1,21 @@ +// +// AsyncEvent.swift +// Utilities +// +// Created by Rui Huang on 1/30/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation +import Combine + +public enum AsyncEvent { + case progress(ProgressType) + case result(ResultType?, Error?) +} + +public protocol AsyncStep { + associatedtype ProgressType + associatedtype ResultType + func run() -> AnyPublisher, Never> +} diff --git a/Utilities/Utilities/_Auth/AuthProtocol.swift b/Utilities/Utilities/_Auth/AuthProtocol.swift new file mode 100644 index 000000000..ec6ab36cf --- /dev/null +++ b/Utilities/Utilities/_Auth/AuthProtocol.swift @@ -0,0 +1,113 @@ +// +// AuthProtocol.swift +// Utilities +// +// Created by Qiang Huang on 9/15/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +public typealias AuthCompletionBlock = (_ succeeded: Bool) -> Void +public typealias AuthAttachmentCompletionBlock = (_ succeeded: Bool) -> Void +public typealias TokenCompletionBlock = (_ token: String?, _ refreshToken: String?, _ provider: String?) -> Void + +@objc public protocol AuthProviderAttachmentProtocol: NSObjectProtocol { + func beforeLogin(completion: @escaping AuthAttachmentCompletionBlock) + func afterLogin() + func beforeLogout(token: String, completion: @escaping AuthAttachmentCompletionBlock) + func afterLogout() +} + +@objc public protocol AuthProviderProtocol: NSObjectProtocol { + @objc var token: String? { get } + var name: String? { get } + var loginXib: String? { get } + var logoutXib: String? { get } + var attachments: [AuthProviderAttachmentProtocol]? { get set } + func token(completion: @escaping TokenCompletionBlock) + func reallyLogin(completion: @escaping AuthCompletionBlock) + func reallyLogout(completion: @escaping AuthCompletionBlock) +} + +public extension AuthProviderProtocol { + func add(attachment: AuthProviderAttachmentProtocol) { + if attachments == nil { + attachments = [AuthProviderAttachmentProtocol]() + } + attachments?.append(attachment) + } + + func login(completion: @escaping AuthCompletionBlock) { + beforeLogin(index: 0) { [weak self] succeeded in + if let self = self, succeeded { + self.reallyLogin { [weak self] successful in + if successful { + self?.afterLogin() + } + completion(successful) + } + } + } + } + + func attachment(at index: Int) -> AuthProviderAttachmentProtocol? { + if index < attachments?.count ?? 0 { + return attachments?[index] + } + return nil + } + + func beforeLogin(index: Int, completion: @escaping AuthAttachmentCompletionBlock) { + if let attachment = self.attachment(at: index) { + attachment.beforeLogin { [weak self] succeeded in + if let self = self, succeeded { + self.beforeLogin(index: index + 1, completion: completion) + } + } + } else { + completion(true) + } + } + + func afterLogin() { + if let attachments = attachments { + for attachment in attachments { + attachment.afterLogin() + } + } + } + + func logout(token: String, completion: @escaping AuthCompletionBlock) { + beforeLogout(token: token, index: 0) { [weak self] _ in + if let self = self { + self.reallyLogout(completion: completion) + } + } + } + + func beforeLogout(token: String, index: Int, completion: @escaping AuthAttachmentCompletionBlock) { + if let attachment = self.attachment(at: index) { + attachment.beforeLogout(token: token) { [weak self] succeeded in + if let self = self, succeeded { + self.beforeLogout(token: token, index: index + 1, completion: completion) + } + } + } else { + reallyLogout { [weak self] successful in + if successful { + self?.afterLogout() + } + completion(successful) + } + } + } + + func afterLogout() { + if let attachments = attachments { + for attachment in attachments { + attachment.afterLogout() + } + } + } +} diff --git a/Utilities/Utilities/_Auth/AuthService.swift b/Utilities/Utilities/_Auth/AuthService.swift new file mode 100644 index 000000000..825e80af7 --- /dev/null +++ b/Utilities/Utilities/_Auth/AuthService.swift @@ -0,0 +1,50 @@ +// +// AuthService.swift +// Utilities +// +// Created by Qiang Huang on 9/15/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +public final class AuthService: NSObject, SingletonProtocol { + public static var shared: AuthService = AuthService() + + private var providers: [AuthProviderProtocol]? + public var name: String? { + didSet { + didSetName(oldValue: oldValue) + } + } + + @objc public dynamic var provider: AuthProviderProtocol? + + public func didSetName(oldValue: String?) { + if name != oldValue { + updateProvider() + } + } + + public func add(provider: AuthProviderProtocol) { + if providers == nil { + providers = [AuthProviderProtocol]() + } + providers?.append(provider) + updateProvider() + } + + private func updateProvider() { + provider = provider(named: name) + } + + private func provider(named: String?) -> AuthProviderProtocol? { + if let named = named { + return providers?.first(where: { (provider) -> Bool in + provider.name == named + }) + } else { + return providers?.first + } + } +} diff --git a/Utilities/Utilities/_Error/Console.swift b/Utilities/Utilities/_Error/Console.swift new file mode 100644 index 000000000..a98a10ad0 --- /dev/null +++ b/Utilities/Utilities/_Error/Console.swift @@ -0,0 +1,30 @@ +// +// Console.swift +// Utilities +// +// Created by Qiang Huang on 3/9/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +public final class Console: NSObject, SingletonProtocol { + public static var shared: Console = Console() + + private var visible: Bool = { + switch Installation.source { + case .debug, .testFlight: return true + case .appStore, .jailBroken: return false + } + }() + + public func log(_ object: Any?) { + if visible, let object = object { + print(object) + } + } + + public func log(_ object1: Any?, _ object2: Any?) { + log("\(object1 ?? "")\n\(object2 ?? "")") + } +} diff --git a/Utilities/Utilities/_Error/ErrorInfo.swift b/Utilities/Utilities/_Error/ErrorInfo.swift new file mode 100644 index 000000000..72c6a5b52 --- /dev/null +++ b/Utilities/Utilities/_Error/ErrorInfo.swift @@ -0,0 +1,180 @@ +// +// ErrorAlert.swift +// ParticlesKit +// +// Created by Qiang Huang on 9/9/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public typealias ErrorActionHandler = () -> Void + +@objc public class ErrorAction: NSObject { + public var text: String + public var handler: ErrorActionHandler + + public init(text: String, handler: @escaping ErrorActionHandler) { + self.text = text + self.handler = handler + super.init() + } + + @IBAction public func action(_ sender: Any?) { + handler() + } +} + +@objc public enum EInfoType: Int { + case info + case wait + case error + case warning + case success +} + +public struct ErrorInfoData { + public var title: String? + public var message: String? + public var type: EInfoType? + public var error: Error? + public var time: Double? + public var actions: [ErrorAction]? +} + +public protocol ErrorInfoProtocol: NSObjectProtocol { + var appState: AppState? { get set } + var pending: ErrorInfoData? { get set } + + func info(data: ErrorInfoData) +} + +public extension ErrorInfoProtocol where Self: NSObject & CombineObserving { + func didSetAppState(oldValue: AppState?) { + changeObservation(from: oldValue, to: appState, keyPath: #keyPath(AppState.background)) {[weak self] observer, obj, change, animated in + if self?.appState?.background == false, let pending = self?.pending { + self?.info(data: pending) + self?.pending = nil + } + } + } +} + +public extension ErrorInfoProtocol { + func info(title: String?, message: String?, error: Error?) { + info(title: title, message: message, type: nil, error: error, time: 3.0) + } + + func info(title: String?, message: String?, error: Error?, time: Double?) { + info(title: title, message: message, type: nil, error: error, time: time, actions: nil) + } + + func info(title: String?, message: String?, error: Error?, actions: [ErrorAction]?) { + info(title: title, message: message, type: nil, error: error, time: 3.0, actions: actions) + } + + func info(title: String?, message: String?, type: EInfoType?, error: Error?) { + info(title: title, message: message, type: type, error: error, time: 3.0) + } + + func info(title: String?, message: String?, type: EInfoType?, error: Error?, time: Double?) { + info(title: title, message: message, type: type, error: error, time: time, actions: nil) + } + + func info(title: String?, message: String?, type: EInfoType?, error: Error?, actions: [ErrorAction]?) { + info(title: title, message: message, type: type, error: error, time: 3.0, actions: actions) + } + + func info(title: String?, message: String?, type: EInfoType?, error: Error?, time: Double?, actions: [ErrorAction]?) { + var message = message + if message == nil, let errors = (error as? NSError)?.userInfo["errors"] as? [[String: String]], let msg = errors.first?["msg"] { + message = trim(message: msg) + } + + process(data: ErrorInfoData(title: title, message: message, type: type, error: error, time: time, actions: actions)) + } + + private func trim(message: String) -> String { + return message.components(separatedBy: "(error=").first?.trim() ?? message + } + + func process(data: ErrorInfoData) { + if appState?.background == false { + info(data: data) + } else { + pending = data + } + } + + func type(type: EInfoType?, error: Error?) -> EInfoType { + if let type = type { + return type + } else { + if let error = error { + let nsError = error as NSError + if nsError.code != 0 { + return .error + } else { + return .info + } + } else { + return .info + } + } + } + + func message(message: String?, error: Error?) -> String? { + if let message = message { + return message + } else { + if let error = error { + let nsError = error as NSError + var text: String? + if let description = nsError.userInfo["description"] as? String { + text = description + } else if let message = nsError.userInfo["message"] as? String { + text = message + } else if let msg = nsError.userInfo["msg"] as? String { + text = msg + } else if let error = nsError.userInfo["error"] as? [String: Any] { + text = error["msg"] as? String ?? error["message"] as? String + } + return text ?? error.localizedDescription + } + return nil + } + } +} + +public class ErrorInfo { + public static var shared: ErrorInfoProtocol? { + didSet { + shared?.appState = AppState.shared + } + } +} + +@objc public class ErrorInfoSwitcher: NSObject { + public static func switcher(errorInfo: ErrorInfoProtocol) -> ErrorInfoSwitcher { + let switcher = ErrorInfoSwitcher() + switcher.errorInfo = errorInfo + return switcher + } + + private var errorInfo: ErrorInfoProtocol? { + didSet { + if errorInfo !== oldValue { + previous = ErrorInfo.shared + ErrorInfo.shared = errorInfo + } + } + } + + private var previous: ErrorInfoProtocol? + + deinit { + if let previous = previous { + ErrorInfo.shared = previous + } + } +} diff --git a/Utilities/Utilities/_Export/ExportProtocol.swift b/Utilities/Utilities/_Export/ExportProtocol.swift new file mode 100644 index 000000000..e7cd6e9ee --- /dev/null +++ b/Utilities/Utilities/_Export/ExportProtocol.swift @@ -0,0 +1,70 @@ +// +// ExportProtocol.swift +// Utilities +// +// Created by Qiang Huang on 12/23/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public protocol DataExportProtocol { + var columes: [String]? { get } + var fileName: String? { get } + var memeType: String? { get } + func text(line: Int, colume: String) -> String? +} + +public extension DataExportProtocol { + func export() -> Data? { + if let columes = columes { + var lines = [String]() + lines.append(join(items: columes, withQuotes: false)) + var index = 0 + var lineItems: [String]? + repeat { + lineItems = text(line: index, columes: columes) + if let lineItems = lineItems { + lines.append(join(items: lineItems, withQuotes: true)) + index += 1 + } + } while lineItems != nil + let text = lines.joined(separator: "\n") + return text.data(using: .utf8) + } else { + return nil + } + } + + func text(line: Int, columes: [String]) -> [String]? { + var items = [String]() + var result = true + for colume in columes { + if let item = text(line: line, colume: colume) { + items.append(item) + } else { + result = false + break + } + } + return result ? items : nil + } + + func join(items: [String], withQuotes: Bool) -> String { + if withQuotes { + return items.map { item in + "\"\(item)\"" + }.joined(separator: ",") + } else { + return items.joined(separator: ",") + } + } +} + +public protocol ExporterProtocol { + func export(exporters: [DataExportProtocol]?) +} + +public class Exporter { + public static var shared: ExporterProtocol? +} diff --git a/Utilities/Utilities/_Extensions/Array+Compare.swift b/Utilities/Utilities/_Extensions/Array+Compare.swift new file mode 100644 index 000000000..cfcb4290d --- /dev/null +++ b/Utilities/Utilities/_Extensions/Array+Compare.swift @@ -0,0 +1,62 @@ +// +// Array+Compare.swift +// Utilities +// +// Created by Qiang Huang on 1/19/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +extension Array { + public func object(at index: Int) -> Element? { + if index < count { + return self[index] + } + return nil + } + + public func at(_ index: Int) -> Element? { + if index < count { + return self[index] + } + return nil + } +} + +extension Array where Element: NSObjectProtocol { + public func containsSame(as other: [Element]?) -> Bool { + if let other = other { + if count == other.count { + var pass = true + for i in 0 ..< count { + if self[i] !== other[i] { + pass = false + break + } + } + return pass + } + } + return false + } + + public mutating func add(_ object: Element?) { + if let object = object { + append(object) + } + } + + public func maybe() -> Array? { + if count > 0 { + return self + } + return nil + } +} + +extension Array { + public func filterNils() -> Array where Element == T? { + compactMap { $0 } + } +} diff --git a/Utilities/Utilities/_Extensions/AttributedString+Ext.swift b/Utilities/Utilities/_Extensions/AttributedString+Ext.swift new file mode 100644 index 000000000..4b990ae35 --- /dev/null +++ b/Utilities/Utilities/_Extensions/AttributedString+Ext.swift @@ -0,0 +1,31 @@ +// +// AttributedString+Ext.swift +// Utilities +// +// Created by Rui Huang on 10/11/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +extension AttributedString { + public init(text: String, url: URL?) { + self.init(text) + if let url = url { + self.link = url + self.foregroundColor = .link + } + } + + public init(text: String, urlString: String?) { + self.init(text: text, url: createUrl(string: urlString)) + } +} + +private func createUrl(string: String?) -> URL? { + if let string = string { + return URL(string: string) + } else { + return nil + } +} diff --git a/Utilities/Utilities/_Extensions/Bundle+UIBundle.swift b/Utilities/Utilities/_Extensions/Bundle+UIBundle.swift new file mode 100644 index 000000000..bf3711b28 --- /dev/null +++ b/Utilities/Utilities/_Extensions/Bundle+UIBundle.swift @@ -0,0 +1,149 @@ +// +// Bundle+UIBundle.swift +// Utilities +// +// Created by John Huang on 10/27/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +#if _macOS + extension Bundle { + open func loadNibNamed(_ name: String, owner: Any?, options: [Any]? = nil) -> [Any]? { + var nibContents: NSArray? + if Bundle.ui().loadNibNamed(name, owner: owner, topLevelObjects: &nibContents) { + return nibContents as? [Any] + } + return nil + } + } +#endif + +public extension Bundle { + @objc static var particles: [Bundle] = { + var bundles = [Bundle]() + bundles.append(Bundle.main) + if let json = JsonLoader.load(bundle: Bundle.main, fileName: "ui.json") as? [String] { + let names = json.map({ name -> String in + name.lowercased() + }) + let set = Set(names) + var map = [String: Bundle]() + let frameworks = Bundle.allFrameworks + for framework in frameworks { + let name = framework.bundlePath.lastPathComponent.stringByDeletingPathExtension.lowercased() + if set.contains(name) { + map[name] = framework + } + } + var uiBundle: Bundle? + for name in names { + if let bundle = map[name] { + bundles.append(bundle) + if uiBundle == nil { + uiBundle = bundle + } + } + } + if let downloaded = downloaded(bundle: uiBundle) { + bundles.append(downloaded) + } + } + return bundles + }() + + @objc static func load(xib name: String, owner: Any?, options: [UINib.OptionsKey: Any]? = nil) -> [Any]? { + var result: [Any]? + for bundle in particles { + result = bundle.safeLoad(xib: name, owner: owner, options: options) + } + return result + } + + @objc class func downloaded(bundle: Bundle?) -> Bundle? { + if let bundleName = bundle?.bundlePath.lastPathComponent.stringByDeletingPathExtension, let document = FolderService.shared?.documents() { + let bundlePath = URL(fileURLWithPath: document).appendingPathComponent(bundleName).absoluteString + return Bundle(path: bundlePath) + } + return nil + } + + var scheme: String? { + return parser.asStrings( + parser.asDictionary( + parser.asArray(Bundle.main.infoDictionary?["CFBundleURLTypes"])?.first + )?["CFBundleURLSchemes"] + )?.first + } +} + +public extension Bundle { + var displayName: String? { + return object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? + object(forInfoDictionaryKey: "CFBundleName") as? String + } + + @objc func safeLoad(xib: String, owner: Any? = nil, options: [UINib.OptionsKey: Any]? = nil) -> [Any]? { + let file = path(forResource: xib, ofType: "nib") + if File.exists(file) { + return loadNibNamed(xib, owner: owner, options: options) + } + return nil + } +} + +public extension Bundle { + var version: String? { + return infoDictionary?["CFBundleShortVersionString"] as? String + } + + var build: String? { + return infoDictionary?["CFBundleVersion"] as? String + } + + var versionAndBuild: String? { + if let version = version { + if let build = build { + return "\(version).\(build)" + } else { + return version + } + } + return nil + } + + var versionPretty: String? { + if let version = version { + return "v\(version)" + } + return nil + } + + func versionCompare(otherVersion: String) -> ComparisonResult { + guard let version = version else { + return .orderedAscending + } + + let versionDelimiter = "." + + var versionComponents = version.components(separatedBy: versionDelimiter) // <1> + var otherVersionComponents = otherVersion.components(separatedBy: versionDelimiter) + + let zeroDiff = versionComponents.count - otherVersionComponents.count // <2> + + if zeroDiff == 0 { // <3> + // Same format, compare normally + return version.compare(otherVersion, options: .numeric) + } else { + let zeros = Array(repeating: "0", count: abs(zeroDiff)) // <4> + if zeroDiff > 0 { + otherVersionComponents.append(contentsOf: zeros) // <5> + } else { + versionComponents.append(contentsOf: zeros) + } + return versionComponents.joined(separator: versionDelimiter) + .compare(otherVersionComponents.joined(separator: versionDelimiter), options: .numeric) // <6> + } + } +} diff --git a/Utilities/Utilities/_Extensions/Collection+Random.swift b/Utilities/Utilities/_Extensions/Collection+Random.swift new file mode 100644 index 000000000..b11196a65 --- /dev/null +++ b/Utilities/Utilities/_Extensions/Collection+Random.swift @@ -0,0 +1,20 @@ +// +// Collection+Random.swift +// Utilities +// +// Created by Qiang Huang on 12/31/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +extension Collection { + func random() -> Self.Iterator.Element? { + let count = distance(from: startIndex, to: endIndex) + if count > 0 { + let roll = Int.random(lower: 0, upper: count - 1) + return self[index(startIndex, offsetBy: roll)] + } + return nil + } +} diff --git a/Utilities/Utilities/_Extensions/Combine+Ext.swift b/Utilities/Utilities/_Extensions/Combine+Ext.swift new file mode 100644 index 000000000..a3f0d853d --- /dev/null +++ b/Utilities/Utilities/_Extensions/Combine+Ext.swift @@ -0,0 +1,58 @@ +// +// Combine+Ext.swift +// Utilities +// +// Created by Rui Huang on 1/30/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Combine + +public extension AnyPublisher { + + static func create(_ subscribe: @escaping (AnySubscriber) -> AnyCancellable) -> AnyPublisher { + + let passthroughSubject = PassthroughSubject() + var cancellable: AnyCancellable? + + return passthroughSubject + .handleEvents(receiveSubscription: { _ in + + let subscriber = AnySubscriber { _ in + + } receiveValue: { input in + passthroughSubject.send(input) + return .unlimited + } receiveCompletion: { completion in + passthroughSubject.send(completion: completion) + } + + DispatchQueue.main.async { + cancellable = subscribe(subscriber) + } + + }, receiveCompletion: { _ in + + }, receiveCancel: { + cancellable?.cancel() + }) + .eraseToAnyPublisher() + } +} + +public extension Timer { + static func publish(every interval: TimeInterval, + on runLoop: RunLoop = .main, + in mode: RunLoop.Mode = .common, + triggerNow: Bool) -> AnyPublisher { + let publisher = Timer.publish(every: interval, on: runLoop, in: mode).autoconnect() + if triggerNow { + return publisher + .prepend(Date()) + .eraseToAnyPublisher() + } else { + return publisher + .eraseToAnyPublisher() + } + } +} diff --git a/Utilities/Utilities/_Extensions/Date+Utils.swift b/Utilities/Utilities/_Extensions/Date+Utils.swift new file mode 100644 index 000000000..3e33efa87 --- /dev/null +++ b/Utilities/Utilities/_Extensions/Date+Utils.swift @@ -0,0 +1,227 @@ +// +// Date+Utils.swift +// Utilities +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public extension Date { + static var iso8601Formatter: ISO8601DateFormatter = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + return formatter + }() + + static var gmtFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en-US") + let gmt = NSTimeZone(abbreviation: "GMT") + if let aGmt = gmt { + formatter.timeZone = aGmt as TimeZone + } + formatter.dateFormat = nil + formatter.dateStyle = .none + formatter.timeStyle = .none + + return formatter + }() + + static var localFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en-US") + formatter.timeZone = NSTimeZone.local as TimeZone + formatter.dateFormat = nil + formatter.dateStyle = .none + formatter.timeStyle = .none + return formatter + }() + + static func date(serverString: String) -> Date? { + let formatter = localFormatter + formatter.dateFormat = "M/d/y" + return formatter.date(from: serverString) + } + + var serverDateString: String { + let formatter = type(of: self).localFormatter + formatter.dateFormat = "M/d/y" + return formatter.string(from: self) + } + + static func datetime(gmtServerString: String?) -> Date? { + if let gmtServerString = gmtServerString { + let formatter = gmtFormatter + formatter.dateFormat = "MM/dd/yyyy H:mm:ss" + var datetime = formatter.date(from: gmtServerString) + if datetime == nil { + formatter.dateFormat = "MM/dd/yyyy h:mm:ss a" + datetime = formatter.date(from: gmtServerString) + } + if datetime == nil { + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + datetime = formatter.date(from: gmtServerString) + } + return datetime + } + return nil + } + + static func datetime(iso8601ServerString: String?) -> Date? { + if let iso8601ServerString = iso8601ServerString { + return iso8601Formatter.date(from: iso8601ServerString) + } + return nil + } + + var gmtServerDatetimeString: String { + let formatter = type(of: self).gmtFormatter + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + return formatter.string(from: self) + } + + var utcServerDatetimeString: String { + let formatter = type(of: self).localFormatter + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSxxx" + return formatter.string(from: self) + } + + var iso8601ServerDatetimeString: String { + return type(of: self).iso8601Formatter.string(from: self) + } + + static func datetime(gmtSqliteString: String) -> Date? { + let formatter = gmtFormatter + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + return formatter.date(from: gmtSqliteString) + } + + var gmtSqliteDatetimeString: String { + let formatter = type(of: self).gmtFormatter + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + return formatter.string(from: self) + } + + static func date(gmtSqliteString: String?) -> Date? { + if let gmtSqliteString = gmtSqliteString { + let formatter = gmtFormatter + formatter.dateFormat = "yyyy-MM-dd" + return formatter.date(from: gmtSqliteString) + } + return nil + } + + var gmtSqliteDateString: String { + let formatter = type(of: self).gmtFormatter + formatter.dateFormat = "yyyy-MM-dd" + return formatter.string(from: self) + } + + var localDatetimeString: String { + let formatter = type(of: self).localFormatter + formatter.dateStyle = .short + formatter.timeStyle = .short + return formatter.string(from: self) + } + + var localLongDateString: String { + let formatter = type(of: self).localFormatter + formatter.dateStyle = .long + formatter.timeStyle = .none + return formatter.string(from: self) + } + + var localLongTimeString: String { + let formatter = type(of: self).localFormatter + formatter.dateStyle = .none + formatter.timeStyle = .long + return formatter.string(from: self) + } + + var localDateString: String { + let formatter = type(of: self).localFormatter + formatter.dateStyle = .short + formatter.timeStyle = .none + return formatter.string(from: self) + } + + var localTimeString: String { + let formatter = type(of: self).localFormatter + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter.string(from: self) + } + + static func date(string: String) -> Date? { + let formatter = localFormatter + formatter.dateStyle = .short + formatter.timeStyle = .short + return formatter.date(from: string) + } + + var englishDatetimeString: String { + let formatter = type(of: self).localFormatter + formatter.dateStyle = .short + formatter.timeStyle = .short + return formatter.string(from: self) + } + + var timeString: String { + let formatter = type(of: self).localFormatter + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter.string(from: self) + } + + func add(month: Int) -> Date? { + let gregorian = Calendar(identifier: .gregorian) + var components = DateComponents() + components.month = month + return gregorian.date(byAdding: components, to: self) + } + + func add(day: Int) -> Date? { + let gregorian = Calendar(identifier: .gregorian) + var components = DateComponents() + components.day = day + return gregorian.date(byAdding: components, to: self) + } +} + +public extension Date { + var startOfDay: Date { + return Calendar.current.startOfDay(for: self) + } + + var endOfDay: Date { + var components = DateComponents() + components.day = 1 + components.second = -1 + return Calendar.current.date(byAdding: components, to: startOfDay)! + } + + var tomorrow: Date { + var components = DateComponents() + components.day = 1 + return Calendar.current.date(byAdding: components, to: self)! + } + + var startOfMonth: Date { + let components = Calendar.current.dateComponents([.year, .month], from: startOfDay) + return Calendar.current.date(from: components)! + } + + var endOfMonth: Date { + var components = DateComponents() + components.month = 1 + components.second = -1 + return Calendar.current.date(byAdding: components, to: startOfMonth)! + } + + var nextHour: Date { + let components = Calendar.current.dateComponents([.year, .month, .day, .hour], from: self) + return Calendar.current.date(from: components)!.addingTimeInterval(3600) + } +} diff --git a/Utilities/Utilities/_Extensions/Decimal+Utils.swift b/Utilities/Utilities/_Extensions/Decimal+Utils.swift new file mode 100644 index 000000000..5cc61113f --- /dev/null +++ b/Utilities/Utilities/_Extensions/Decimal+Utils.swift @@ -0,0 +1,30 @@ +// +// Decimal+Utils.swift +// Utilities +// +// Created by Qiang Huang on 5/28/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Foundation + +public extension Decimal { + func mod(_ b: Decimal) -> Decimal { + var d = self / b + var f: Decimal = 0 + NSDecimalRound(&f, &d, 0, .down) + return self - (b * f) + } + + mutating func round(_ scale: Int, _ roundingMode: NSDecimalNumber.RoundingMode) { + var localCopy = self + NSDecimalRound(&self, &localCopy, scale, roundingMode) + } + + func rounded(_ scale: Int, _ roundingMode: NSDecimalNumber.RoundingMode) -> Decimal { + var result = Decimal() + var localCopy = self + NSDecimalRound(&result, &localCopy, scale, roundingMode) + return result + } +} diff --git a/Utilities/Utilities/_Extensions/Dictionary+Utils.swift b/Utilities/Utilities/_Extensions/Dictionary+Utils.swift new file mode 100644 index 000000000..b64b30d05 --- /dev/null +++ b/Utilities/Utilities/_Extensions/Dictionary+Utils.swift @@ -0,0 +1,70 @@ +// +// Dictionary+Untils.swift +// Utilities +// +// Created by Qiang Huang on 10/28/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public class DictionaryUtils { + public static func merge(_ dictionary1: [String: Any]?, with dictionary2: [String: Any]?) -> [String: Any]? { + if let dictionary1 = dictionary1 { + if let dictionary2 = dictionary2 { + return dictionary1.merging(dictionary2, uniquingKeysWith: { _, last in last }) + } else { + return dictionary1 + } + } else { + return dictionary2 + } + } +} + +extension Dictionary { + public func percentEscaped() -> String { + return map { key, value in + let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" + let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" + return escapedKey + "=" + escapedValue + } + .joined(separator: "&") + } +} + +extension Dictionary where Key == String { + public subscript(caseInsensitive key: Key) -> Value? { + get { + if let k = keys.first(where: { $0.caseInsensitiveCompare(key) == .orderedSame }) { + return self[k] + } + return nil + } + set { + if let k = keys.first(where: { $0.caseInsensitiveCompare(key) == .orderedSame }) { + self[k] = newValue + } else { + self[key] = newValue + } + } + } +} + +extension CharacterSet { + static let urlQueryValueAllowed: CharacterSet = { + let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 + let subDelimitersToEncode = "!$&'()*+,;=" + + var allowed = CharacterSet.urlQueryAllowed + allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") + return allowed + }() +} + +extension Dictionary { + public func filterNils() -> [Key: Value] where Value == Optional { + filter { $0.value != nil } as? [Key: T] ?? self + } +} + diff --git a/Utilities/Utilities/_Extensions/DispatchQueue+Utils.swift b/Utilities/Utilities/_Extensions/DispatchQueue+Utils.swift new file mode 100644 index 000000000..1c2a96208 --- /dev/null +++ b/Utilities/Utilities/_Extensions/DispatchQueue+Utils.swift @@ -0,0 +1,23 @@ +// +// DispatchQueue+Utils.swift +// Utilities +// +// Created by Qiang Huang on 12/27/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public typealias RunBlock = @convention(block) () -> Void + +extension DispatchQueue { + public static func runInMainThread(_ block: @escaping RunBlock) { + if Thread.isMainThread { + block() + } else { + DispatchQueue.main.async { + block() + } + } + } +} diff --git a/Utilities/Utilities/_Extensions/Double+String.swift b/Utilities/Utilities/_Extensions/Double+String.swift new file mode 100644 index 000000000..490d74a84 --- /dev/null +++ b/Utilities/Utilities/_Extensions/Double+String.swift @@ -0,0 +1,47 @@ +// +// Double+String.swift +// Utilities +// +// Created by Qiang Huang on 11/28/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +extension Double { + public var shortString: String { + if isNaN { + return "NaN" + } + if isInfinite { + return "\(self < 0.0 ? "-" : "+")Infinity" + } + let units = ["", "k", "M"] + var interval = self + var i = 0 + while i < units.count - 1 { + if abs(interval) < 1000.0 { + break + } + i += 1 + interval /= 1000.0 + } + // + 2 to have one digit after the comma, + 1 to not have any. + // Remove the * and the number of digits argument to display all the digits after the comma. + return "\(String(format: "%0.*g", Int(log10(abs(interval))) + 2, interval))\(units[i])" + } + + public func round(to places: Int) -> Double { + let divisor = pow(10.0, Double(places)) + return (self * divisor).rounded() / divisor + } + + public var stringValue: String? { "\(self)" } +} + +public extension Optional where Wrapped == Double { + var stringValue: String? { + guard let self else { return nil } + return "\(self)" + } +} diff --git a/Utilities/Utilities/_Extensions/Int+Random.swift b/Utilities/Utilities/_Extensions/Int+Random.swift new file mode 100644 index 000000000..09d97f77a --- /dev/null +++ b/Utilities/Utilities/_Extensions/Int+Random.swift @@ -0,0 +1,16 @@ +// +// Int+Random.swift +// Utilities +// +// Created by Qiang Huang on 12/31/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public extension Int { + static func random(lower: Int, upper: Int) -> Int { + let range = upper - lower + 1 + return lower + Int(arc4random_uniform(UInt32(range))) + } +} diff --git a/Utilities/Utilities/_Extensions/Int+Utils.swift b/Utilities/Utilities/_Extensions/Int+Utils.swift new file mode 100644 index 000000000..99114b488 --- /dev/null +++ b/Utilities/Utilities/_Extensions/Int+Utils.swift @@ -0,0 +1,23 @@ +// +// Int+Utils.swift +// Utilities +// +// Created by Qiang Huang on 2/2/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +extension Int { + public static func ascending(int1: Int?, int2: Int?) -> Bool { + if int1 != nil { + if int2 != nil { + return int1! < int2! + } else { + return false + } + } else { + return true + } + } +} diff --git a/Utilities/Utilities/_Extensions/NSAttributedString+Utils.swift b/Utilities/Utilities/_Extensions/NSAttributedString+Utils.swift new file mode 100644 index 000000000..5c5efe0ef --- /dev/null +++ b/Utilities/Utilities/_Extensions/NSAttributedString+Utils.swift @@ -0,0 +1,17 @@ +// +// NSAttributedString+Utils.swift +// Utilities +// +// Created by Rui Huang on 7/6/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public extension NSAttributedString { + func appending(string: NSAttributedString) -> NSAttributedString { + let mutableStr = NSMutableAttributedString(attributedString: self) + mutableStr.append(string) + return mutableStr + } +} diff --git a/Utilities/Utilities/_Extensions/NSDecimalNumber+Rounding.swift b/Utilities/Utilities/_Extensions/NSDecimalNumber+Rounding.swift new file mode 100644 index 000000000..51edd4588 --- /dev/null +++ b/Utilities/Utilities/_Extensions/NSDecimalNumber+Rounding.swift @@ -0,0 +1,58 @@ +// +// NSNumber+Rounding.swift +// Utilities +// +// Created by Qiang Huang on 5/28/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Foundation + +public extension NSDecimalNumber { + func up(dp: Int16) -> NSDecimalNumber { + return round(rm: .up, dp: dp) + } + + func down(dp: Int16) -> NSDecimalNumber { + return round(rm: .down, dp: dp) + } + + func round(rm: NSDecimalNumber.RoundingMode, dp: Int16) -> NSDecimalNumber { + return rounding(accordingToBehavior: NSDecimalNumberHandler(roundingMode: rm, scale: dp, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false)) + } + + func round(rm: NSDecimalNumber.RoundingMode, stepSize: Double) -> NSDecimalNumber { + let scale = scale(step: stepSize) + return round(rm: rm, dp: scale) + } + + func scale(step: Double) -> Int16 { + if step == 1000 { + return -3 + } else if step == 100 { + return -2 + } else if step == 10 { + return -1 + } else if step == 1.0 { + return 0 + } else if step == 0.1 { + return 1 + } else if step == 0.01 { + return 2 + } else if step == 0.001 { + return 3 + } else if step == 0.0001 { + return 4 + } else if step == 0.00001 { + return 5 + } else if step == 0.000001 { + return 6 + } else if step == 0.0000001 { + return 7 + } else if step == 0.00000001 { + return 8 + } else { + return 0 + } + } +} diff --git a/Utilities/Utilities/_Extensions/NSNumber+Format.swift b/Utilities/Utilities/_Extensions/NSNumber+Format.swift new file mode 100644 index 000000000..b298084ba --- /dev/null +++ b/Utilities/Utilities/_Extensions/NSNumber+Format.swift @@ -0,0 +1,136 @@ +// +// NSNumber+Format.swift +// Utilities +// +// Created by Qiang Huang on 4/16/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Foundation + +public extension NSNumber { + private static var currencyFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.locale = Locale.current + formatter.numberStyle = .currency + return formatter + }() + + private static var deepDollorFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.locale = Locale.current + formatter.numberStyle = .currency + formatter.minimumIntegerDigits = 1 + formatter.minimumFractionDigits = 2 + formatter.maximumFractionDigits = 6 + formatter.minimumSignificantDigits = 1 + formatter.maximumSignificantDigits = 7 + return formatter + }() + + private static var percentageFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .percent + formatter.minimumIntegerDigits = 1 + formatter.minimumFractionDigits = 2 + formatter.maximumFractionDigits = 2 + formatter.minimumSignificantDigits = 1 + formatter.maximumSignificantDigits = 3 + return formatter + }() + + private static var deepPercentageFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .percent + formatter.minimumIntegerDigits = 1 + formatter.minimumFractionDigits = 2 + formatter.maximumFractionDigits = 6 + formatter.minimumSignificantDigits = 1 + formatter.maximumSignificantDigits = 7 + return formatter + }() + + private static var significantDigitsFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.minimumIntegerDigits = 1 + formatter.minimumFractionDigits = 2 + formatter.maximumFractionDigits = 6 + formatter.minimumSignificantDigits = 1 + formatter.maximumSignificantDigits = 5 + return formatter + }() + + /* + func asCurrency() -> String? { + return NSNumber.currencyFormatter.string(from: self) + } + + func asDollarVolume() -> String? { + let postfix = ["", "K", "M", "B", "T"] + var value = decimalValue + var index = 0 + while value > 1000.0 && index < (postfix.count - 1) { + value = value / 1000.0 + index += 1 + } + if let numberString = NSNumber.significantDigitsFormatter.string(from: NSDecimalNumber(decimal: value)) { + return "\(numberString)\(postfix[index])" + } + return nil + } + + func asPercentage() -> String? { + return NSNumber.percentageFormatter.string(from: self) + } + + func asDeepPercentage() -> String? { + return NSNumber.deepPercentageFormatter.string(from: self) + } + + */ + + @objc func abs() -> NSNumber { + return NSNumber(value: Swift.abs(doubleValue)) + } + + @objc func positiveOrZero() -> Bool { + return doubleValue >= 0.0 + } + + @objc func negative() -> NSNumber { + return NSNumber(value: doubleValue * -1.0) + } + + @objc func floor(_ minimum: Double) -> NSNumber { + if doubleValue >= minimum { + return self + } else { + return NSNumber(value: minimum) + } + } +} + +public extension NSDecimalNumber { + override func abs() -> NSNumber { + return NSDecimalNumber(decimal: Swift.abs(decimalValue)) + } + + override func positiveOrZero() -> Bool { + return decimalValue >= 0.0 + } + + convenience init?(decimal: Decimal?) { + if let decimal = decimal { + self.init(decimal: decimal) + } else { + return nil + } + } +} + +public extension Decimal { + var doubleValue: Double { + return NSDecimalNumber(decimal: self).doubleValue + } +} diff --git a/Utilities/Utilities/_Extensions/NSObject+Association.swift b/Utilities/Utilities/_Extensions/NSObject+Association.swift new file mode 100644 index 000000000..819883fa5 --- /dev/null +++ b/Utilities/Utilities/_Extensions/NSObject+Association.swift @@ -0,0 +1,32 @@ +// +// NSObject+Association.swift +// Utilities +// +// Created by Qiang Huang on 4/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import ObjectiveC + +public func associatedObject(base: AnyObject, key: UnsafeRawPointer) -> T? { + return objc_getAssociatedObject(base, key) as? T +} + +public func retainObject(base: AnyObject, key: UnsafeRawPointer, value: T?) { + objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN) +} + +public func associateObject(base: AnyObject, key: UnsafeRawPointer, value: T?) { + objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_ASSIGN) +} + +public func associatedObject(base: AnyObject, key: UnsafeRawPointer, initialiser: () -> T) -> T { + if let associated = objc_getAssociatedObject(base, key) as? T { + return associated + } + + let associated = initialiser() + objc_setAssociatedObject(base, key, associated, .OBJC_ASSOCIATION_RETAIN) + return associated +} + diff --git a/Utilities/Utilities/_Extensions/NSObject+Class.h b/Utilities/Utilities/_Extensions/NSObject+Class.h new file mode 100644 index 000000000..b2f8e6efe --- /dev/null +++ b/Utilities/Utilities/_Extensions/NSObject+Class.h @@ -0,0 +1,20 @@ +// +// NSObject+Class.h +// Utilities +// +// Created by Qiang Huang on 10/10/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSObject (Class) + +- (NSArray *)classNames; +- (NSString *)className; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Utilities/Utilities/_Extensions/NSObject+Class.m b/Utilities/Utilities/_Extensions/NSObject+Class.m new file mode 100644 index 000000000..545691e1f --- /dev/null +++ b/Utilities/Utilities/_Extensions/NSObject+Class.m @@ -0,0 +1,28 @@ +// +// NSObject+Class.m +// Utilities +// +// Created by Qiang Huang on 10/10/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import "NSObject+Class.h" + +@implementation NSObject (Class) + +- (NSArray *)classNames +{ + NSMutableArray *classNames = [NSMutableArray array]; + Class class = self.class; + while (class != nil) { + [classNames addObject:NSStringFromClass(class)]; + class = [class superclass]; + } + return classNames; +} + +- (NSString *)className { + return [[self classNames] firstObject]; +} + +@end diff --git a/Utilities/Utilities/_Extensions/NSObject+Observing.swift b/Utilities/Utilities/_Extensions/NSObject+Observing.swift new file mode 100644 index 000000000..d0cbff638 --- /dev/null +++ b/Utilities/Utilities/_Extensions/NSObject+Observing.swift @@ -0,0 +1,111 @@ +// +// NSObject+Observing.swift +// Utilities +// +// Created by Qiang Huang on 8/7/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +import KVOController + +public typealias KVONotificationBlock = (_ observer: Any?, _ obj: Any, _ change: [String: Any], _ animated: Bool) -> Void + +extension NSObject { + public func changeObservation(from: NSObjectProtocol?, to: NSObjectProtocol?, keyPath: String, initial: KVONotificationBlock?, change: KVONotificationBlock?) { + if from !== to { + kvoController.unobserve(from, keyPath: keyPath) + if let to = to { + initial?(self, NSNull(), [:], false) + if let change = change { + kvoController.observe(to, keyPath: keyPath, options: [.new, .old]) { observer, keyPath, changes in + if let old = changes["old"], let new = changes["new"] { + if let oldValue = old as? String, let newValue = new as? String { + if oldValue != newValue { + change(observer, keyPath, changes, true) + } + } else if let oldValue = old as? Date, let newValue = new as? Date { + if oldValue != newValue { + change(observer, keyPath, changes, true) + } + } else if let oldValue = old as? NSDecimalNumber, let newValue = new as? NSDecimalNumber { + if oldValue.decimalValue != newValue.decimalValue { + change(observer, keyPath, changes, true) + } + } else if let oldValue = old as? NSNumber, let newValue = new as? NSNumber { + if oldValue.doubleValue != newValue.doubleValue { + change(observer, keyPath, changes, true) + } + } else if let oldValue = old as? Bool, let newValue = new as? Bool { + if oldValue != newValue { + change(observer, keyPath, changes, true) + } + } else if let oldValue = old as? Int, let newValue = new as? Int { + if oldValue != newValue { + change(observer, keyPath, changes, true) + } + } else if let oldValue = old as? Float, let newValue = new as? Float { + if oldValue != newValue { + change(observer, keyPath, changes, true) + } + } else if let oldValue = old as? Double, let newValue = new as? Double { + if oldValue != newValue { + change(observer, keyPath, changes, true) + } + } else if let oldValue = old as? TimeInterval, let newValue = new as? TimeInterval { + if oldValue != newValue { + change(observer, keyPath, changes, true) + } + } else if let oldObject = old as? NSObject, let newObject = new as? NSObject { + if oldObject !== newObject { + change(observer, keyPath, changes, true) + } + } else { + change(observer, keyPath, changes, true) + } + } else { + change(observer, keyPath, changes, true) + } + } + } + } else { + initial?(self, NSNull(), [:], false) + } + } + } + + public func changeObservation(from: NSObjectProtocol?, to: NSObjectProtocol?, keyPath: String, block: @escaping KVONotificationBlock) { + changeObservation(from: from, to: to, keyPath: keyPath, initial: block, change: block) + } + + public func run(_ function: () -> Void, notify: String) { + willChangeValue(forKey: notify) + function() + didChangeValue(forKey: notify) + } + + public func changeDictionaryObservation(from: [String: NSObjectProtocol]?, to: [String: NSObjectProtocol]?, blocks: [String: KVONotificationBlock?]) { + var from = from ?? [String: NSObjectProtocol]() + let to = to ?? [String: NSObjectProtocol]() + for (key, newValue) in to { + let old = from[key] + if old !== newValue { + for (keyPath, block) in blocks { + if let block = block { + changeObservation(from: old, to: newValue, keyPath: keyPath, block: block) + } + } + } + from.removeValue(forKey: key) + } + for (_, old) in from { + for (keyPath, _) in blocks { + for (keyPath, block) in blocks { + if let block = block { + changeObservation(from: old, to: nil, keyPath: keyPath, block: block) + } + } + } + } + } +} diff --git a/Utilities/Utilities/_Extensions/RandomAccessCollection+Sort.swift b/Utilities/Utilities/_Extensions/RandomAccessCollection+Sort.swift new file mode 100644 index 000000000..d7cffdae8 --- /dev/null +++ b/Utilities/Utilities/_Extensions/RandomAccessCollection+Sort.swift @@ -0,0 +1,49 @@ +// +// RandomAccessCollection+Sort.swift +// Utilities +// +// Created by Qiang Huang on 11/11/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public extension RandomAccessCollection { // the predicate version is not required to conform to Comparable + func insertionIndex(for predicate: (Element) -> ComparisonResult) -> Index? { + var slice: SubSequence = self[...] + + while !slice.isEmpty { + let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2) + switch predicate(slice[middle]) { + case .orderedAscending: + slice = slice[index(after: middle)...] + + case .orderedDescending: + slice = slice[.. ComparisonResult) -> Index? { + var slice: SubSequence = self[...] + + while !slice.isEmpty { + let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2) + switch predicate(slice[middle]) { + case .orderedAscending: + slice = slice[index(after: middle)...] + + case .orderedDescending: + slice = slice[..: Error { + let result: Result +} + +extension Sequence { + func reduce( + _ initialResult: Result, + _ nextPartialResult: (Result, Self.Iterator.Element) throws -> Result, + until conditionPassFor: (Result, Self.Iterator.Element) -> Bool + ) rethrows -> Result { + do { + return try reduce( + initialResult, + { + if conditionPassFor($0, $1) { + throw BreakConditionError(result: $0) + } else { + return try nextPartialResult($0, $1) + } + } + ) + } catch let error as BreakConditionError { + return error.result + } + } + + func reduce( + _ initialResult: Result, + _ nextPartialResult: (Result, Self.Iterator.Element) throws -> Result, + while conditionPassFor: (Result, Self.Iterator.Element) -> Bool + ) rethrows -> Result { + do { + return try reduce( + initialResult, + { + let _nextPartialResult = try nextPartialResult($0, $1) + if conditionPassFor(_nextPartialResult, $1) { + return _nextPartialResult + } else { + throw BreakConditionError(result: $0) + } + } + ) + } catch let error as BreakConditionError { + return error.result + } + } +} diff --git a/Utilities/Utilities/_Extensions/String+Localized.swift b/Utilities/Utilities/_Extensions/String+Localized.swift new file mode 100644 index 000000000..9f1396224 --- /dev/null +++ b/Utilities/Utilities/_Extensions/String+Localized.swift @@ -0,0 +1,32 @@ +// +// String+Localized.swift +// Utilities +// +// Created by Qiang Huang on 4/30/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public extension String { + var localized: String { + return localized(in: nil) + } + + func localized(in bundle: Bundle?) -> String { + if let bundle = bundle { + return NSLocalizedString(self, tableName: nil, bundle: bundle, value: self, comment: "") + } else { + let bundles = Bundle.particles + var localized: String = "" + for bundle in bundles { + localized = NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "") + if localized != "" { + break + } + } + LocalizerBuffer.shared?.localize(self, to: localized) + return localized != "" ? localized : self + } + } +} diff --git a/Utilities/Utilities/_Extensions/String+Security.swift b/Utilities/Utilities/_Extensions/String+Security.swift new file mode 100644 index 000000000..a78138a41 --- /dev/null +++ b/Utilities/Utilities/_Extensions/String+Security.swift @@ -0,0 +1,77 @@ +// +// String+Security.swift +// Utilities +// +// Created by Qiang Huang on 12/4/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation +import Security + +extension String { + public var djb2hash: Int { + let unicodeScalars = self.unicodeScalars.map { $0.value } + return unicodeScalars.reduce(5381) { + ($0 << 5) &+ $0 &+ Int($1) + } + } + + public var sdbmhash: Int { + let unicodeScalars = self.unicodeScalars.map { $0.value } + return unicodeScalars.reduce(0) { + (Int($1) &+ ($0 << 6) &+ ($0 << 16)).addingReportingOverflow(-$0).partialValue + } + } + + public var md5: Data { + let messageData = data(using: .utf8)! + var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH)) + + _ = digestData.withUnsafeMutableBytes { digestBytes in + messageData.withUnsafeBytes { messageBytes in + CC_MD5(messageBytes, CC_LONG(messageData.count), digestBytes) + } + } + + return digestData + } + + public func sha256Hex() -> String { + if let stringData = data(using: String.Encoding.utf8) { + return hexStringFromData(input: digest(input: stringData as NSData)) + } + return "" + } + + private func digest(input: NSData) -> NSData { + let digestLength = Int(CC_SHA256_DIGEST_LENGTH) + var hash = [UInt8](repeating: 0, count: digestLength) + CC_SHA256(input.bytes, UInt32(input.length), &hash) + return NSData(bytes: hash, length: digestLength) + } + + private func hexStringFromData(input: NSData) -> String { + var bytes = [UInt8](repeating: 0, count: input.length) + input.getBytes(&bytes, length: input.length) + + var hexString = "" + for byte in bytes { + hexString += String(format: "%02x", UInt8(byte)) + } + + return hexString + } + + public func encodeBase64() -> String? { + let utf8str = data(using: .utf8) + return utf8str?.base64EncodedString() + } + + public func decodeBase64() -> String? { + if let utf8str = Data(base64Encoded: self) { + return String(data: utf8str, encoding: .utf8) + } + return nil + } +} diff --git a/Utilities/Utilities/_Extensions/String+Utils.swift b/Utilities/Utilities/_Extensions/String+Utils.swift new file mode 100644 index 000000000..c022e75a4 --- /dev/null +++ b/Utilities/Utilities/_Extensions/String+Utils.swift @@ -0,0 +1,473 @@ +// +// String+Utils.swift +// Utilities +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +extension String { + public var xmlEscaped: String { + return replacingOccurrences(of: "&", with: "&") + .replacingOccurrences(of: "\"", with: """) + .replacingOccurrences(of: "'", with: "'") + .replacingOccurrences(of: ">", with: ">") + .replacingOccurrences(of: "<", with: "<") + } + + public func encodeUrl() -> String? { + return addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) + } + + public func decodeUrl() -> String? { + return removingPercentEncoding + } +} + +extension Optional where Wrapped: Collection { + public var isNilOrEmpty: Bool { + self?.isEmpty ?? true + } +} + +extension String { + public var hasText: Bool { + return self != "" + } + + public var isNotEmpty: Bool { + !isEmpty + } + + public func trim() -> String? { + let trimmed = trimmingCharacters(in: .whitespacesAndNewlines) + return trimmed == "" ? nil : trimmed + } + + public func begins(with string: String) -> Bool { + return hasPrefix(string) + } + + public func ends(with string: String) -> Bool { + return hasSuffix(string) + } + + public static func same(_ text1: String?, as text2: String?) -> Bool { + if let text1 = text1 { + return text1 == text2 + } else { + return text2 == nil + } + } + + public static func hex(of number: Int) -> String? { + return String(format: "0x%X", number) + } + + public static func trim(_ lines: [String]) -> [String] { + return lines.filter({ (line: String) -> Bool in + line.hasText + }) + } + + public static func ascending(string1: String?, string2: String?) -> Bool { + if string1 != nil { + if string2 != nil { + return string1! < string2! + } else { + return false + } + } else { + return true + } + } +} + +extension String { + public var lastPathComponent: String { + return (self as NSString).lastPathComponent + } + + public var pathExtension: String { + return (self as NSString).pathExtension + } + + public var stringByDeletingLastPathComponent: String { + return (self as NSString).deletingLastPathComponent + } + + public var stringByDeletingPathExtension: String { + return (self as NSString).deletingPathExtension + } + + public var pathComponents: [String] { + return (self as NSString).pathComponents + } + + public func stringByAppendingPathComponent(path: String) -> String { + let nsSt = self as NSString + return nsSt.appendingPathComponent(path) + } + + public func stringByAppendingPathExtension(ext: String) -> String? { + let nsSt = self as NSString + return nsSt.appendingPathExtension(ext) + } +} + +extension String { + public func asInts() -> [Int] { + let elements = components(separatedBy: ",") + return elements.compactMap({ (item) -> Int? in + Int(item) + }) + } + + public var digits: String { + filter { + ("0" ... "9").contains($0) + } + } + + private func removingPrefixZeros() -> String { + let decimalSeparator = Locale.current.decimalSeparator ?? "." + // Removing preceding 0s, i.e. 050 -> 50 + var input = self + while input.hasPrefix("0") && !input.hasPrefix("0\(decimalSeparator)") && input.count > 1 { + input.removeFirst() + } + return input + } + + private func removingNegation() -> String { + var input = self + let isNegative = input.first == "-" + if isNegative { + input.removeFirst() + } + return input + } + + private func isValidNumberForParsing() -> Bool { + NumberFormatter().number(from: self) != nil + } + + /// returns the string as a whole number, truncated (round towards 0) + /// - Returns: the string as a whole number, truncated + public func truncateToWholeNumber() -> String? { + guard self.isValidNumberForParsing() || self == "-" else { return nil } + + let decimalSeparator = Locale.current.decimalSeparator ?? "." + + var input = self + + let isNegative = input.first == "-" + input = input.removingNegation() + + input = input.removingPrefixZeros() + + if let range = input.range(of: decimalSeparator) { + input = String(input[startIndex.. String? { + let decimalSeparator = Locale.current.decimalSeparator ?? "." + guard self.isValidNumberForParsing() || self == decimalSeparator || self == "-" || self == "-\(decimalSeparator)" else { return nil } + + var input = self + + let isNegative = input.first == "-" + input = input.removingNegation() + + // If the input string starts with a decimal separator, return "0." + if input.hasPrefix(decimalSeparator) { + input = "0" + input + } + + input = input.removingPrefixZeros() + + return isNegative ? "-" + input : input + } +} + +public extension StringProtocol { + func index(of string: S, options: String.CompareOptions = []) -> Index? { + range(of: string, options: options)?.lowerBound + } + + func endIndex(of string: S, options: String.CompareOptions = []) -> Index? { + range(of: string, options: options)?.upperBound + } + + func indices(of string: S, options: String.CompareOptions = []) -> [Index] { + var indices: [Index] = [] + var startIndex = self.startIndex + while startIndex < endIndex, + let range = self[startIndex...] + .range(of: string, options: options) { + indices.append(range.lowerBound) + startIndex = range.lowerBound < range.upperBound ? range.upperBound : + index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex + } + return indices + } + + func ranges(of string: S, options: String.CompareOptions = []) -> [Range] { + var result: [Range] = [] + var startIndex = self.startIndex + while startIndex < endIndex, + let range = self[startIndex...] + .range(of: string, options: options) { + result.append(range) + startIndex = range.lowerBound < range.upperBound ? range.upperBound : + index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex + } + return result + } +} + +public extension String { + var length: Int { + return count + } + + subscript(i: Int) -> String { + return self[i ..< i + 1] + } + + func substring(fromIndex: Int) -> String { + return self[min(fromIndex, length) ..< length] + } + + func substring(toIndex: Int) -> String { + return self[0 ..< max(0, toIndex)] + } + + subscript(r: Range) -> String { + let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)), + upper: min(length, max(0, r.upperBound)))) + let start = index(startIndex, offsetBy: range.lowerBound) + let end = index(start, offsetBy: range.upperBound - range.lowerBound) + return String(self[start ..< end]) + } + + var isNumeric: Bool { + guard count > 0 else { return false } + let nums: Set = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] + return Set(self).isSubset(of: nums) + } + + func slice(seperators: [String]) -> [String?]? { + if let seperator = seperators.first { + var remainingSeperators = seperators + remainingSeperators.removeFirst() + if let range = range(of: seperator) { + var slices = [String?]() + let beforeSeperator = prefix(upTo: range.lowerBound) + let before = String(beforeSeperator) + slices.append(before) + + let afterSeperator = suffix(from: range.upperBound) + let after = String(afterSeperator).trim() + if let remaining = after?.slice(seperators: remainingSeperators) { + slices.append(contentsOf: remaining) + } + return slices + } + } + return [self] + } + + func slice(any: [String]) -> [String]? { + return slice(any: any, prefix: nil) + } + + private func slice(any: [String], prefix: String?) -> [String]? { + if let seperator = any.first { + var slices = [String]() + var rest = any + rest.removeFirst() + + let components = self.components(separatedBy: seperator) + + for index in 0 ..< components.count { + let component = components[index] + if index == 0 { + if let subSlices = component.slice(any: rest, prefix: prefix) { + slices.append(contentsOf: subSlices) + } + } else { + if let subSlices = component.slice(any: rest, prefix: seperator) { + slices.append(contentsOf: subSlices) + } + } + } + + return slices + } else { + if let prefix = prefix { + return ["\(prefix)\(self)"] + } else { + return nil + } + } + } +} + +public extension String { + init?(value: Double?) { + if let value = value { + self = String(value) + } else { + return nil + } + } + + func substring(with: NSRange) -> String { + let startIndex = with.lowerBound + let endIndex = with.upperBound + let range = startIndex ..< endIndex + return String(self[range]) + } + + func detectUrl() -> [NSRange]? { + if let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) { + let matches = detector.matches(in: self, options: [], range: NSRange(location: 0, length: utf16.count)) + var ranges = [NSRange]() + for match in matches { + ranges.append(match.range) + } + return ranges.count > 0 ? ranges : nil + } + return nil + } + + func detectHttpUrl() -> [NSRange]? { + if let ranges = detectUrl() { + var httpRanges = [NSRange]() + for range in ranges { + let url = substring(with: range) + if url.lowercased().starts(with: "http:") || url.lowercased().starts(with: "https:") { + httpRanges.append(range) + } + } + return httpRanges.count > 0 ? httpRanges : nil + } + return nil + } +} + +class DataDetector { + private class func _find(all type: NSTextCheckingResult.CheckingType, + in string: String, iterationClosure: (String) -> Bool) { + guard let detector = try? NSDataDetector(types: type.rawValue) else { return } + let range = NSRange(string.startIndex ..< string.endIndex, in: string) + let matches = detector.matches(in: string, options: [], range: range) + loop: for match in matches { + for i in 0 ..< match.numberOfRanges { + let nsrange = match.range(at: i) + let startIndex = string.index(string.startIndex, offsetBy: nsrange.lowerBound) + let endIndex = string.index(string.startIndex, offsetBy: nsrange.upperBound) + let range = startIndex ..< endIndex + guard iterationClosure(String(string[range])) else { break loop } + } + } + } + + class func find(all type: NSTextCheckingResult.CheckingType, in string: String) -> [String] { + var results = [String]() + _find(all: type, in: string) { + results.append($0) + return true + } + return results + } + + class func first(type: NSTextCheckingResult.CheckingType, in string: String) -> String? { + var result: String? + _find(all: type, in: string) { + result = $0 + return false + } + return result + } +} + +// MARK: String extension + +extension String { + var detectedLinks: [String] { DataDetector.find(all: .link, in: self) } + var detectedFirstLink: String? { DataDetector.first(type: .link, in: self) } + var detectedURLs: [URL] { detectedLinks.compactMap { URL(string: $0) } } + var detectedFirstURL: URL? { + guard let urlString = detectedFirstLink else { return nil } + return URL(string: urlString) + } +} + +extension String { + public func color() -> String? { + let hash = abs(sdbmhash) + let colorNum = hash % (256 * 256 * 256) + let red = ((colorNum >> 16) * 4 / 5) + let green = (((colorNum & 0x00FF00) >> 8) * 4 / 5) + let blue = (colorNum & 0x0000FF) * 4 / 5 + let redHex = String(format: "%X", red) + let greenHex = String(format: "%X", green) + let blueHex = String(format: "%X", blue) + return "#\(redHex)\(greenHex)\(blueHex)" + } +} + +extension String { + public func pad(to length: Int, with: String) -> String { + if self.length < length { + return "\(self)\(with)".pad(to: length, with: with) + } else { + return self + } + } + + public func prefix(_ with: String, length: Int) -> String { + if self.length < length { + return "\(with)\(self)".prefix(with, length: length) + } else { + return self + } + } + + public func removeTrailing(_ removing: String) -> String { + if self.ends(with: removing) { + return self.substring(toIndex: self.length - removing.length).removeTrailing(removing) + } else { + return self + } + } +} + +extension String { + public var jsonDictionary: [String: Any]? { + guard let data = data(using: .utf8) else { + return nil + } + return try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + } +} + +extension [String: Any] { + public var jsonString: String? { + guard let json = try? JSONSerialization.data(withJSONObject: self) else { + return nil + } + + return String(data: json, encoding: .utf8) + } +} diff --git a/Utilities/Utilities/_Extensions/TimeIntervale+String.swift b/Utilities/Utilities/_Extensions/TimeIntervale+String.swift new file mode 100644 index 000000000..19bf5f2da --- /dev/null +++ b/Utilities/Utilities/_Extensions/TimeIntervale+String.swift @@ -0,0 +1,31 @@ +// +// TimeIntervale+String.swift +// Utilities +// +// Created by Qiang Huang on 5/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +extension TimeInterval { + var text: String? { + let ms = Int(truncatingRemainder(dividingBy: 1) * 1000) + + return String(format: "\(shortText).%0.3d", ms) + } + + var shortText: String { + let time = NSInteger(self) + + let seconds = time % 60 + let minutes = (time / 60) % 60 + let hours = (time / 3600) + + if hours > 0 { + return String(format: "%0.2d:%0.2d:%0.2d", hours, minutes, seconds) + } else { + return String(format: "%0.2d:%0.2d", minutes, seconds) + } + } +} diff --git a/Utilities/Utilities/_Extensions/UIColor+Hex.swift b/Utilities/Utilities/_Extensions/UIColor+Hex.swift new file mode 100644 index 000000000..3404c9c17 --- /dev/null +++ b/Utilities/Utilities/_Extensions/UIColor+Hex.swift @@ -0,0 +1,88 @@ +// +// UIColor+Hex.swift +// Utilities +// +// Created by Rui Huang on 8/9/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +extension UIColor { + public convenience init?(hex: String?) { + let input: String! = (hex ?? "") + .replacingOccurrences(of: "#", with: "") + .uppercased() + var alpha: CGFloat = 1.0 + var red: CGFloat = 0 + var blue: CGFloat = 0 + var green: CGFloat = 0 + switch (input.count) { + case 3 /* #RGB */: + red = Self.colorComponent(from: input, start: 0, length: 1) + green = Self.colorComponent(from: input, start: 1, length: 1) + blue = Self.colorComponent(from: input, start: 2, length: 1) + break + case 4 /* #ARGB */: + alpha = Self.colorComponent(from: input, start: 0, length: 1) + red = Self.colorComponent(from: input, start: 1, length: 1) + green = Self.colorComponent(from: input, start: 2, length: 1) + blue = Self.colorComponent(from: input, start: 3, length: 1) + break + case 6 /* #RRGGBB */: + red = Self.colorComponent(from: input, start: 0, length: 2) + green = Self.colorComponent(from: input, start: 2, length: 2) + blue = Self.colorComponent(from: input, start: 4, length: 2) + break + case 8 /* #RRGGBBAA */: + red = Self.colorComponent(from: input, start: 0, length: 2) + green = Self.colorComponent(from: input, start: 2, length: 2) + blue = Self.colorComponent(from: input, start: 4, length: 2) + alpha = Self.colorComponent(from: input, start: 6, length: 2) + break + default: + NSException.raise(NSExceptionName("Invalid color value"), format: "Color value \"%@\" is invalid. It should be a hex value of the form #RBG, #ARGB, #RRGGBB, or #AARRGGBB", arguments:getVaList([hex ?? ""])) + } + self.init(red: red, green: green, blue: blue, alpha: alpha) + } + + static func colorComponent(from string: String!, start: Int, length: Int) -> CGFloat { + let substring = (string as NSString) + .substring(with: NSRange(location: start, length: length)) + let fullHex = length == 2 ? substring : "\(substring)\(substring)" + var hexComponent: UInt64 = 0 + Scanner(string: fullHex) + .scanHexInt64(&hexComponent) + return CGFloat(Double(hexComponent) / 255.0) + } +} + +extension UIColor { + public static func color(string: String?) -> UIColor? { + if let string = string { + let hash = abs(string.sdbmhash) + let colorNum = hash % (256 * 256 * 256) + let red = colorNum >> 16 + let green = (colorNum & 0x00FF00) >> 8 + let blue = (colorNum & 0x0000FF) + let darkeningFactor = CGFloat(1.25 * 255.0) + return UIColor(red: CGFloat(red) / darkeningFactor, green: CGFloat(green) / darkeningFactor, blue: CGFloat(blue) / darkeningFactor, alpha: 1.0) + } + return nil + } + + public static func blend(color1: UIColor, intensity1: CGFloat = 0.5, color2: UIColor, intensity2: CGFloat = 0.5) -> UIColor { + let total = intensity1 + intensity2 + let l1 = intensity1/total + let l2 = intensity2/total + guard l1 > 0 else { return color2} + guard l2 > 0 else { return color1} + var (r1, g1, b1, a1): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0) + var (r2, g2, b2, a2): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0) + + color1.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) + color2.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) + + return UIColor(red: l1*r1 + l2*r2, green: l1*g1 + l2*g2, blue: l1*b1 + l2*b2, alpha: l1*a1 + l2*a2) + } +} diff --git a/Utilities/Utilities/_Extensions/UINib+Safeload.swift b/Utilities/Utilities/_Extensions/UINib+Safeload.swift new file mode 100644 index 000000000..2296fac35 --- /dev/null +++ b/Utilities/Utilities/_Extensions/UINib+Safeload.swift @@ -0,0 +1,30 @@ +// +// UINib+Safeload.swift +// Utilities +// +// Created by Qiang Huang on 4/30/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +extension UINib { + @objc public static func safeLoad(xib: String, bundles: [Bundle]) -> UINib? { + var nib: UINib? + for bundle in bundles { + nib = safeLoad(xib: xib, bundle: bundle) + if nib != nil { + break + } + } + return nib + } + + @objc public static func safeLoad(xib: String, bundle: Bundle) -> UINib? { + let file = bundle.path(forResource: xib, ofType: "nib") + if File.exists(file) { + return UINib(nibName: xib, bundle: bundle) + } + return nil + } +} diff --git a/Utilities/Utilities/_Extensions/URL+Params.swift b/Utilities/Utilities/_Extensions/URL+Params.swift new file mode 100644 index 000000000..7f1531daa --- /dev/null +++ b/Utilities/Utilities/_Extensions/URL+Params.swift @@ -0,0 +1,24 @@ +// +// URL+Params.swift +// Utilities +// +// Created by Qiang Huang on 10/18/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +extension URL { + public var params: [String: String]? { + guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true), let queryItems = components.queryItems else { + return nil + } + + var parameters = [String: String]() + for item in queryItems { + parameters[item.name] = item.value + } + + return parameters + } +} diff --git a/Utilities/Utilities/_Extensions/URLComponents+Params.swift b/Utilities/Utilities/_Extensions/URLComponents+Params.swift new file mode 100644 index 000000000..bf49ee6bc --- /dev/null +++ b/Utilities/Utilities/_Extensions/URLComponents+Params.swift @@ -0,0 +1,21 @@ +// +// URLComponents+Params.swift +// Utilities +// +// Created by Qiang Huang on 5/11/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public extension URLComponents { + var params: [String: String] { + var params = [String: String]() + if let queryItems = queryItems { + for item in queryItems { + params[item.name] = item.value + } + } + return params + } +} diff --git a/Utilities/Utilities/_Files/Directory.swift b/Utilities/Utilities/_Files/Directory.swift new file mode 100644 index 000000000..dc3ddedd4 --- /dev/null +++ b/Utilities/Utilities/_Files/Directory.swift @@ -0,0 +1,188 @@ +// The converted code is limited to 2 KB. +// Upgrade your plan to remove this limitation. +// +// Converted to Swift 4 by Swiftify v4.2.6846 - https://objectivec2swift.com/ +// +// Directory.swift +// Utilities +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// +import Foundation + +@objc public class Directory: NSObject { + @objc public static var user: String = { + NSHomeDirectory() + }() + + @objc public static var document: String? = { + let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) + return paths.first + }() + + @objc public static var library: String? = { + let paths = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true) + return paths.first + }() + + @objc public static var cache: String? = { + let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true) + return paths.first + }() + + @objc public static var bundle: String? = { + Bundle.main.bundlePath + }() + + @objc public static func download() -> String? { + return folder(named: "download", in: cache) + } + + @objc public static func temp() -> String? { + let folder = NSTemporaryDirectory() + if folder != "" { + return folder + } else { + return document + } + } + + @objc public static func folder(named path: String?, in folder: String?) -> String? { + if let path = path, let folder = folder as NSString? { + return folder.appendingPathComponent(path) + } + return nil + } + + @objc public static func userFolder(_ path: String?) -> String? { + return folder(named: path, in: user) + } + + @objc public static func documentFolder(_ path: String?) -> String? { + return folder(named: path, in: document) + } + + @objc public static func libraryFolder(_ path: String?) -> String? { + return folder(named: path, in: library) + } + + @objc public static func downloadFolder(_ path: String?) -> String? { + return folder(named: path, in: download()) + } + + @objc public static func cacheFolder(_ path: String?) -> String? { + return folder(named: path, in: cache) + } + + @objc public static func bundleFolder(_ path: String?) -> String? { + return folder(named: path, in: bundle) + } + + @objc public static func pictures() -> String? { + return folder(named: "pictures", in: user) + } + + @objc public static func isFolder(_ path: String?) -> Bool { + if let path = path { + var isDir: ObjCBool = false + FileManager.default.fileExists(atPath: path, isDirectory: &isDir) + return isDir.boolValue + } + return false + } + + @objc public static func exists(_ path: String?) -> Bool { + var isDir: ObjCBool = false + let exist: Bool = FileManager.default.fileExists(atPath: path ?? "", isDirectory: &isDir) + return exist && isDir.boolValue + } + + @objc public static func parent(of path: String?) -> String? { + if let path = path as NSString? { + return path.deletingLastPathComponent + } + return nil + } + + @objc public static func ensure(_ path: String?) -> Bool { + if let path = path { + if path == "/" || path == "" { + return true + } else if exists(path) { + return true + } else { + let parent = self.parent(of: path) + if ensure(parent) { + try? FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) + return true + } else { + return false + } + } + } + return true + } + + @objc public static func delete(_ path: String?) { + if let path = path { + try? FileManager.default.removeItem(atPath: path) + } + } + + @objc public static func all(in parent: String?, extension fileExtension: String? = nil) -> [String]? { + if let path = parent { + if let array = try? FileManager.default.contentsOfDirectory(atPath: path) { + let lowercaseExtension = fileExtension?.lowercased() + var items: [String] = [] + + for child in array { + if let fullPath = self.folder(named: child, in: path) { + if lowercaseExtension == nil || (fullPath as NSString).pathExtension.lowercased() == lowercaseExtension { + items.append(fullPath) + } + } + } + return items + } + } + return nil + } + + @objc public static func folders(in parent: String?) -> [String]? { + if let path = parent { + if let array = try? FileManager.default.contentsOfDirectory(atPath: path) { + var folders: [String] = [] + for child: String? in array { + if let fullPath = self.folder(named: child, in: path) { + if isFolder(fullPath) { + folders.append(fullPath) + } + } + } + return folders + } + } + return nil + } + + @objc public static func files(in parent: String?, fileExtension: String?) -> [String]? { + if let path = parent { + if let array = try? FileManager.default.contentsOfDirectory(atPath: path) { + let lowercaseExtension = fileExtension?.lowercased() + var files: [String] = [] + for child: String in array { + if let fullPath = self.folder(named: child, in: path) { + if !isFolder(fullPath) { + if lowercaseExtension == nil || (fullPath as NSString).pathExtension.lowercased() == lowercaseExtension { + files.append(fullPath) + } + } + } + } + return files + } + } + return nil + } +} diff --git a/Utilities/Utilities/_Files/File.swift b/Utilities/Utilities/_Files/File.swift new file mode 100644 index 000000000..37b30f183 --- /dev/null +++ b/Utilities/Utilities/_Files/File.swift @@ -0,0 +1,59 @@ +// Converted to Swift 4 by Swiftify v4.2.6846 - https://objectivec2swift.com/ +// +// File.swift +// Utilities +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// +import Foundation + +public class File { + public class func exists(_ path: String?) -> Bool { + if let path = path { + var isDir: ObjCBool = false + return FileManager.default.fileExists(atPath: path, isDirectory: &isDir) + } + return false + } + + public class func delete(_ path: String?) { + if let path = path { + try? FileManager.default.removeItem(atPath: path) + } + } + + public class func copy(_ path: String?, to toPath: String?) -> Bool { + return copy(path, to: toPath, forced: false) + } + + public class func copy(_ path: String?, to toPath: String?, forced: Bool) -> Bool { + if let path = path, let toPath = toPath { + if forced { + delete(toPath) + } + if exists(path) && !exists(toPath) { + try? FileManager.default.copyItem(atPath: path, toPath: toPath) + return true + } + } + return false + } + + public class func move(_ path: String?, to toPath: String?) -> Bool { + return move(path, to: toPath, forced: true) + } + + public class func move(_ path: String?, to toPath: String?, forced: Bool) -> Bool { + if let path = path, let toPath = toPath { + if forced { + delete(toPath) + } + if exists(path) && !exists(toPath) { + try? FileManager.default.copyItem(atPath: path, toPath: toPath) + return true + } + } + return false + } +} diff --git a/Utilities/Utilities/_Files/FolderService.swift b/Utilities/Utilities/_Files/FolderService.swift new file mode 100644 index 000000000..9c3256344 --- /dev/null +++ b/Utilities/Utilities/_Files/FolderService.swift @@ -0,0 +1,50 @@ +// +// FolderService.swift +// Utilities +// +// Created by Qiang Huang on 12/14/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public protocol FolderProviderProtocol { + func documents() -> String? + func temp() -> String? +} + +public class FolderService { + public static var shared: FolderProviderProtocol? +} + +public class RealFolderProvider: FolderProviderProtocol { + public var documentFolder: String? { + didSet { + _ = Directory.ensure(documentFolder) + } + } + + public var tempFolder: String? { + didSet { + _ = Directory.ensure(tempFolder) + } + } + + public static func mock() -> RealFolderProvider { + let provider = RealFolderProvider() + provider.documentFolder = ProcessInfo.processInfo.environment["TEST_DOCUMENTS_DIR"] + provider.tempFolder = ProcessInfo.processInfo.environment["TEST_TEMP_DIR"] + return provider + } + + public init() { + } + + public func documents() -> String? { + return documentFolder ?? Directory.document + } + + public func temp() -> String? { + return tempFolder ?? Directory.cache + } +} diff --git a/Utilities/Utilities/_Javascript/CosmoJavascript.swift b/Utilities/Utilities/_Javascript/CosmoJavascript.swift new file mode 100644 index 000000000..97e607659 --- /dev/null +++ b/Utilities/Utilities/_Javascript/CosmoJavascript.swift @@ -0,0 +1,123 @@ +// +// CosmoJavascript.swift +// dydxModels +// +// Created by John Huang on 11/30/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import JavaScriptCore + +public final class CosmoJavascript: NSObject, SingletonProtocol { + public static var shared: CosmoJavascript = CosmoJavascript() + + private var v4ClientInitialized: Bool = false + public var v4ClientRunner: JavascriptRunner? = { + JavascriptRunner.runner(file: "v4-native-client.js") + }() + + public func loadV4Client(completion: @escaping JavascriptCompletion) { + if v4ClientInitialized { + completion(nil) + } else { + if let runner = v4ClientRunner { + runner.load { [weak self] successful in + self?.v4ClientInitialized = successful + completion(true) + } + } else { + completion(nil) + } + } + } + + public func deriveCosmosKey( + signature: String, + completion: @escaping JavascriptCompletion) { + callNativeClient(functionName: "deriveMnemomicFromEthereumSignature", params: [signature], completion: completion) + } + + public func getAccountBalance(completion: @escaping JavascriptCompletion) { + callNativeClient(functionName: "getAccountBalance", params: [], completion: completion) + } + + public func withdrawToIBC(subaccount: Int, + amount: String, + payload: String, + completion: @escaping JavascriptCompletion) { + if let data = payload.data(using: .utf8) { + let base64String = data.base64EncodedString() + callNativeClient(functionName: "withdrawToIBC", params: [subaccount, amount, base64String], completion: completion) + } else { + assertionFailure("Invalid data") + } + } + + public func depositToSubaccount(subaccount: Int, + amount: String, + completion: @escaping JavascriptCompletion) { + let json = "{\"subaccountNumber\": \(subaccount),\"amount\": \"\(amount)\"}" + callNativeClient(functionName: "deposit", params: [json], completion: completion) + } + + private func callNativeClient(functionName: String, params: [Any?], completion: @escaping JavascriptCompletion) { + loadV4Client { [weak self] _ in + DispatchQueue.main.async { + if let runner = self?.v4ClientRunner { + runner.invoke(className: nil, function: functionName, params: params) { result in + DispatchQueue.main.async { + completion(result) + } + } + } else { + completion(nil) + } + } + } + } + + public func getWithdrawalCapacityByDenom(denom: String, completion: @escaping JavascriptCompletion) { + callNativeClient(functionName: "getWithdrawalCapacityByDenom", params: [denom]) { result in + completion(result) + } + } + + public func getWithdrawalAndTransferGatingStatus(completion: @escaping JavascriptCompletion) { + callNativeClient(functionName: "getWithdrawalAndTransferGatingStatus", params: []) { result in + completion(result) + } + } + + public func test(completion: @escaping JavascriptCompletion) { + callNativeClient(functionName: "connectClient", params: ["dydxprotocol-staging"]) { result in + self.callNativeClient(functionName: "getPerpetualMarkets", params: []) { result in + completion(result) + } + } + } + + public func connectNetwork(paramsInJson: String, completion: @escaping JavascriptCompletion) { + callNativeClient(functionName: "connectNetwork", params: [paramsInJson]) { result in + completion(result) + } + } + + public func connectWallet(mnemonic: String, completion: @escaping JavascriptCompletion) { + callNativeClient(functionName: "connectWallet", params: [mnemonic]) { result in + completion(result) + } + } + + public func call(functionName: String, paramsInJson: String?, completion: @escaping JavascriptCompletion) { + callNativeClient(functionName: functionName, params: paramsInJson != nil ? [paramsInJson!] : []) { result in + completion(result) + } + } +} + +/* to test + Add this code somewhere + CosmoJavascript.shared.test { result in + Console.shared.log(result) + } + */ diff --git a/Utilities/Utilities/_Javascript/JavascriptRunner.swift b/Utilities/Utilities/_Javascript/JavascriptRunner.swift new file mode 100644 index 000000000..d0b223c9a --- /dev/null +++ b/Utilities/Utilities/_Javascript/JavascriptRunner.swift @@ -0,0 +1,116 @@ +// +// JavascriptRunner.swift +// Utilities +// +// Created by Qiang Huang on 4/21/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import JavaScriptCore +import WebKit + +public typealias JavascriptCompletion = (_ result: Any?) -> Void + +@objc public class JavascriptRunner: NSObject { + let webView = WKWebView(frame: .zero) + private var file: String? + private var initialized: Bool = false + + public static func runner(file: String) -> JavascriptRunner { + return JavascriptRunner(file: file) + } + + public init(file: String?) { + self.file = file + super.init() + } + + public func load(completed: @escaping (_ successful: Bool) -> Void) { + if initialized { + completed(true) + } else { + if let fileScript = StringLoader.load(bundles: Bundle.particles, fileName: file) { + webView.evaluateJavaScript(fileScript) { [weak self] _, _ in + let script = """ + \(fileScript) + + return Promise.resolve() + """ + self?.webView.callAsyncJavaScript(script, arguments: [:], in: nil, in: .defaultClient) { [weak self] result in + switch result { + case .success: + self?.initialized = true + completed(true) + + case .failure: + self?.initialized = false + completed(false) + } + } + } + } else { + initialized = true + completed(true) + } + } + } + + public func run(script: String, completion: JavascriptCompletion?) { + let time = Date() + webView.evaluateJavaScript(script) { result, error in + DispatchQueue.main.async { + let interval = Date().timeIntervalSince(time) + // Console.shared.log("Javascript time interval: \(interval)") + if error == nil { + completion?(result) + } else { + completion?(nil) + } + } + } + } + + private func buildFunction(function named: String, params: [Any?]?) -> String { + if let params = params { + let parser = Parser() + let paramStrings: [String] = params.map { item in + if let value = item { + if value is String { + return "'\(value)'" + } else { + return parser.asString(value) ?? "null" + } + } else { + return "undefined" + } + } + let paramString = paramStrings.joined(separator: ", ") + return "\(named)(\(paramString))" + } else { + return "\(named)()" + } + } + + public func invoke(className: String?, function named: String, params: [Any?]?, completion: JavascriptCompletion?) { + let time = Date() + let function = buildFunction(function: named, params: params) + let script = (className != nil) ? """ + return \(className!).\(function); + """ : """ + return \(function); + """ + webView.callAsyncJavaScript(script, in: nil, in: .defaultClient) { result in + DispatchQueue.main.async { + // let interval = Date().timeIntervalSince(time) + // Console.shared.log("Javascript time interval: \(interval)") + switch result { + case let .success(data): + completion?(data) + + case .failure: + completion?(nil) + } + } + } + } +} diff --git a/Utilities/Utilities/_Localization/DataLocalizer.swift b/Utilities/Utilities/_Localization/DataLocalizer.swift new file mode 100644 index 000000000..eaafb3ba8 --- /dev/null +++ b/Utilities/Utilities/_Localization/DataLocalizer.swift @@ -0,0 +1,25 @@ +// +// DataLocalizer.swift +// Utilities +// +// Created by Qiang Huang on 5/22/21. +// Copyright © 2021 dYdX. All rights reserved. +// + +import Foundation +import Combine + +public protocol DataLocalizerProtocol { + var language: String? { get } + var languagePublisher: AnyPublisher { get } + func setLanguage(language: String, completed: @escaping (_ successful: Bool)-> Void) + func localize(path: String, params: [String: String]?) -> String? +} + +public class DataLocalizer { + static public var shared: DataLocalizerProtocol? + + static public func localize(path: String, params: [String: String]? = nil) -> String { + Self.shared?.localize(path: path, params: params) ?? path.lastPathComponent.pathExtension + } +} diff --git a/Utilities/Utilities/_Localization/Localizer.swift b/Utilities/Utilities/_Localization/Localizer.swift new file mode 100644 index 000000000..284759dfe --- /dev/null +++ b/Utilities/Utilities/_Localization/Localizer.swift @@ -0,0 +1,40 @@ +// +// Localizer.swift +// Utilities +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public protocol LocalizerProtocol { + func localize(_ text: String?) -> String? +} + +public class StandardLocalizer: LocalizerProtocol { + public func localize(_ text: String?) -> String? { + if let text = text { + return NSLocalizedString(text, comment: text) + } + return nil + } +} + +public class Localizer { + private static var _shared: LocalizerProtocol? + + public static var shared: LocalizerProtocol? { + get { + if _shared == nil { + _shared = StandardLocalizer() + } + return _shared + } + set { _shared = newValue } + } + + public static func localize(_ text: String?) -> String? { + return shared?.localize(text) + } +} diff --git a/Utilities/Utilities/_Localization/LocalizerBuffer.swift b/Utilities/Utilities/_Localization/LocalizerBuffer.swift new file mode 100644 index 000000000..df5e1e367 --- /dev/null +++ b/Utilities/Utilities/_Localization/LocalizerBuffer.swift @@ -0,0 +1,63 @@ +// +// LocalizationBuffer.swift +// Utilities +// +// Created by Qiang Huang on 11/24/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation +import Combine + +public protocol LocalizerBufferProtocol { + func localize(_ text: String?, to: String?) +} + +public class LocalizerBuffer { + public static var shared: LocalizerBufferProtocol? +} + +public class DebugLocalizer: NSObject, LocalizerBufferProtocol, CombineObserving { + public var cancellableMap = [AnyKeyPath : AnyCancellable]() + + private var appState: AppState? { + didSet { + changeObservation(from: oldValue, to: appState, keyPath: #keyPath(AppState.background)) {[weak self] observer, obj, change, animated in + if self?.appState?.background ?? false { + self?.write() + } + } + } + } + + private var strings: [String: String] = [:] + public func localize(_ text: String?, to: String?) { + if let text = text?.trim() { + strings[text] = to ?? text + } + } + + override public init() { + super.init() + DispatchQueue.main.async { [weak self] in + self?.appState = AppState.shared + } + } + + private func write() { + if let localized = FolderService.shared?.documents()?.stringByAppendingPathComponent(path: "Localizable.strings") { + File.delete(localized) + let mutable = NSMutableString() + let keys = strings.keys.sorted() + for key in keys { + let value = strings[key] ?? key + mutable.append("\"\(key)\" = \"\(value)\";\n") + } + do { + try mutable.write(toFile: localized, atomically: true, encoding: String.Encoding.utf8.rawValue) + } catch { + // failed to write file – bad permissions, bad filename, missing permissions, or more likely it can't be converted to the encoding + } + } + } +} diff --git a/Utilities/Utilities/_Location/LocationProvider.swift b/Utilities/Utilities/_Location/LocationProvider.swift new file mode 100644 index 000000000..362750813 --- /dev/null +++ b/Utilities/Utilities/_Location/LocationProvider.swift @@ -0,0 +1,19 @@ +// +// LocationProvider.swift +// Utilities +// +// Created by Qiang Huang on 8/18/19. +// Copyright © 2019 Qiang Huang. All rights reserved. +// + +import CoreLocation +import Foundation + +@objc public protocol LocationProviderProtocol: NSObjectProtocol { + @objc var location: CLLocation? { get } + @objc var locationManager: CLLocationManager? { get } +} + +public class LocationProvider { + public static var shared: LocationProviderProtocol? +} diff --git a/Utilities/Utilities/_Location/RegionMonitor.swift b/Utilities/Utilities/_Location/RegionMonitor.swift new file mode 100644 index 000000000..1ce5fab3d --- /dev/null +++ b/Utilities/Utilities/_Location/RegionMonitor.swift @@ -0,0 +1,24 @@ +// +// RegionMonitor.swift +// Utilities +// +// Created by Qiang Huang on 1/19/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import CoreLocation +import Foundation + +@objc public protocol RegionMonitorProtocol: NSObjectProtocol { + @objc var current: Set? { get set } + + func monitor(lat: Double, lng: Double, callbackUrl: String?) + func clear() + + func enter(lat: Double, lng: Double) + func exit(lat: Double, lng: Double) +} + +public class RegionMonitor { + public static var shared: RegionMonitorProtocol? +} diff --git a/Utilities/Utilities/_Logging/Logging.swift b/Utilities/Utilities/_Logging/Logging.swift new file mode 100644 index 000000000..a26f656a7 --- /dev/null +++ b/Utilities/Utilities/_Logging/Logging.swift @@ -0,0 +1,14 @@ +// +// Logging.swift +// Utilities +// +// Created by Rui Huang on 13/05/2024. +// Copyright © 2024 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public protocol Logging { + func e(tag: String, message: String) + func d(tag: String, message: String) +} diff --git a/Utilities/Utilities/_Map/MapArea.swift b/Utilities/Utilities/_Map/MapArea.swift new file mode 100644 index 000000000..50c6ed07f --- /dev/null +++ b/Utilities/Utilities/_Map/MapArea.swift @@ -0,0 +1,165 @@ +// +// MapArea.swift +// Utilities +// +// Created by Qiang Huang on 7/18/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import CoreLocation +import Foundation + +public class MapArea: NSObject { + public static func check(latitude: Double?, longitude: Double?, minLatitude: inout Double?, maxLatitude: inout Double?, minLongitude: inout Double?, maxLongitude: inout Double?) { + if let lat = latitude, let lng = longitude { + if minLatitude != nil, maxLatitude != nil, minLongitude != nil, maxLongitude != nil { + minLatitude = min(lat, minLatitude!) + maxLatitude = max(lat, maxLatitude!) + minLongitude = min(lng, minLongitude!) + maxLongitude = max(lng, maxLongitude!) + } else { + minLatitude = lat + maxLatitude = lat + minLongitude = lng + maxLongitude = lng + } + } + } + + public var topLeft: MapPoint? + public var bottomRight: MapPoint? + public var center: MapPoint? { + if let lat1 = topLeft?.latitude, let lat2 = bottomRight?.latitude, let lng1 = topLeft?.longitude, let lng2 = bottomRight?.longitude { + let center = MapPoint() + center.latitude = (lat1 + lat2) / 2 + center.longitude = (lng1 + lng2) / 2 + return center + } + return nil + } + + public var latitudeDelta: Double? { + if let latitude1 = bottomRight?.latitude, let latitude2 = topLeft?.latitude { + return latitude1 - latitude2 + } + return nil + } + + public var longitudeDelta: Double? { + if let longitude1 = bottomRight?.longitude, let longitude2 = topLeft?.longitude { + return longitude1 - longitude2 + } + return nil + } + + public var topRight: MapPoint? { + if let topLeft = topLeft, let bottomRight = bottomRight { + let topRight = MapPoint() + topRight.latitude = topLeft.latitude + topRight.longitude = bottomRight.longitude + return topRight + } + return nil + } + + public var bottomLeft: MapPoint? { + if let topLeft = topLeft, let bottomRight = bottomRight { + let bottomLeft = MapPoint() + bottomLeft.latitude = bottomRight.latitude + bottomLeft.longitude = topLeft.longitude + return bottomLeft + } + return nil + } + + public var valid: Bool { + return topLeft?.latitude != nil && topLeft?.longitude != nil && bottomRight?.latitude != nil && bottomRight?.longitude != nil + } + + public init(points: [MapPoint], extend: Double? = nil) { + super.init() + + var minLatitude: Double? + var maxLatitude: Double? + var minLongitude: Double? + var maxLongitude: Double? + for point in points { + type(of: self).check(latitude: point.latitude, longitude: point.longitude, minLatitude: &minLatitude, maxLatitude: &maxLatitude, minLongitude: &minLongitude, maxLongitude: &maxLongitude) + } + if var minLatitude = minLatitude, var maxLatitude = maxLatitude, var minLongitude = minLongitude, var maxLongitude = maxLongitude { + if let extend = extend { + minLatitude -= extend + maxLatitude += extend + minLongitude -= extend + maxLongitude += extend + } + topLeft = MapPoint(latitude: minLatitude, longitude: minLongitude) + bottomRight = MapPoint(latitude: maxLatitude, longitude: maxLongitude) + } + } + + public init(topLeft: MapPoint? = nil, bottomRight: MapPoint? = nil) { + super.init() + self.topLeft = topLeft + self.bottomRight = bottomRight + } + + public func contains(point: MapPoint?) -> Bool { + if let latitude = point?.latitude, let longitude = point?.longitude, let topLeftLatitude = topLeft?.latitude, let topLeftLongitude = topLeft?.longitude, let bottomRightLatitude = bottomRight?.latitude, let bottomRightLongitude = bottomRight?.longitude { + let minLatitude = min(topLeftLatitude, bottomRightLatitude) + let maxLatitude = max(topLeftLatitude, bottomRightLatitude) + let minLongitude = min(topLeftLongitude, bottomRightLongitude) + let maxLongitude = max(topLeftLongitude, bottomRightLongitude) + return latitude >= minLatitude && latitude <= maxLatitude && longitude >= minLongitude && longitude <= maxLongitude + } + return false + } + + public func shift(to point: MapPoint?) -> MapArea? { + if let point = point, let latitude = point.latitude, let longitude = point.longitude { + if let latitudeDelta = latitudeDelta, let longitudeDelta = longitudeDelta { + return MapArea(topLeft: MapPoint(latitude: latitude - latitudeDelta / 2.0, longitude: longitude - longitudeDelta / 2.0), bottomRight: MapPoint(latitude: latitude + latitudeDelta / 2.0, longitude: longitude + longitudeDelta / 2.0)) + } else { + return nil + } + } else { + return self + } + } + + public func radius() -> Double? { + if let topLeft = topLeft, let bottomRight = bottomRight, let topLeftLatitude = topLeft.latitude, let topLeftLongitude = topLeft.longitude, let bottomRightLatitude = bottomRight.latitude, let bottomRightLongitude = bottomRight.longitude { + let location1 = CLLocation(latitude: topLeftLatitude, longitude: topLeftLongitude) + let location2 = CLLocation(latitude: bottomRightLatitude, longitude: bottomRightLongitude) + return location1.distance(from: location2) / 2.0 + } + return nil + } + + public func distance() -> Double? { + if let topLeft = topLeft, let topLeftLatitude = topLeft.latitude, let topLeftLongitude = topLeft.longitude, let bottomRight = bottomRight, let bottomRightLatitude = bottomRight.latitude, let bottomRightLongitude = bottomRight.longitude { + let point1 = CLLocation(latitude: topLeftLatitude, longitude: topLeftLongitude) + let point2 = CLLocation(latitude: bottomRightLatitude, longitude: bottomRightLongitude) + return point1.distance(from: point2) + } + return nil + } + + public func latitudeDistance() -> Double? { + if let topLeft = topLeft, let topLeftLatitude = topLeft.latitude, let topLeftLongitude = topLeft.longitude, let bottomRight = bottomRight, let bottomRightLatitude = bottomRight.latitude { + let point1 = CLLocation(latitude: topLeftLatitude, longitude: topLeftLongitude) + let point2 = CLLocation(latitude: bottomRightLatitude, longitude: topLeftLongitude) + return point1.distance(from: point2) + } + return nil + } + + public func longitudeDistance() -> Double? { + if let topLeft = topLeft, let topLeftLatitude = topLeft.latitude, let topLeftLongitude = topLeft.longitude, let bottomRight = bottomRight, let bottomRightLongitude = bottomRight.longitude { + let point1 = CLLocation(latitude: topLeftLatitude, longitude: topLeftLongitude) + let point2 = CLLocation(latitude: topLeftLatitude, longitude: bottomRightLongitude) + return point1.distance(from: point2) + } + return nil + } +} diff --git a/Utilities/Utilities/_Map/MapPoint.swift b/Utilities/Utilities/_Map/MapPoint.swift new file mode 100644 index 000000000..d00d9695d --- /dev/null +++ b/Utilities/Utilities/_Map/MapPoint.swift @@ -0,0 +1,19 @@ +// +// MapPoint.swift +// Utilities +// +// Created by Qiang Huang on 7/18/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +@objc public class MapPoint: NSObject { + public var latitude: Double? + public var longitude: Double? + + public init(latitude: Double? = nil, longitude: Double? = nil) { + self.latitude = latitude + self.longitude = longitude + } +} diff --git a/Utilities/Utilities/_Notification/LocalNotificationService.swift b/Utilities/Utilities/_Notification/LocalNotificationService.swift new file mode 100644 index 000000000..fa4e5ccdf --- /dev/null +++ b/Utilities/Utilities/_Notification/LocalNotificationService.swift @@ -0,0 +1,35 @@ +// +// LocalNotificationService.swift +// Utilities +// +// Created by Qiang Huang on 11/4/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +public class LocalNotificationMessage: NSObject { + public var title: String + public var subtitle: String? + public var text: String? + public var link: String? + public var delay: TimeInterval? + + public init(title: String, subtitle: String? = nil, text: String? = nil, link: String? = nil, delay: TimeInterval? = nil) { + self.title = title + super.init() + self.subtitle = subtitle + self.text = text + self.link = link + self.delay = delay + } +} + +public protocol LocalNotificationProtocol: NSObjectProtocol { + var background: LocalNotificationMessage? { get set } + func send(message: LocalNotificationMessage) +} + +public class LocalNotificationService { + public static var shared: LocalNotificationProtocol? +} diff --git a/Utilities/Utilities/_Notification/NotificationBridge.swift b/Utilities/Utilities/_Notification/NotificationBridge.swift new file mode 100644 index 000000000..7a2e6c088 --- /dev/null +++ b/Utilities/Utilities/_Notification/NotificationBridge.swift @@ -0,0 +1,87 @@ +// +// NotificationBridge.swift +// Utilities +// +// Created by Qiang Huang on 10/1/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol NotificationBridgeProtocol: NSObjectProtocol { + func launched() + func registered(deviceToken: Data) + func failed(error: Error) + func received(userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) + func receivedDeeplink(userInfo: [AnyHashable: Any]) -> URL? +} + +public class NotificationBridge: NSObject { + public static var shared: NotificationBridgeProtocol? +} + +public class CompositeNotificationBridge: NSObject, NotificationBridgeProtocol { + + private var bridges: [NotificationBridgeProtocol]? + + public func add(bridge: NotificationBridgeProtocol) { + if bridges == nil { + bridges = [NotificationBridgeProtocol]() + } + bridges?.append(bridge) + } + + public func launched() { + if let bridges = bridges { + for bridge in bridges { + bridge.launched() + } + } + } + + public func registered(deviceToken: Data) { + if let bridges = bridges { + for bridge in bridges { + bridge.registered(deviceToken: deviceToken) + } + } + } + + public func failed(error: Error) { + if let bridges = bridges { + for bridge in bridges { + bridge.failed(error: error) + } + } + } + + public func received(userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + received(userInfo: userInfo, index: 0, results: Set(), fetchCompletionHandler: completionHandler) + } + + public func receivedDeeplink(userInfo: [AnyHashable : Any]) -> URL? { + bridges?.first?.receivedDeeplink(userInfo: userInfo) + } + + public func received(userInfo: [AnyHashable: Any], index: Int, results: Set, fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + if let bridge = bridges?.object(at: index) { + bridge.received(userInfo: userInfo) { [weak self] result in + var results = results + results.insert(result) + self?.received(userInfo: userInfo, index: index + 1, results: results, fetchCompletionHandler: completionHandler) + } + } else { + completionHandler(result(results: results)) + } + } + + private func result(results: Set) -> UIBackgroundFetchResult { + if results.contains(.newData) { + return .newData + } else if results.contains(.noData) { + return .noData + } else { + return .failed + } + } +} diff --git a/Utilities/Utilities/_Notification/NotificationService.swift b/Utilities/Utilities/_Notification/NotificationService.swift new file mode 100644 index 000000000..33a527ac9 --- /dev/null +++ b/Utilities/Utilities/_Notification/NotificationService.swift @@ -0,0 +1,195 @@ +// +// NotificationService.swift +// Utilities +// +// Created by Qiang Huang on 7/18/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +public class FirebaseNotificationConfiguration: NSObject { + public let subscribedTopics: Set + + public init(subscribedTopics: Set = []) { + self.subscribedTopics = subscribedTopics + } + + public override func isEqual(_ object: Any?) -> Bool { + let other = object as? FirebaseNotificationConfiguration + return subscribedTopics == other?.subscribedTopics + } +} + +public class NotificationConfiguration: NSObject { + public let firebase: FirebaseNotificationConfiguration? + + public init(firebase: FirebaseNotificationConfiguration?) { + self.firebase = firebase + } + + public override func isEqual(_ object: Any?) -> Bool { + let other = object as? NotificationConfiguration + return firebase == other?.firebase + } +} + +@objc public protocol NotificationHandlerDelegate { + func didReceiveToken(token: String?) + func didReceivePermission(permission: EPrivacyPermission) +} + +@objc public protocol NotificationHandlerProtocol: NSObjectProtocol { + @objc var authorization: NotificationPermission? { get set } + @objc var configuration: NotificationConfiguration? { get set } + @objc var permission: EPrivacyPermission { get set } + @objc weak var delegate: NotificationHandlerDelegate? { get set } + + func request() + func present(message: [AnyHashable: Any]) + func receive(message: [AnyHashable: Any]) -> Bool +} + +public class NotificationService: NSObject { + public static var shared: NotificationHandlerProtocol? { + didSet { + shared?.authorization = NotificationPermission.shared + } + } +} + +@objc open class NotificationHandler: NSObject, NotificationHandlerProtocol { + + public weak var delegate: NotificationHandlerDelegate? + + @objc open dynamic var authorization: NotificationPermission? { + didSet { + changeObservation(from: oldValue, to: authorization, keyPath: #keyPath(PrivacyPermission.authorization)) { [weak self] _, _, _, _ in + if let self = self { + if let permission = self.authorization?.authorization { + self.permission = permission + } + } + } + } + } + + @objc open dynamic var configuration: NotificationConfiguration? { + didSet { + didSetConfiguration(oldValue: oldValue) + } + } + + @objc open dynamic var permission: EPrivacyPermission = .notDetermined { + didSet { + if permission != oldValue { + switch permission { + case .authorized: + request() + + default: + break + } + delegate?.didReceivePermission(permission: permission) + } + } + } + + @objc open dynamic var token: String? { + didSet { + if token != oldValue { + delegate?.didReceiveToken(token: token) + } + } + } + + open func request() { + } + + open func present(message: [AnyHashable: Any]) { + } + + open func receive(message: [AnyHashable: Any]) -> Bool { + return false + } + + open func didSetConfiguration(oldValue: NotificationConfiguration?) { + } +} + +open class NotificationUserAssociation: NSObject, AuthProviderAttachmentProtocol { + public static var shared: NotificationUserAssociation? + + private let userIdentifierTag = "NotificationHandler.userIdentifier" + private let deviceTokenTag = "NotificationHandler.deviceToken.2" + + @objc open dynamic var userIdentifier: String? { + didSet { + if userIdentifier != oldValue { + dissociate(deviceToken: deviceToken, from: oldValue, userChanged: true) + associate(deviceToken: deviceToken, with: userIdentifier) + UserDefaults.standard.set(userIdentifier, forKey: userIdentifierTag) + } + } + } + + @objc open dynamic var deviceToken: String? { + didSet { + if deviceToken != oldValue { + dissociate(deviceToken: oldValue, from: userIdentifier, userChanged: false) + associate(deviceToken: deviceToken, with: userIdentifier) + UserDefaults.standard.set(deviceToken, forKey: deviceTokenTag) + } + } + } + + override public init() { + super.init() + userIdentifier = UserDefaults.standard.string(forKey: userIdentifierTag)?.trim() + deviceToken = UserDefaults.standard.string(forKey: deviceTokenTag)?.trim() + } + + open func dissociate(deviceToken: String?, from userIdentifier: String?, userChanged: Bool) { + if !userChanged { + reallyDisassociate(deviceToken: deviceToken, from: userIdentifier, completion: nil) + } + } + + private var associateDebouncer: Debouncer = Debouncer() + + open func associate(deviceToken: String?, with userIdentifier: String?) { + let handler = associateDebouncer.debounce() + handler?.run({ [weak self] in + self?.reallyAssociate(deviceToken: deviceToken, with: userIdentifier) + }, delay: 0.1) + } + + open func reallyAssociate(deviceToken: String?, with userIdentifier: String?) { + if let deviceToken = deviceToken, let userIdentifier = userIdentifier { + Console.shared.log("associate \(deviceToken) with user \(userIdentifier)") + } + } + + open func beforeLogin(completion: @escaping AuthAttachmentCompletionBlock) { + completion(true) + } + + open func afterLogin() { + associate(deviceToken: deviceToken, with: userIdentifier) + } + + open func beforeLogout(token: String, completion: @escaping AuthAttachmentCompletionBlock) { + completion(true) + reallyDisassociate(deviceToken: deviceToken, from: token, completion: completion) + } + + open func afterLogout() { + } + + open func reallyDisassociate(deviceToken: String?, from userIndentifier: String?, completion: AuthAttachmentCompletionBlock?) { + if let deviceToken = deviceToken, let userIdentifier = userIdentifier { + Console.shared.log("dissociate \(deviceToken) from user \(userIdentifier)") + } + completion?(true) + } +} diff --git a/Utilities/Utilities/_Observing/CombineObserving.swift b/Utilities/Utilities/_Observing/CombineObserving.swift new file mode 100644 index 000000000..1fec07d9a --- /dev/null +++ b/Utilities/Utilities/_Observing/CombineObserving.swift @@ -0,0 +1,86 @@ +// +// CombineObserving.swift +// Utilities +// +// Created by Rui Huang on 5/5/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import Foundation +import Combine + +public protocol CombineObserving: AnyObject { + var cancellableMap: [AnyKeyPath: AnyCancellable] { get set } +} + +public extension CombineObserving { + + func observeTo(publisher: Published.Publisher?, + keyPath: AnyKeyPath, + resetCondition: (() -> Bool), + dedupCondition: ( (T, T) -> Bool)? = nil, + initial: @escaping ((_ obj: T?, _ emitState: EmitState) -> ()), + change: @escaping ((_ obj: T?, _ emitState: EmitState) -> ())) { + if resetCondition() { + cancellableMap[keyPath]?.cancel() + cancellableMap.removeValue(forKey: keyPath) + if let publisher = publisher { + _ = publisher + .prefix(1) + .sink { t in + DispatchQueue.main.async { + initial(t, .initial) + } + } + + if let dedupCondition = dedupCondition { + cancellableMap[keyPath] = + publisher + .dropFirst() + .removeDuplicates(by: dedupCondition) + .sink { t in + DispatchQueue.main.async { + change(t, .change) + } + } + } else { + cancellableMap[keyPath] = + publisher + .dropFirst() + .sink { t in + DispatchQueue.main.async { + change(t, .change) + } + } + } + } + } + } + + func observeTo(publisher: Published.Publisher?, + keyPath: AnyKeyPath, + resetCondition: (() -> Bool), + dedupCondition: ( (T, T) -> Bool)? = nil, + change: @escaping ((_ obj: T?, _ emitState: EmitState) -> ())) { + observeTo(publisher: publisher, + keyPath: keyPath, + resetCondition: resetCondition, + dedupCondition: dedupCondition, + initial: change, + change: change) + } +} + +public enum EmitState { + case initial + case change + + public var shouldAnimate: Bool { + switch self { + case .initial: + return false + case .change: + return true + } + } +} diff --git a/Utilities/Utilities/_Observing/Publisher+Ext.swift b/Utilities/Utilities/_Observing/Publisher+Ext.swift new file mode 100644 index 000000000..a4bf71ef5 --- /dev/null +++ b/Utilities/Utilities/_Observing/Publisher+Ext.swift @@ -0,0 +1,33 @@ +// +// Publisher+Ext.swift +// Utilities +// +// Created by Rui Huang on 4/21/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation +import Combine + +public extension Publisher { + func withLatestFrom

(_ other: P) -> AnyPublisher<(Self.Output, P.Output), Failure> where P: Publisher, Self.Failure == P.Failure { + let other = other + // Note: Do not use `.map(Optional.some)` and `.prepend(nil)`. + // There is a bug in iOS versions prior 14.5 in `.combineLatest`. If P.Output itself is Optional. + // In this case prepended `Optional.some(nil)` will become just `nil` after `combineLatest`. + .map { (value: $0, ()) } + .prepend((value: nil, ())) + + return map { (value: $0, token: UUID()) } + .combineLatest(other) + .removeDuplicates(by: { (old, new) in + let lhs = old.0, rhs = new.0 + return lhs.token == rhs.token + }) + .map { ($0.value, $1.value) } + .compactMap { (left, right) in + right.map { (left, $0) } + } + .eraseToAnyPublisher() + } +} diff --git a/Utilities/Utilities/_Permissions/BluetoothPermission.swift b/Utilities/Utilities/_Permissions/BluetoothPermission.swift new file mode 100644 index 000000000..350283cc3 --- /dev/null +++ b/Utilities/Utilities/_Permissions/BluetoothPermission.swift @@ -0,0 +1,79 @@ +// +// BluetoothPermission.swift +// Utilities +// +// Created by Qiang Huang on 7/18/20. +// Copyright © 2020 Qiang Huang. All rights reserved. +// + +import CoreBluetooth +import Foundation + +@objc public class BluetoothPermission: PrivacyPermission, CBCentralManagerDelegate { + private static var _shared: BluetoothPermission? + public static var shared: BluetoothPermission { + get { + if _shared == nil { + _shared = BluetoothPermission() + } + return _shared! + } + set { + _shared = newValue + } + } + + public override func awakeAfter(using aDecoder: NSCoder) -> Any? { + if type(of: self)._shared == nil { + type(of: self)._shared = super.awakeAfter(using: aDecoder) as? BluetoothPermission + } + return type(of: self).shared + } + + public override var requestMessage: String? { + return "Please enable Bluetooth in your app settings." + } + + public var centralManager: CBCentralManager? { + didSet { + if centralManager !== oldValue { + oldValue?.delegate = nil + centralManager?.delegate = self + } + } + } + + public override func currentAuthorizationStatus(completion: @escaping PermissionStatusCompletionHandler) { + var status: EPrivacyPermission = .authorized + if #available(iOS 13.1, *) { + switch CBManager.authorization { + case .notDetermined: + status = .notDetermined + + case .denied: + status = .denied + + case .allowedAlways: + status = .authorized + + case .restricted: + status = .restricted + + default: + status = .notDetermined + break + } + } + completion(status, true) + } + + public override func promptToAuthorize() { + centralManager = CBCentralManager(delegate: self, queue: nil) + } + + public func centralManagerDidUpdateState(_ central: CBCentralManager) { + centralManager?.delegate = nil + centralManager = nil + refreshStatus() + } +} diff --git a/Utilities/Utilities/_Permissions/CalendarPermission.swift b/Utilities/Utilities/_Permissions/CalendarPermission.swift new file mode 100644 index 000000000..6707762a3 --- /dev/null +++ b/Utilities/Utilities/_Permissions/CalendarPermission.swift @@ -0,0 +1,31 @@ +// +// CalendarPermission.swift +// Utilities +// +// Created by Qiang Huang on 7/18/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +@objc public class CalendarPermission: PrivacyPermission { + private static var _shared: CalendarPermission? + public static var shared: CalendarPermission { + get { + if _shared == nil { + _shared = CalendarPermission() + } + return _shared! + } + set { + _shared = newValue + } + } + + public override func awakeAfter(using aDecoder: NSCoder) -> Any? { + if type(of: self)._shared == nil { + type(of: self)._shared = super.awakeAfter(using: aDecoder) as? CalendarPermission + } + return type(of: self).shared + } +} diff --git a/Utilities/Utilities/_Permissions/CameraPermission.swift b/Utilities/Utilities/_Permissions/CameraPermission.swift new file mode 100644 index 000000000..4934da77e --- /dev/null +++ b/Utilities/Utilities/_Permissions/CameraPermission.swift @@ -0,0 +1,66 @@ +// +// CameraPermission.swift +// Utilities +// +// Created by Qiang Huang on 7/18/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import AVFoundation +import Foundation + +@objc public class CameraPermission: PrivacyPermission { + private static var _shared: CameraPermission? + public static var shared: CameraPermission { + get { + if _shared == nil { + _shared = CameraPermission() + } + return _shared! + } + set { + _shared = newValue + } + } + + public override func awakeAfter(using aDecoder: NSCoder) -> Any? { + if type(of: self)._shared == nil { + type(of: self)._shared = super.awakeAfter(using: aDecoder) as? CameraPermission + } + return type(of: self).shared + } + + public override var requestMessage: String? { + return "Please enable Camera in your app settings." + } + + public override func currentAuthorizationStatus(completion: @escaping PermissionStatusCompletionHandler) { + let authorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) + var status: EPrivacyPermission = .notDetermined + switch authorizationStatus { + case .authorized: + status = .authorized + + case .denied: + status = .denied + + case .restricted: + status = .restricted + + case .notDetermined: + status = .notDetermined + + default: + status = .notDetermined + } + completion(status, nil) + } + + public override func promptToAuthorize() { + AVCaptureDevice.requestAccess(for: .video, completionHandler: { [weak self] _ in + DispatchQueue.main.async { [weak self] in + self?.refreshStatus() + } + }) + } +} diff --git a/Utilities/Utilities/_Permissions/LocationPermission.swift b/Utilities/Utilities/_Permissions/LocationPermission.swift new file mode 100644 index 000000000..caee63ff3 --- /dev/null +++ b/Utilities/Utilities/_Permissions/LocationPermission.swift @@ -0,0 +1,85 @@ +// +// LocationPermission.swift +// Utilities +// +// Created by Qiang Huang on 7/18/20. +// Copyright © 2020 Qiang Huang. All rights reserved. +// + +import CoreLocation +import Foundation + +@objc open class LocationPermission: PrivacyPermission, CLLocationManagerDelegate { + private static var _shared: LocationPermission? + public static var shared: LocationPermission { + get { + if _shared == nil { + _shared = LocationPermission() + } + return _shared! + } + set { + _shared = newValue + } + } + + public var always: Bool = false + + override public func awakeAfter(using aDecoder: NSCoder) -> Any? { + if type(of: self)._shared == nil { + type(of: self)._shared = super.awakeAfter(using: aDecoder) as? LocationPermission + } + return type(of: self).shared + } + + override public var requestMessage: String? { + return "Please enable Location Service in your app settings." + } + + public var locationManager: CLLocationManager? { + didSet { + if locationManager !== oldValue { + oldValue?.delegate = nil + locationManager?.delegate = self + } + } + } + + override public func currentAuthorizationStatus(completion: @escaping PermissionStatusCompletionHandler) { + let authorizationStatus = CLLocationManager.authorizationStatus() + var status: EPrivacyPermission = .notDetermined + var background: NSNumber? + switch authorizationStatus { + case .notDetermined: + status = .notDetermined + + case .restricted: + status = .restricted + + case .authorizedAlways: + status = .authorized + background = NSNumber(value: true) + + case .authorizedWhenInUse: + status = .authorized + if always { + background = NSNumber(value: false) + } + + default: + status = .denied + } + completion(status, background) + } + + override open func promptToAuthorize() { + if locationManager == nil { + locationManager = CLLocationManager() + } + locationManager?.requestWhenInUseAuthorization() + } + + public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + refreshStatus() + } +} diff --git a/Utilities/Utilities/_Permissions/MotionPermission.swift b/Utilities/Utilities/_Permissions/MotionPermission.swift new file mode 100644 index 000000000..834155276 --- /dev/null +++ b/Utilities/Utilities/_Permissions/MotionPermission.swift @@ -0,0 +1,63 @@ +// +// MotionPermission.swift +// Utilities +// +// Created by Qiang Huang on 9/15/20. +// Copyright © 2020 Qiang Huang. All rights reserved. +// + +import CoreMotion + +@objc public class MotionPermission: PrivacyPermission { + private static var _shared: MotionPermission? + public static var shared: MotionPermission { + get { + if _shared == nil { + _shared = MotionPermission() + } + return _shared! + } + set { + _shared = newValue + } + } + + private var motion: CMMotionActivityManager = CMMotionActivityManager() + + override public func awakeAfter(using aDecoder: NSCoder) -> Any? { + if type(of: self)._shared == nil { + type(of: self)._shared = super.awakeAfter(using: aDecoder) as? MotionPermission + } + return type(of: self).shared + } + + override public var requestMessage: String? { + return "Please enable motion in your app settings." + } + + override public func currentAuthorizationStatus(completion: @escaping PermissionStatusCompletionHandler) { + let authorization = CMMotionActivityManager.authorizationStatus() + switch authorization { + case .notDetermined: + completion(.notDetermined, nil) + + case .authorized: + completion(.authorized, nil) + + case .restricted: + completion(.restricted, nil) + + case .denied: + fallthrough + default: + completion(.denied, nil) + } + } + + override public func promptToAuthorize() { + let now = Date() + motion.queryActivityStarting(from: now, to: now, to: .main) { [weak self] _, _ in + self?.refreshStatus() + } + } +} diff --git a/Utilities/Utilities/_Permissions/NotificationPermission.swift b/Utilities/Utilities/_Permissions/NotificationPermission.swift new file mode 100644 index 000000000..682d4fce9 --- /dev/null +++ b/Utilities/Utilities/_Permissions/NotificationPermission.swift @@ -0,0 +1,60 @@ +// +// NotificationPermission.swift +// Utilities +// +// Created by Qiang Huang on 7/18/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import UserNotifications + +@objc public class NotificationPermission: PrivacyPermission { + private static var _shared: NotificationPermission? + public static var shared: NotificationPermission { + get { + if _shared == nil { + _shared = NotificationPermission() + } + return _shared! + } + set { + _shared = newValue + } + } + + public override func awakeAfter(using aDecoder: NSCoder) -> Any? { + if type(of: self)._shared == nil { + type(of: self)._shared = super.awakeAfter(using: aDecoder) as? NotificationPermission + } + return type(of: self).shared + } + + public override var requestMessage: String? { + return "Please enable notifications in your app settings." + } + + public override func currentAuthorizationStatus(completion: @escaping PermissionStatusCompletionHandler) { + UNUserNotificationCenter.current().getNotificationSettings { settings in + var status: EPrivacyPermission = .notDetermined + switch settings.authorizationStatus { + case .authorized: + status = .authorized + case .denied: + status = .denied + case .notDetermined: + status = .notDetermined + default: + status = .notDetermined + } + completion(status, nil) + } + } + + public override func promptToAuthorize() { + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] _, _ in + DispatchQueue.main.async { [weak self] in + self?.refreshStatus() + } + } + } +} diff --git a/Utilities/Utilities/_Permissions/PhotoAlbumsPermission.swift b/Utilities/Utilities/_Permissions/PhotoAlbumsPermission.swift new file mode 100644 index 000000000..92d93e60a --- /dev/null +++ b/Utilities/Utilities/_Permissions/PhotoAlbumsPermission.swift @@ -0,0 +1,64 @@ +// +// PhotoAlbumPermission.swift +// Utilities +// +// Created by Qiang Huang on 7/18/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation +import Photos + +@objc public class PhotoAlbumsPermission: PrivacyPermission { + private static var _shared: PhotoAlbumsPermission? + public static var shared: PhotoAlbumsPermission { + get { + if _shared == nil { + _shared = PhotoAlbumsPermission() + } + return _shared! + } + set { + _shared = newValue + } + } + + public override func awakeAfter(using aDecoder: NSCoder) -> Any? { + if type(of: self)._shared == nil { + type(of: self)._shared = super.awakeAfter(using: aDecoder) as? PhotoAlbumsPermission + } + return type(of: self).shared + } + + public override var requestMessage: String? { + return "Please enable Photo Albums in your app settings." + } + + public override func currentAuthorizationStatus(completion: @escaping PermissionStatusCompletionHandler) { + let authorizationStatus: PHAuthorizationStatus = PHPhotoLibrary.authorizationStatus() + var status: EPrivacyPermission = .notDetermined + switch authorizationStatus { + case .authorized: + status = .authorized + case .denied: + status = .denied + case .restricted: + status = .restricted + case .notDetermined: + status = .notDetermined + default: + status = .notDetermined + } + completion(status, nil) + } + + public override func promptToAuthorize() { + PHPhotoLibrary.requestAuthorization({ [weak self] _ in + DispatchQueue.main.async { [weak self] in + if let self = self { + self.refreshStatus() + } + } + }) + } +} diff --git a/Utilities/Utilities/_Permissions/PrivacyPermission.swift b/Utilities/Utilities/_Permissions/PrivacyPermission.swift new file mode 100644 index 000000000..49cbb8edc --- /dev/null +++ b/Utilities/Utilities/_Permissions/PrivacyPermission.swift @@ -0,0 +1,130 @@ +// +// PrivacyPermission.swift +// Utilities +// +// Created by Qiang Huang on 7/18/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +@objc public enum EPrivacyPermission: Int { + case unknown = 0 + case notDetermined + case restricted + case denied + case authorized +} + +public typealias PermissionStatusCompletionHandler = (_ authorization: EPrivacyPermission, _ background: NSNumber?) -> Void + +@objc public protocol PrivacyPermissionProtocol { + var authorization: EPrivacyPermission { get set } + var requestTitle: String? { get } + var requestMessage: String? { get } + + func currentAuthorizationStatus(completion: @escaping PermissionStatusCompletionHandler) + func promptToAuthorize() + func promptToSettings(requestTitle: String?, requestMessage: String?, requestCTA: String, cancelTitle: String) + func promptWithRestriction() + func performWithAuthorization() +} + +@objc open class PrivacyPermission: NSObject, PrivacyPermissionProtocol { + open var requestTitle: String? { + return nil + } + + open var requestMessage: String? { + return nil + } + + @objc open dynamic var authorization: EPrivacyPermission = .unknown { + didSet { + if authorization != oldValue { + switch authorization { + case .notDetermined: +// promptToAuthorize() + break + + case .denied: +// promptToSettings() + break + + case .restricted: +// promptWithRestriction() + break + + case .authorized: +// performWithAuthorization() + break + + default: + break + } + } + } + } + + @objc open dynamic var background: NSNumber? + + private var foregroundToken: NotificationToken? + + override public init() { + super.init() + DispatchQueue.main.async { [weak self] in + self?.refreshStatus() + } + + foregroundToken = NotificationCenter.default.observe(notification: UIApplication.willEnterForegroundNotification, do: { [weak self] _ in + if let self = self { + self.refreshStatus() + } + }) + } + + open func refreshStatus() { + currentAuthorizationStatus { [weak self] authorization, background in + if let self = self { + DispatchQueue.main.async { [weak self] in + if let self = self { + if self.authorization != authorization { + self.authorization = authorization + } + if self.background != background { + self.background = background + } + } + } + } + } + } + + open func currentAuthorizationStatus(completion: @escaping PermissionStatusCompletionHandler) { + completion(.notDetermined, nil) + } + + open func promptToAuthorize() { + } + + open func promptWithRestriction() { + } + + open func promptToSettings(requestTitle: String? = nil, requestMessage: String? = nil, requestCTA: String = "Settings", cancelTitle: String = "Cancel") { + if let prompter = PrompterFactory.shared?.prompter() { + prompter.title = requestTitle ?? self.requestTitle + prompter.message = requestMessage ?? self.requestMessage + prompter.style = .selection + let cancel = PrompterAction.cancel() + let settings = PrompterAction(title: requestCTA) { + if let url = URL(string: UIApplication.openSettingsURLString) { + URLHandler.shared?.open(url, completionHandler: nil) + } + } + prompter.prompt([cancel, settings]) + } + } + + open func performWithAuthorization() { + } +} diff --git a/Utilities/Utilities/_Prompter/PrompterFactory.swift b/Utilities/Utilities/_Prompter/PrompterFactory.swift new file mode 100644 index 000000000..0856b48df --- /dev/null +++ b/Utilities/Utilities/_Prompter/PrompterFactory.swift @@ -0,0 +1,13 @@ +// +// PrompterFactory.swift +// Utilities +// +// Created by Qiang Huang on 7/18/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +public final class PrompterFactory: NSObject { + public static var shared: PrompterFactoryProtocol? +} diff --git a/Utilities/Utilities/_Prompter/PrompterProtocol.swift b/Utilities/Utilities/_Prompter/PrompterProtocol.swift new file mode 100644 index 000000000..b8958eaa8 --- /dev/null +++ b/Utilities/Utilities/_Prompter/PrompterProtocol.swift @@ -0,0 +1,63 @@ +// +// PrompterProtocol.swift +// Utilities +// +// Created by Qiang Huang on 7/18/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +public enum PrompterActionStyle { + case normal + case cancel + case destructive +} + +public enum PrompterStyle { + case error + case selection +} + +public typealias PrompterSelection = () -> Void + +open class PrompterAction: NSObject { + public var title: String? + public var style: PrompterActionStyle = .normal + public var enabled: Bool = true + public var selection: PrompterSelection? + + public static func cancel(title: String? = "Cancel", selection: PrompterSelection? = nil) -> PrompterAction { + return PrompterAction(title: title, style: .cancel, selection: selection) + } + + public init(title: String?, style: PrompterActionStyle = .normal, enabled: Bool = true, selection: PrompterSelection? = nil) { + self.title = title + self.style = style + self.enabled = enabled + self.selection = selection + } +} + +public protocol PrompterProtocol: NSObjectProtocol { + var title: String? { get set } + var message: String? { get set } + var style: PrompterStyle { get set } + func set(title: String?, message: String?, style: PrompterStyle) + func prompt(_ actions: [PrompterAction]) + func dismiss() +} + +public typealias TextEntrySelection = (_ text: String?, _ ok: Bool) -> Void + +public protocol TextPrompterProtocol: PrompterProtocol { + var placeholder: String? { get set } + var text: String? { get set } + + func prompt(title: String?, message: String?, text: String?, placeholder: String?, completion: @escaping TextEntrySelection) +} + +public protocol PrompterFactoryProtocol: NSObjectProtocol { + func prompter() -> PrompterProtocol + func textPrompter() -> TextPrompterProtocol +} diff --git a/Utilities/Utilities/_Protocols/GraphingAnchorProtocol.swift b/Utilities/Utilities/_Protocols/GraphingAnchorProtocol.swift new file mode 100644 index 000000000..e21dfafb1 --- /dev/null +++ b/Utilities/Utilities/_Protocols/GraphingAnchorProtocol.swift @@ -0,0 +1,32 @@ +// +// GraphingAnchorProtocol.swift +// Utilities +// +// Created by Qiang Huang on 11/1/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +@objc public protocol GraphingAnchorProtocol: NSObjectProtocol { + var date: Date { get } +} + +@objc public class GraphingAnchor: NSObject { + public static var shared: GraphingAnchorProtocol? +} + +@objc public class StandardGraphingAnchor: NSObject, GraphingAnchorProtocol { + private var _graphingAnchor: Date? + + public var date: Date { + if _graphingAnchor == nil { + let now = Date() + var calendar = Calendar.current + calendar.timeZone = TimeZone(identifier: "UTC")! + let components = calendar.dateComponents([.year, .month, .day], from: now) + _graphingAnchor = calendar.date(from: components) + } + return _graphingAnchor! + } +} diff --git a/Utilities/Utilities/_Protocols/HapticFeedbackProtocol.swift b/Utilities/Utilities/_Protocols/HapticFeedbackProtocol.swift new file mode 100644 index 000000000..92d68efd6 --- /dev/null +++ b/Utilities/Utilities/_Protocols/HapticFeedbackProtocol.swift @@ -0,0 +1,35 @@ +// +// HapticFeedbackProtocol.swift +// Utilities +// +// Created by Qiang Huang on 10/30/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public enum ImpactLevel: Int { + case low + case medium + case high +} + +public enum NotificationType: Int { + case success + case warnng + case error +} + +public protocol HapticFeedbackProtocol { + func prepareImpact(level: ImpactLevel) + func prepareSelection() + func prepareNotify(type: NotificationType) + + func impact(level: ImpactLevel) + func selection() + func notify(type: NotificationType) +} + +public class HapticFeedback: NSObject { + public static var shared: HapticFeedbackProtocol? +} diff --git a/Utilities/Utilities/_Protocols/ImageUploaderProtocol.swift b/Utilities/Utilities/_Protocols/ImageUploaderProtocol.swift new file mode 100644 index 000000000..6a36930ed --- /dev/null +++ b/Utilities/Utilities/_Protocols/ImageUploaderProtocol.swift @@ -0,0 +1,20 @@ +// +// ImageUploaderProtocol.swift +// Utilities +// +// Created by Qiang Huang on 11/28/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import CoreLocation +import Foundation + +public typealias UploadCompletion = (_: String?, _: Error?) -> Void + +@objc public protocol ImageUploaderProtocol: NSObjectProtocol { + @objc var uploading: Bool { get set } + @objc var progress: Float { get set } + + func upload(image: UIImage, location: CLLocation?, completion: UploadCompletion?) + func upload(file: String, location: CLLocation?, completion: UploadCompletion?) +} diff --git a/Utilities/Utilities/_Protocols/LocalAuthenticator.swift b/Utilities/Utilities/_Protocols/LocalAuthenticator.swift new file mode 100644 index 000000000..8aedf564e --- /dev/null +++ b/Utilities/Utilities/_Protocols/LocalAuthenticator.swift @@ -0,0 +1,84 @@ +// +// LocalAuthenticatorProtocol.swift +// Utilities +// +// Created by John Huang on 3/16/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import Foundation +import Combine + +public protocol LocalAuthenticatorProtocol { + var appState: AppState? { get set } + var paused: Bool { get set } + func trigger() +} + +public class LocalAuthenticator: NSObject { + public static var shared: LocalAuthenticatorProtocol? { + didSet { + shared?.appState = AppState.shared + } + } +} + +open class TimedLocalAuthenticator: NSObject, LocalAuthenticatorProtocol, CombineObserving { + public var cancellableMap = [AnyKeyPath : AnyCancellable]() + + public var paused: Bool = false { + didSet { + if paused { + backgroundTimer = nil + } + } + } + + public var appState: AppState? { + didSet { + didSetAppState(oldValue: oldValue) + } + } + + private var background: Bool = false { + didSet { + didSetBackground(oldValue: oldValue) + } + } + + private var backgroundTimer: Timer? { + didSet { + didSetBackgroundTimer(oldValue: oldValue) + } + } + + private func didSetAppState(oldValue: AppState?) { + changeObservation(from: oldValue, to: appState, keyPath: #keyPath(AppState.background)) {[weak self] observer, obj, change, animated in + self?.background = self?.appState?.background ?? false + } + } + + private func didSetBackground(oldValue: Bool) { + if background != oldValue { + if background { + if !paused { + backgroundTimer = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: false, block: { [weak self] _ in + self?.trigger() + self?.backgroundTimer = nil + }) + } + } else { + backgroundTimer = nil + } + } + } + + private func didSetBackgroundTimer(oldValue: Timer?) { + if backgroundTimer !== oldValue { + oldValue?.invalidate() + } + } + + open func trigger() { + } +} diff --git a/Utilities/Utilities/_Protocols/ParsingProtocol.swift b/Utilities/Utilities/_Protocols/ParsingProtocol.swift new file mode 100644 index 000000000..b48a9f8ba --- /dev/null +++ b/Utilities/Utilities/_Protocols/ParsingProtocol.swift @@ -0,0 +1,14 @@ +// +// ParsingProtocol.swift +// Utilities +// +// Created by Qiang Huang on 3/25/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol ParsingProtocol: NSObjectProtocol { + @objc optional func parse(dictionary: [String: Any]) + @objc optional func parse(array: [Any]) +} diff --git a/Utilities/Utilities/_Protocols/ProgressProtocol.swift b/Utilities/Utilities/_Protocols/ProgressProtocol.swift new file mode 100644 index 000000000..471e2d9f8 --- /dev/null +++ b/Utilities/Utilities/_Protocols/ProgressProtocol.swift @@ -0,0 +1,16 @@ +// +// ProgressProtocol.swift +// Utilities +// +// Created by Qiang Huang on 9/1/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol ProgressProtocol: NSObjectProtocol { + @objc var started: Bool { get set } + @objc var error: Error? { get set } + @objc var progress: Float { get set } + @objc var text: String? { get set } +} diff --git a/Utilities/Utilities/_Protocols/SingletonProtocol.swift b/Utilities/Utilities/_Protocols/SingletonProtocol.swift new file mode 100644 index 000000000..c87e745e3 --- /dev/null +++ b/Utilities/Utilities/_Protocols/SingletonProtocol.swift @@ -0,0 +1,17 @@ +// +// SingletonProtocol.swift +// Utilities +// +// Created by Qiang Huang on 11/2/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public protocol SingletonProtocol { + static var shared: Self { get } +} + +public protocol InjectionProtocol: NSObjectProtocol { + static var shared: Self? { get } +} diff --git a/Utilities/Utilities/_Rating/PointsRating.swift b/Utilities/Utilities/_Rating/PointsRating.swift new file mode 100644 index 000000000..e68cfce93 --- /dev/null +++ b/Utilities/Utilities/_Rating/PointsRating.swift @@ -0,0 +1,43 @@ +// +// PointedRating.swift +// Utilities +// +// Created by Qiang Huang on 9/19/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +open class PointsRating: NSObject, RatingProtocol { + public func add(points: Int) { + self.points = self.points + points + } + + private var pointsKey: String { + return "\(String(describing: className)).points" + } + + private var threshold: Int + + open var points: Int { + get { + return UserDefaults.standard.integer(forKey: pointsKey) + } + set { + if newValue >= threshold { + promptForRating() + UserDefaults.standard.set(0, forKey: pointsKey) + } else { + UserDefaults.standard.set(newValue, forKey: pointsKey) + } + } + } + + public init(threshold: Int) { + self.threshold = threshold + super.init() + } + + open func promptForRating() { + } +} diff --git a/Utilities/Utilities/_Rating/RatingService.swift b/Utilities/Utilities/_Rating/RatingService.swift new file mode 100644 index 000000000..cf7bcb37d --- /dev/null +++ b/Utilities/Utilities/_Rating/RatingService.swift @@ -0,0 +1,17 @@ +// +// RatingService.swift +// Utilities +// +// Created by Qiang Huang on 9/19/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public protocol RatingProtocol: NSObjectProtocol { + func add(points: Int) +} + +public class RatingService { + public static var shared: RatingProtocol? +} diff --git a/Utilities/Utilities/_Store/DebugSettingsStore.swift b/Utilities/Utilities/_Store/DebugSettingsStore.swift new file mode 100644 index 000000000..faa511598 --- /dev/null +++ b/Utilities/Utilities/_Store/DebugSettingsStore.swift @@ -0,0 +1,21 @@ +// +// DebugSettingsStore.swift +// Utilities +// +// Created by Rui Huang on 3/31/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +open class DebugSettingsStore: UserDefaultsStore, DebugProtocol { + public var debug: [String : Any]? { + get { + dictionary + } + set { + dictionary = newValue + } + } +} + diff --git a/Utilities/Utilities/_Store/FeatureFlagsStore.swift b/Utilities/Utilities/_Store/FeatureFlagsStore.swift new file mode 100644 index 000000000..dd6b59e34 --- /dev/null +++ b/Utilities/Utilities/_Store/FeatureFlagsStore.swift @@ -0,0 +1,52 @@ +// +// FeatureFlagsStore.swift +// Utilities +// +// Created by Rui Huang on 3/31/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public class FeatureFlagsStore: UserDefaultsStore, FeatureFlagsProtocol { + public static var shared: FeatureFlagsStore? + + public var featureFlags: [String : Any]? { + get { + dictionary + } + set { + dictionary = newValue + } + } + + public func refresh(completion: @escaping () -> Void) { + completion() + } + + public func activate(completion: @escaping () -> Void) { + completion() + } + + public func isOn(feature: String) -> Bool? { + if let value = featureFlags?[feature] as? Bool { + return value + } + return nil + } + + public func value(feature: String) -> String? { + if let value = featureFlags?[feature] as? String { + return value + } + return nil + } + + public func customized() -> Bool { + #if DEBUG + return false + #else + return (featureFlags?.count ?? 0) > 0 + #endif + } +} diff --git a/Utilities/Utilities/_Store/KeyValueStoreProtocol.swift b/Utilities/Utilities/_Store/KeyValueStoreProtocol.swift new file mode 100644 index 000000000..ffa7271fb --- /dev/null +++ b/Utilities/Utilities/_Store/KeyValueStoreProtocol.swift @@ -0,0 +1,16 @@ +// +// KeyValueStoreProtocol.swift +// Utilities +// +// Created by Rui Huang on 3/21/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public protocol KeyValueStoreProtocol { + func value(forKey: String) -> Any? + func setValue(_ value: Any?, forKey: String) + func reset() +} + diff --git a/Utilities/Utilities/_Store/SecureStore.swift b/Utilities/Utilities/_Store/SecureStore.swift new file mode 100644 index 000000000..9c88a748b --- /dev/null +++ b/Utilities/Utilities/_Store/SecureStore.swift @@ -0,0 +1,101 @@ +// +// SecureStore.swift +// Utilities +// +// Created by Rui Huang on 5/19/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public class SecureStore: SecureStoreProtocol { + public static let shared = SecureStore() + + private let secClass = kSecClassGenericPassword + + private init() {} + + public func save(_ data: Data, service: String, account: String) { + let query = [ + kSecValueData: data, + kSecClass: secClass, + kSecAttrService: service, + kSecAttrAccount: account, + ] as [CFString : Any] as CFDictionary + + // Add data in query to keychain + let status = SecItemAdd(query, nil) + + if status == errSecDuplicateItem { + // Item already exist, thus update it. + let query = [ + kSecAttrService: service, + kSecAttrAccount: account, + kSecClass: secClass, + ] as [CFString : Any] as CFDictionary + + let attributesToUpdate = [kSecValueData: data] as CFDictionary + + // Update existing item + let status = SecItemUpdate(query, attributesToUpdate) + if status != errSecSuccess { + Console.shared.log("SecureStore save() error: \(status)") + } + + } else if status != errSecSuccess { + Console.shared.log("SecureStore save() error: \(status)") + } + } + + public func read(service: String, account: String) -> Data? { + let query = [ + kSecAttrService: service, + kSecAttrAccount: account, + kSecClass: secClass, + kSecReturnData: true + ] as [CFString : Any] as CFDictionary + + var result: AnyObject? + SecItemCopyMatching(query, &result) + + return (result as? Data) + } + + public func delete(service: String, account: String) { + let query = [ + kSecAttrService: service, + kSecAttrAccount: account, + kSecClass: secClass, + ] as [CFString : Any] as CFDictionary + + // Delete item from keychain + let status = SecItemDelete(query) + + if status != errSecSuccess { + Console.shared.log("SecureStore delete() error: \(status)") + } + } + + public func save(_ item: T, service: String, account: String) where T : Codable { + do { + let data = try JSONEncoder().encode(item) + save(data, service: service, account: account) + } catch { + Console.shared.log("SecureStore save() error: \(error)") + } + } + + public func read(service: String, account: String, type: T.Type) -> T? where T : Codable { + guard let data = read(service: service, account: account) else { + return nil + } + + do { + let item = try JSONDecoder().decode(type, from: data) + return item + } catch { + Console.shared.log("SecureStore read() error: \(error)") + return nil + } + } +} diff --git a/Utilities/Utilities/_Store/SecureStoreProtocol.swift b/Utilities/Utilities/_Store/SecureStoreProtocol.swift new file mode 100644 index 000000000..f7838ca96 --- /dev/null +++ b/Utilities/Utilities/_Store/SecureStoreProtocol.swift @@ -0,0 +1,18 @@ +// +// SecureStoreProtocol.swift +// Utilities +// +// Created by Rui Huang on 5/19/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public protocol SecureStoreProtocol { + func save(_ data: Data, service: String, account: String) + func read(service: String, account: String) -> Data? + func delete(service: String, account: String) + + func save(_ item: T, service: String, account: String) where T : Codable + func read(service: String, account: String, type: T.Type) -> T? where T : Codable +} diff --git a/Utilities/Utilities/_Store/SettingsStore.swift b/Utilities/Utilities/_Store/SettingsStore.swift new file mode 100644 index 000000000..52d3c884a --- /dev/null +++ b/Utilities/Utilities/_Store/SettingsStore.swift @@ -0,0 +1,12 @@ +// +// SettingsStore.swift +// PlatformUIJedio +// +// Created by Rui Huang on 3/21/23. +// + +import Foundation + +public struct SettingsStore { + public static var shared: (KeyValueStoreProtocol & DebugProtocol)? +} diff --git a/Utilities/Utilities/_Store/UserDefaultsStore.swift b/Utilities/Utilities/_Store/UserDefaultsStore.swift new file mode 100644 index 000000000..487fcb207 --- /dev/null +++ b/Utilities/Utilities/_Store/UserDefaultsStore.swift @@ -0,0 +1,55 @@ +// +// UserDefaultsStore.swift +// Utilities +// +// Created by Rui Huang on 3/21/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +open class UserDefaultsStore: KeyValueStoreProtocol { + public var dictionary: [String : Any]? { + didSet { + var keys = Set() + dictionary?.keys.forEach { key in + userDefaults.setValue(dictionary?[key], forKey: key) + keys.insert(key) + } + oldValue?.keys.forEach { key in + if keys.contains(key) == false { + userDefaults.removeObject(forKey: key) + } + } + } + } + + private let userDefaults: UserDefaults + + public init(tag: String) { + userDefaults = UserDefaults(suiteName: tag) ?? UserDefaults.standard + dictionary = userDefaults.dictionaryRepresentation() + } + + public func value(forKey key: String) -> Any? { + userDefaults.value(forKey: key) + } + + public func setValue(_ value: Any?, forKey key: String) { + if let value = value { + userDefaults.set(value, forKey: key) + dictionary?[key] = value + } else { + userDefaults.removeObject(forKey: key) + dictionary?.removeValue(forKey: key) + } + } + + public func reset() { + let dictionary = userDefaults.dictionaryRepresentation() + dictionary.keys.forEach { key in + userDefaults.removeObject(forKey: key) + self.dictionary?.removeValue(forKey: key) + } + } +} diff --git a/Utilities/Utilities/_Time/Clock.swift b/Utilities/Utilities/_Time/Clock.swift new file mode 100644 index 000000000..93b5a8706 --- /dev/null +++ b/Utilities/Utilities/_Time/Clock.swift @@ -0,0 +1,58 @@ +// +// Clock.swift +// Utilities +// +// Created by John Huang on 1/14/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +@objc public class Clock: NSObject { + public static var shared: Clock = Clock() + + private var timer: Timer? { + didSet { + didSetTimer(oldValue: oldValue) + } + } + + private var refreshingSeconds: Bool = false + private var refreshSeconds: Bool = true + + public var displayTime: Date? { + get { + return nil + } + set { + if let displayTime = newValue, refreshSeconds == false { + let interval = displayTime.timeIntervalSince(time) + refreshSeconds = abs(interval) < 120.0 + } + } + } + + @objc public dynamic var time: Date = Date() + + override public init() { + refreshSeconds = true + super.init() + updateTimer() + } + + private func updateTimer() { + if refreshSeconds != refreshingSeconds || timer === nil { + refreshingSeconds = refreshSeconds + timer = Timer.scheduledTimer(withTimeInterval: refreshSeconds ? 1 : 60, repeats: true, block: { [weak self] _ in + self?.refreshSeconds = false + self?.time = Date() + }) + } + } + + private func didSetTimer(oldValue: Timer?) { + if timer != oldValue { + oldValue?.invalidate() + } + } +} diff --git a/Utilities/Utilities/_Time/TimeCounter.swift b/Utilities/Utilities/_Time/TimeCounter.swift new file mode 100644 index 000000000..82faa90f2 --- /dev/null +++ b/Utilities/Utilities/_Time/TimeCounter.swift @@ -0,0 +1,99 @@ +// +// TimeCounter.swift +// Utilities +// +// Created by Qiang Huang on 5/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +import Combine + +open class TimeCounter: NSObject, TimeCounterProtocol, CombineObserving { + public var cancellableMap = [AnyKeyPath : AnyCancellable]() + + private var appState: AppState? { + didSet { + changeObservation(from: oldValue, to: appState, keyPath: #keyPath(AppState.background)) {[weak self] observer, obj, change, animated in + self?.background = self?.appState?.background ?? false + } + } + } + private var wasOn: Bool = false + + private var background: Bool = false { + didSet { + if background != oldValue { + if background { + wasOn = on + on = false + } else { + on = wasOn + } + } + } + } + + @objc open dynamic var on: Bool = false { + didSet { + if on != oldValue { + timer?.invalidate() + timer = nil + if on { + startTime = Date() + let next = TimeInterval(Int(previousTime + 1)) + timer = Timer.scheduledTimer(withTimeInterval: next - previousTime, repeats: false, block: { [weak self] _ in + if let self = self, self.on { + self.update() + self.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { [weak self] _ in + if let self = self, self.on { + self.update() + } + }) + } + }) + } else { + if let startTime = startTime { + let lapsed = Date().timeIntervalSince(startTime) + previousTime += lapsed + self.startTime = nil + update() + } + } + } + } + } + + @objc open dynamic var time: TimeInterval = 0 { + didSet { + timeText = time.shortText + } + } + + @objc open dynamic var timeText: String? + + private var previousTime: TimeInterval = 0 + + private var startTime: Date? + + private var timer: Timer? + + public override init() { + super.init() + DispatchQueue.main.async {[weak self] in + self?.appState = AppState.shared + } + } + + private func update() { + if let startTime = startTime { + time = previousTime + Date().timeIntervalSince(startTime) + } else { + time = previousTime + } + } + + deinit { + on = false + } +} diff --git a/Utilities/Utilities/_Time/TimeCounterProtocol.swift b/Utilities/Utilities/_Time/TimeCounterProtocol.swift new file mode 100644 index 000000000..61eb148f5 --- /dev/null +++ b/Utilities/Utilities/_Time/TimeCounterProtocol.swift @@ -0,0 +1,14 @@ +// +// TimeCounterProtocol.swift +// Utilities +// +// Created by Qiang Huang on 5/27/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol TimeCounterProtocol { + @objc var on: Bool { get set } + @objc var time: TimeInterval { get } +} diff --git a/Utilities/Utilities/_Tracker/_Shared/CompositeTracking.swift b/Utilities/Utilities/_Tracker/_Shared/CompositeTracking.swift new file mode 100644 index 000000000..afe7ee02a --- /dev/null +++ b/Utilities/Utilities/_Tracker/_Shared/CompositeTracking.swift @@ -0,0 +1,54 @@ +// +// CompositeTracking.swift +// TrackingKit +// +// Created by Qiang Huang on 10/9/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +open class CompositeTracking: NSObject & TrackingProtocol { + public var excluded: Bool = false { + didSet { + if excluded != oldValue { + for tracking in trackings { + tracking.excluded = excluded + } + } + } + } + + private var trackings: [TrackingProtocol] = [TrackingProtocol]() + + open func add(_ tracking: TrackingProtocol?) { + if let aTracking = tracking { + aTracking.excluded = excluded + trackings.append(aTracking) + } + } + + open func setUserId(_ userId: String?) { + for tracking: TrackingProtocol in trackings { + tracking.setUserId(userId) + } + } + + open func setValue(_ value: Any?, forUserProperty userProperty: String) { + for tracking: TrackingProtocol in trackings { + tracking.setValue(value, forUserProperty: userProperty) + } + } + + open func leave(_ path: String?) { + for tracking: TrackingProtocol in trackings { + tracking.leave(path) + } + } + + open func log(event: String, data: [String: Any]?, revenue: NSNumber?) { + for tracking: TrackingProtocol in trackings { + tracking.log(event: event, data: data, revenue: revenue) + } + } +} diff --git a/Utilities/Utilities/_Tracker/_Shared/DebugTracking.swift b/Utilities/Utilities/_Tracker/_Shared/DebugTracking.swift new file mode 100644 index 000000000..3cd827a39 --- /dev/null +++ b/Utilities/Utilities/_Tracker/_Shared/DebugTracking.swift @@ -0,0 +1,36 @@ +// +// DebugTracking.swift +// ParticlesKit +// +// Created by John Huang on 12/20/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public class DebugTracking: NSObject & TrackingProtocol { + + public var excluded: Bool = false + + public func setUserId(_ userId: String?) {} + + public func setValue(_ value: Any?, forUserProperty userProperty: String) {} + + public func leave(_ path: String?) { + if let path = path { + if excluded { + Console.shared.log("Debug Tracking: Leave Excluded Path:\(path)") + } else { + Console.shared.log("Debug Tracking: Leave Path:\(path)") + } + } + } + + open func log(event: String, data: [String: Any]?, revenue: NSNumber?) { + if let revenue = revenue { + Console.shared.log("Debug Tracking: event:\(event), revenue:\(revenue), ", data ?? "") + } else { + Console.shared.log("Debug Tracking: event:\(event), ", data ?? "") + } + } +} diff --git a/Utilities/Utilities/_Tracker/_Shared/Tracking.swift b/Utilities/Utilities/_Tracker/_Shared/Tracking.swift new file mode 100644 index 000000000..bb8a6969c --- /dev/null +++ b/Utilities/Utilities/_Tracker/_Shared/Tracking.swift @@ -0,0 +1,51 @@ +// +// AnalyticsProtocol.swift +// TrackingKit +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public protocol TrackingProtocol: NSObjectProtocol { + var excluded: Bool { get set } + func setUserId(_ userId: String?) + func setValue(_ value: Any?, forUserProperty userProperty: String) + func leave(_ path: String?) + func log(event: String, data: [String: Any]?, revenue: NSNumber?) +} + +public extension TrackingProtocol { + func log(event: String, data: [String: Any]?) { + log(event: event, data: data, revenue: nil) + } +} + +public class Tracking { + public static var shared: TrackingProtocol? +} + +public protocol TrackableEvent: CustomStringConvertible { + var name: String { get } + var customParameters: [String: Any] { get } +} + +public extension TrackableEvent { + var description: String { + let sorted = customParameters.sorted { $0.key < $1.key } + return "dydxAnalytics event \(name) with data: \(sorted)" + } +} + +public protocol TrackingViewProtocol: ScreenIdentifiable { + func logScreenView() +} + +public protocol ScreenIdentifiable { + /// the path identifier specific to mobile + var mobilePath: String { get } + /// the web path identifier which corresponds to the mobile screen + var correspondingWebPath: String? { get } + var screenClass: String { get } +} diff --git a/Utilities/Utilities/_URL/UrlHandler.swift b/Utilities/Utilities/_URL/UrlHandler.swift new file mode 100644 index 000000000..e42176a87 --- /dev/null +++ b/Utilities/Utilities/_URL/UrlHandler.swift @@ -0,0 +1,18 @@ +// +// UrlHandler.swift +// Utilities +// +// Created by Qiang Huang on 8/21/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public protocol URLHandlerProtocol { + func open(_ url: URL, completionHandler completion: ((Bool) -> Void)?) + func canOpenURL(_ url: URL) -> Bool +} + +public class URLHandler { + public static var shared: URLHandlerProtocol? +} diff --git a/Utilities/Utilities/_UserAgent/UserAgent.swift b/Utilities/Utilities/_UserAgent/UserAgent.swift new file mode 100644 index 000000000..e304f1eb1 --- /dev/null +++ b/Utilities/Utilities/_UserAgent/UserAgent.swift @@ -0,0 +1,17 @@ +// +// UserAgent.swift +// Utilities +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +public protocol UserAgentProtocol: NSObjectProtocol { + func userAgent() -> String? +} + +public class UserAgentProvider: NSObject { + public static var shared: UserAgentProtocol? +} diff --git a/Utilities/Utilities/_UserInterface/UserInterface.swift b/Utilities/Utilities/_UserInterface/UserInterface.swift new file mode 100644 index 000000000..15b2653a1 --- /dev/null +++ b/Utilities/Utilities/_UserInterface/UserInterface.swift @@ -0,0 +1,28 @@ +// +// UserInterface.swift +// Utilities +// +// Created by Qiang Huang on 1/19/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +public enum InterfaceType { + case phone + case pad + case watch + case car + case tv + case mac + case voice + case none +} + +public protocol UserInterfaceProtocol: NSObjectProtocol { + var type: InterfaceType? { get } +} + +public class UserInterface { + public static var shared: UserInterfaceProtocol? +} diff --git a/Utilities/Utilities/_Utils/CachedFileLoader.swift b/Utilities/Utilities/_Utils/CachedFileLoader.swift new file mode 100644 index 000000000..9f0fa1919 --- /dev/null +++ b/Utilities/Utilities/_Utils/CachedFileLoader.swift @@ -0,0 +1,70 @@ +// +// CachedFileLoader.swift +// Utilities +// +// Created by Rui Huang on 8/30/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +final public class CachedFileLoader: SingletonProtocol { + public static let shared: CachedFileLoader = CachedFileLoader() + + public func loadString(filePath: String, url: String?, completion: @escaping ((String?) -> Void)) { + loadData(filePath: filePath, url: url) { (data: Data?) in + if let data = data, let string = String(data: data, encoding: .utf8) { + completion(string) + } + } + } + + public func loadData(filePath: String, url: String?, completion: @escaping ((Data?) -> Void)) { + if let cachedFile = cachedFilePath(filePath: filePath), + let cachedData = try? Data(contentsOf: cachedFile) { + completion(cachedData) + } else { + let bundledFile = Bundle.main.bundleURL.appendingPathComponent(filePath) + let bundledData = try? Data(contentsOf: bundledFile) + completion(bundledData) + } + + if let url = url, let url = URL(string: url) { + URLSession.shared.dataTask(with: url) { [weak self] data, response, error in + guard let data = data, error == nil else { + return + } + + if let cachedFile = self?.cachedFilePath(filePath: filePath) { + self?.writeToFile(fileUrl: cachedFile, data: data) + } + completion(data) + } + .resume() + } + } + + private func writeToFile(fileUrl: URL, data: Data) { + do { + let filePath = fileUrl.path + _ = Directory.ensure(filePath.stringByDeletingLastPathComponent) + File.delete(filePath) + try data.write(to: fileUrl) + } catch { + Console.shared.log("CachedFileLoader: unable to write file \(fileUrl): \(error)") + } + } + + private func cachedFilePath(filePath: String) -> URL? { + do { + let cacheDirectory = try FileManager.default.url(for: .documentDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: true) + return cacheDirectory.appendingPathComponent(filePath) + } catch { + Console.shared.log("CachedFileLoader: Error getting cached file path: \(error)") + return nil + } + } +} diff --git a/Utilities/Utilities/_Utils/ClassLoader.swift b/Utilities/Utilities/_Utils/ClassLoader.swift new file mode 100644 index 000000000..55e64eaf2 --- /dev/null +++ b/Utilities/Utilities/_Utils/ClassLoader.swift @@ -0,0 +1,77 @@ +// +// ClassLoader.swift +// Utilities +// +// Created by Rui Huang on 8/12/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public protocol ObjectBuilderProtocol: NSObjectProtocol { + func build() -> T? + func buildAsync(completion: @escaping ((T?) -> Void)) +} + +public extension ObjectBuilderProtocol { + func buildAsync(completion: @escaping ((T?) -> Void)) { + completion(nil) + } +} + +public struct ClassLoader { + private static var cache: [String: AnyClass] = [:] + + public static func load(from className: String, completion: @escaping ((T?) -> Void)) { + if let objClass = cache[className] as? NSObject.Type { + let obj = objClass.init() + if let ret = obj as? T { + completion(ret) + return + } + if let builder = obj as? ObjectBuilderProtocol { + builder.buildAsync { (ret: T?) in + if ret != nil { + completion(ret) + } else if let ret: T = builder.build() { + completion(ret) + } else { + completion(nil) + } + } + return + } + } + + let bundles = Bundle.particles + for bundle in bundles { + // The builder objects are loaded dynamically so they need to NSObject + // However, we can load the builder ObjC object, but builds a non-ObjC object + if let objClass = bundle.classNamed(className) as? NSObject.Type { + let obj = objClass.init() + if let ret = obj as? T { + cache[className] = objClass + completion(ret) + return + } + if let builder = obj as? ObjectBuilderProtocol { + builder.buildAsync { (ret: T?) in + if ret != nil { + cache[className] = objClass + completion(ret) + } else if let ret: T = builder.build() { + cache[className] = objClass + completion(ret) + } else { + completion(nil) + } + } + return + } + } + } + + // assertionFailure("ClassLoader: No matching object found for " + className) + completion(nil) + } +} diff --git a/Utilities/Utilities/_Utils/Debouncer.swift b/Utilities/Utilities/_Utils/Debouncer.swift new file mode 100644 index 000000000..e72c8ea8b --- /dev/null +++ b/Utilities/Utilities/_Utils/Debouncer.swift @@ -0,0 +1,160 @@ +// +// Debouncer.swift +// Utilities +// +// Created by John Huang on 10/21/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public typealias DebouncedFunction = () -> Void + +public protocol DebouncerProtocol: NSObjectProtocol { + func ready(handler: DebounceHandler?, function: @escaping DebouncedFunction) + @discardableResult func run(handler: DebounceHandler?, function: @escaping DebouncedFunction) -> Bool + func finish(handler: DebounceHandler?) + func cancel(handler: DebounceHandler?) +} + +public class Debouncer: NSObject, DebouncerProtocol { + public var current: DebounceHandler? { + didSet { + didSetCurrent(oldValue: oldValue) + } + } + + internal var previous: DebounceHandler? + + public var fifo: Bool = false + + override public init() { + super.init() + } + + public init(fifo: Bool) { + self.fifo = fifo + super.init() + } + + open func didSetCurrent(oldValue: DebounceHandler?) { + if current !== oldValue { + if current != nil { + previous = current + } + } + } + + public func debounce() -> DebounceHandler? { + if !fifo || current == nil { + let debouncer = DebounceHandler(debouncer: self) + current = debouncer + return debouncer + } + return nil + } + + public func ready(handler: DebounceHandler?, function: @escaping DebouncedFunction) { + function() + } + + @discardableResult open func run(handler: DebounceHandler?, function: @escaping DebouncedFunction) -> Bool { + if handler === current { + handler?.reallyRun(function) + return true + } + return false + } + + open func finish(handler: DebounceHandler?) { + if fifo, current === handler { + current = nil + } + } + + open func cancel(handler: DebounceHandler?) { + if current == handler { + current = nil + } + } +} + +public class DebounceHandler: NSObject { + private weak var debouncer: DebouncerProtocol? + + public init(debouncer: DebouncerProtocol) { + self.debouncer = debouncer + super.init() + } + + public func run(_ function: @escaping DebouncedFunction, delay: TimeInterval?, finish: Bool = true) { + let backgrounds: [DebouncedFunction?] = [] + run(backgrounds: backgrounds, final: function, delay: delay) + } + + open func reallyRun(_ function: @escaping DebouncedFunction) { + function() + } + + public func run(background: @escaping DebouncedFunction, final: @escaping DebouncedFunction, delay: TimeInterval?) { + let backgrounds: [DebouncedFunction?] = [background] + run(backgrounds: backgrounds, final: final, delay: delay) + } + + public func run(background: @escaping DebouncedFunction, then: @escaping DebouncedFunction, final: @escaping DebouncedFunction, delay: TimeInterval?) { + let backgrounds: [DebouncedFunction?] = [background, then] + run(backgrounds: backgrounds, final: final, delay: delay) + } + + public func run(background: @escaping DebouncedFunction, then: @escaping DebouncedFunction, then another: @escaping DebouncedFunction, final: @escaping DebouncedFunction, delay: TimeInterval?) { + let backgrounds: [DebouncedFunction?] = [background, then, another] + run(backgrounds: backgrounds, final: final, delay: delay) + } + + open func run(backgrounds: [DebouncedFunction?], final: @escaping DebouncedFunction, delay: TimeInterval?) { + debouncer?.ready(handler: self, function: {[weak self] in + self?.reallyRun(backgrounds: backgrounds, final: final, delay: delay) + }) + } + + open func reallyRun(backgrounds: [DebouncedFunction?], final: @escaping DebouncedFunction, delay: TimeInterval?) { + if let first = backgrounds.first { + var leftOver = backgrounds + leftOver.removeFirst() + if let first = first { + DispatchQueue.global().asyncAfter(deadline: .now() + (delay ?? 0)) { [weak self] in + if let self = self { + let ran = self.debouncer?.run(handler: self, function: first) + if let ran = ran, ran { + self.run(backgrounds: leftOver, final: final, delay: 0) + } + } + } + } else { + run(backgrounds: leftOver, final: final, delay: delay) + } + } else { + let direct = Thread.isMainThread && delay == nil + if direct { + if debouncer?.run(handler: self, function: final) ?? false { + debouncer?.finish(handler: self) + } + } else { + DispatchQueue.main.asyncAfter(deadline: .now() + (delay ?? 0)) { [weak self] in + if let self = self { + if self.debouncer?.run(handler: self, function: final) ?? false { + self.debouncer?.finish(handler: self) + } + } + } + } + } + } + + public func cancel() { + debouncer?.cancel(handler: self) + } + + deinit { + } +} diff --git a/Utilities/Utilities/_Utils/DebugEnabled.swift b/Utilities/Utilities/_Utils/DebugEnabled.swift new file mode 100644 index 000000000..d64d720a4 --- /dev/null +++ b/Utilities/Utilities/_Utils/DebugEnabled.swift @@ -0,0 +1,21 @@ +// +// DebugEnabled.swift +// Utilities +// +// Created by Rui Huang on 8/30/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public struct DebugEnabled { + public static let key = "debug.enabled" + + public static var enabled: Bool { + switch Installation.source { + case .debug: return true + case .testFlight: return UserDefaults.standard.bool(forKey: key) + case .appStore, .jailBroken: return false + } + } +} diff --git a/Utilities/Utilities/_Utils/DebugSettings.swift b/Utilities/Utilities/_Utils/DebugSettings.swift new file mode 100644 index 000000000..3cc26be0f --- /dev/null +++ b/Utilities/Utilities/_Utils/DebugSettings.swift @@ -0,0 +1,27 @@ +// +// DebugSettings.swift +// Utilities +// +// Created by Qiang Huang on 11/30/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public protocol DebugProtocol { + var debug: [String: Any]? { get set } +} + +public class DebugSettings { + public static var shared: DebugProtocol? +} + +public extension DebugProtocol { + func customized() -> Bool { + #if DEBUG + return false + #else + return (debug?.count ?? 0) > 0 + #endif + } +} diff --git a/Utilities/Utilities/_Utils/Installation.swift b/Utilities/Utilities/_Utils/Installation.swift new file mode 100644 index 000000000..fd767dbaa --- /dev/null +++ b/Utilities/Utilities/_Utils/Installation.swift @@ -0,0 +1,88 @@ +// +// Installation.swift +// Utilities +// +// Created by Qiang Huang on 8/23/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public class Installation { + public enum Source { + case debug + case testFlight + case appStore + case jailBroken // potentially side-loaded + } + + // This is private because the use of 'appConfiguration' is preferred. + private static let isTestFlight = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt" + + private static let isAppStore = { + // Keep this code for reference. In case "sandboxReceipt" changes in later iOS + if let receipt: URL = Bundle.main.appStoreReceiptURL { + var error: NSError? + if (receipt as NSURL).checkResourceIsReachableAndReturnError(&error), error == nil { + return true + } + } + return false + }() + + // This can be used to add debug statements. + static var isDebug: Bool { + #if DEBUG + return true + #else + return false + #endif + } + + private static var isJailBroken: Bool = { + #if targetEnvironment(simulator) + return false + #else + if FileManager.default.fileExists(atPath: "/Applications/Cydia.app") + || FileManager.default.fileExists(atPath: "/Library/MobileSubstrate/MobileSubstrate.dylib") + || FileManager.default.fileExists(atPath: "/bin/bash") + || FileManager.default.fileExists(atPath: "/usr/sbin/sshd") + || FileManager.default.fileExists(atPath: "/etc/apt") + || FileManager.default.fileExists(atPath: "/private/var/lib/apt/") + || URLHandler.shared?.canOpenURL(URL(string: "cydia://package/com.example.package")!) ?? false { + return true + } + + let stringToWrite = "Something to test" + let file = "/private/poikjkt.txt" + try? FileManager.default.removeItem(atPath: file) + do { + try stringToWrite.write(toFile: file, atomically: true, encoding: String.Encoding.utf8) + + return true + } catch { + return false + } + #endif + }() + + public static var source: Source { + if isJailBroken { + return .jailBroken + } else if isDebug { + return .debug + } else if isTestFlight { + return .testFlight + } else { + return .appStore + } + } + + public static var isSimulator: Bool = { + #if targetEnvironment(simulator) + return true + #else + return false + #endif + }() +} diff --git a/Utilities/Utilities/_Utils/JsonLoader.swift b/Utilities/Utilities/_Utils/JsonLoader.swift new file mode 100644 index 000000000..88911f895 --- /dev/null +++ b/Utilities/Utilities/_Utils/JsonLoader.swift @@ -0,0 +1,46 @@ +// +// JsonLoader.swift +// Utilities +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +@objc public class JsonLoader: NSObject { + @objc public class func load(file: String) -> Any? { + let fileUrl = URL(fileURLWithPath: file) + guard let data = try? Data(contentsOf: fileUrl) else { + return nil + } + return try? JSONSerialization.jsonObject(with: data, options: []) + } + + @objc public class func load(bundle: Bundle, fileName: String?) -> Any? { + if let fileName = fileName { + let file = bundle.bundlePath.stringByAppendingPathComponent(path: fileName) + return load(file: file) + } + return nil + } + + @objc public class func load(bundles: [Bundle], fileName: String?) -> Any? { + var value: Any? + for bundle in bundles { + value = load(bundle: bundle, fileName: fileName) + if value != nil { + break + } + } + return value + } + + @objc public class func load(bundled fileName: String?) -> Any? { + return load(bundle: Bundle.main, fileName: fileName) + } + + @objc public class func load(data: Data) -> Any? { + return try? JSONSerialization.jsonObject(with: data, options: []) + } +} diff --git a/Utilities/Utilities/_Utils/JsonWriter.swift b/Utilities/Utilities/_Utils/JsonWriter.swift new file mode 100644 index 000000000..6ca121b6e --- /dev/null +++ b/Utilities/Utilities/_Utils/JsonWriter.swift @@ -0,0 +1,23 @@ +// +// JsonWriter.swift +// Utilities +// +// Created by Qiang Huang on 11/24/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public class JsonWriter { + public static func write(_ object: Any?, to file: String?) { + if let object = object, let file = file { + do { + File.delete(file) + _ = Directory.ensure(file.stringByDeletingLastPathComponent) + let json = try JSONSerialization.data(withJSONObject: object, options: .prettyPrinted) + try json.write(to: URL(fileURLWithPath: file)) + } catch { + } + } + } +} diff --git a/Utilities/Utilities/_Utils/LoadingStatus.swift b/Utilities/Utilities/_Utils/LoadingStatus.swift new file mode 100644 index 000000000..66d66dbc7 --- /dev/null +++ b/Utilities/Utilities/_Utils/LoadingStatus.swift @@ -0,0 +1,36 @@ +// +// LoadingStatus.swift +// Utilities +// +// Created by Qiang Huang on 11/20/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +@objc public protocol LoadingStatusProtocol: NSObjectProtocol { + @objc var running: Bool { get set } +} + +public final class LoadingStatus: NSObject, SingletonProtocol, LoadingStatusProtocol { + public static var shared: LoadingStatus = { + LoadingStatus() + }() + + @objc public dynamic var running: Bool = false + private var runningCount: Int = 0 { + didSet { + if runningCount != oldValue { + running = runningCount > 0 + } + } + } + + public func plus() { + runningCount += 1 + } + + public func minus() { + runningCount -= 1 + } +} diff --git a/Utilities/Utilities/_Utils/NetworkConnection.swift b/Utilities/Utilities/_Utils/NetworkConnection.swift new file mode 100644 index 000000000..e1cc0f0b1 --- /dev/null +++ b/Utilities/Utilities/_Utils/NetworkConnection.swift @@ -0,0 +1,74 @@ +// +// NetworkConnection.swift +// Utilities +// +// Created by John Huang on 5/16/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Reachability + +@objc public final class NetworkConnection: NSObject, SingletonProtocol { + public static var shared: NetworkConnection = { + let connection = NetworkConnection() + connection.reachability = try? Reachability() + return connection + }() + + private var foregroundToken: NotificationToken? + @objc public dynamic var connected: NSNumber? + @objc public dynamic var wifi: NSNumber? + + public var reachability: Reachability? { + didSet { + if reachability !== oldValue { + oldValue?.stopNotifier() + reachabilityObserving = nil + if let reachability = reachability { + reachabilityObserving = NotificationCenter.default.observe(reachability, notification: .reachabilityChanged, do: { [weak self] notification in + self?.reachabilityChanged(notification: notification) + }) + do { + try reachability.startNotifier() + } catch { + Console.shared.log("could not start reachability notifier") + } + } else { + connected = nil + wifi = nil + } + } + } + } + + private var reachabilityObserving: NotificationToken? + + @objc func reachabilityChanged(notification: Notification) { + let reachability = notification.object as? Reachability + + switch reachability?.connection { + case .wifi: + connected = true + wifi = true + case .cellular: + connected = true + wifi = false + case .unavailable: + connected = false + wifi = nil + default: + break + } + } + + public override init() { + super.init() + foregroundToken = NotificationCenter.default.observe(notification: UIApplication.willEnterForegroundNotification, do: { [weak self] _ in + self?.reachability = try? Reachability() + }) + } + + deinit { + reachability = nil + } +} diff --git a/Utilities/Utilities/_Utils/NotificationToken.swift b/Utilities/Utilities/_Utils/NotificationToken.swift new file mode 100644 index 000000000..cc37a9c0d --- /dev/null +++ b/Utilities/Utilities/_Utils/NotificationToken.swift @@ -0,0 +1,30 @@ +// +// NotificationToken.swift +// Utilities +// +// Created by Qiang Huang on 5/14/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public class NotificationToken { + let notificationCenter: NotificationCenter + let token: NSObjectProtocol + + init(notificationCenter: NotificationCenter = .default, token: NSObjectProtocol) { + self.notificationCenter = notificationCenter + self.token = token + } + + deinit { + notificationCenter.removeObserver(token) + } +} + +public extension NotificationCenter { + func observe(_ obj: Any? = nil, notification: NSNotification.Name?, queue: OperationQueue? = nil, do block: @escaping (Notification) -> Void) -> NotificationToken { + let token = addObserver(forName: notification, object: obj, queue: queue, using: block) + return NotificationToken(notificationCenter: self, token: token) + } +} diff --git a/Utilities/Utilities/_Utils/NumericUtils.swift b/Utilities/Utilities/_Utils/NumericUtils.swift new file mode 100644 index 000000000..a9ab6eba7 --- /dev/null +++ b/Utilities/Utilities/_Utils/NumericUtils.swift @@ -0,0 +1,53 @@ +// +// NumericUtils.swift +// Utilities +// +// Created by John Huang on 11/3/23. +// Copyright © 2023 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public enum NumericFilter { + case notNegative +} + +public extension Double { + func filter(filter: NumericFilter?) -> Double? { + if (filter == .notNegative) { + if self >= 0.0 { + return self + } else { + return nil + } + } else { + return self + } + } +} + +public extension NSNumber { + func filter(filter: NumericFilter?) -> NSNumber? { + if (filter == .notNegative) { + if self.doubleValue >= 0.0 { + return self + } else { + return nil + } + } else { + return self + } + } +} + +public extension Double { + var asNsNumber: NSNumber { + NSNumber(value: self) + } + } + +public extension Int { + var asNsNumber: NSNumber { + NSNumber(value: self) + } +} diff --git a/Utilities/Utilities/_Utils/ObjectLoader.swift b/Utilities/Utilities/_Utils/ObjectLoader.swift new file mode 100644 index 000000000..3553e009d --- /dev/null +++ b/Utilities/Utilities/_Utils/ObjectLoader.swift @@ -0,0 +1,19 @@ +// +// ObjectLoader.swift +// Utilities +// +// Created by Rui Huang on 8/12/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public struct ObjectLoader { + public static func load(from xibOrClassName: String, completion: @escaping ((T?) -> Void)) { + if let xibLoaded: T = XibLoader.load(from: xibOrClassName) { + completion(xibLoaded) + } else { + ClassLoader.load(from: xibOrClassName, completion: completion) + } + } +} diff --git a/Utilities/Utilities/_Utils/StringLoader.swift b/Utilities/Utilities/_Utils/StringLoader.swift new file mode 100644 index 000000000..7dfa92e82 --- /dev/null +++ b/Utilities/Utilities/_Utils/StringLoader.swift @@ -0,0 +1,42 @@ +// +// JsonLoader.swift +// Utilities +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +@objc public class StringLoader: NSObject { + @objc public class func load(file: String) -> String? { + let fileUrl = URL(fileURLWithPath: file) + guard let data = try? Data(contentsOf: fileUrl) else { + return nil + } + return String(decoding: data, as: UTF8.self) + } + + @objc public class func load(bundle: Bundle, fileName: String?) -> String? { + if let fileName = fileName { + let file = bundle.bundlePath.stringByAppendingPathComponent(path: fileName) + return load(file: file) + } + return nil + } + + @objc public class func load(bundles: [Bundle], fileName: String?) -> String? { + var value: String? + for bundle in bundles { + value = load(bundle: bundle, fileName: fileName) + if value != nil { + break + } + } + return value + } + + @objc public class func load(bundled fileName: String?) -> String? { + return load(bundle: Bundle.main, fileName: fileName) + } +} diff --git a/Utilities/Utilities/_Utils/SynchronizedLock.swift b/Utilities/Utilities/_Utils/SynchronizedLock.swift new file mode 100644 index 000000000..d578d37f1 --- /dev/null +++ b/Utilities/Utilities/_Utils/SynchronizedLock.swift @@ -0,0 +1,35 @@ +// +// SynchronizedLock.swift +// Utilities +// +// Created by Michael Maguire on 5/2/24. +// Copyright © 2024 dYdX Trading Inc. All rights reserved. +// + + +import Darwin + +@propertyWrapper +public struct SynchronizedLock { + private var value: Value + private var lock = NSLock() + + public var wrappedValue: Value { + get { lock.synchronized { value } } + set { lock.synchronized { value = newValue } } + } + + public init(wrappedValue value: Value) { + self.value = value + } +} + +private extension NSLock { + + @discardableResult + func synchronized(_ block: () -> T) -> T { + lock() + defer { unlock() } + return block() + } +} diff --git a/Utilities/Utilities/_Utils/Throttle.swift b/Utilities/Utilities/_Utils/Throttle.swift new file mode 100644 index 000000000..4a37b88af --- /dev/null +++ b/Utilities/Utilities/_Utils/Throttle.swift @@ -0,0 +1,98 @@ +// +// Throttle.swift +// Utilities +// +// Created by Qiang Huang on 11/10/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public class Throttle: NSObject, DebouncerProtocol { + public var current: DebounceHandler? { + didSet { + if current !== oldValue { + if current !== nil { + timerOn = true + } + } + } + } + + public var runner: DebounceHandler? { + didSet { + if runner !== oldValue { + if runner !== nil { + pendingFunction?() + } + } + } + } + + private var timerOn: Bool = false { + didSet { + if timerOn != oldValue { + if timerOn { + if throttleTimer === nil { + throttleTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false, block: { [weak self] _ in + self?.runner = self?.current + self?.timerOn = false + }) + } + } else { + throttleTimer = nil + } + } + } + } + + private var throttleTimer: Timer? { + didSet { + if throttleTimer !== oldValue { + oldValue?.invalidate() + } + } + } + + private var interval: TimeInterval + private var pendingFunction: DebouncedFunction? + + public init(interval: TimeInterval) { + self.interval = interval + super.init() + } + + public func debounce() -> DebounceHandler? { + let debouncer = DebounceHandler(debouncer: self) + current = debouncer + return debouncer + } + + public func ready(handler: DebounceHandler?, function: @escaping DebouncedFunction) { + self.pendingFunction = function + } + + @discardableResult open func run(handler: DebounceHandler?, function: @escaping DebouncedFunction) -> Bool { + if handler === runner { + handler?.reallyRun(function) + return true + } + return false + } + + open func finish(handler: DebounceHandler?) { + if runner === handler { + runner = nil + } + } + + open func cancel(handler: DebounceHandler?) { + if runner == handler { + runner = nil + } + } + + deinit { + throttleTimer = nil + } +} diff --git a/Utilities/Utilities/_Utils/Throttler.swift b/Utilities/Utilities/_Utils/Throttler.swift new file mode 100644 index 000000000..cd89fb235 --- /dev/null +++ b/Utilities/Utilities/_Utils/Throttler.swift @@ -0,0 +1,48 @@ +// +// Throttler.swift +// Utilities +// +// Created by Qiang Huang on 11/8/21. +// Copyright © 2021 dYdX Trading Inc. All rights reserved. +// + +import Foundation + +public class Throttler: Debouncer { +// public var throttleInterval: TimeInterval = 0.1 +// private var throttleStart: Date? +// public var last: DebounceHandler? +// +// public init(throttleInterval: TimeInterval = 0.1) { +// super.init() +// self.throttleInterval = throttleInterval +// } +// +// @discardableResult override public func run(handler: DebounceHandler?, function: @escaping DebouncedFunction) -> Bool { +// if let throttleStart = throttleStart { +// if Date().timeIntervalSince(throttleStart) >= throttleInterval { +// if last === nil { +// last = handler +// } +// } +// if handler === last { +// handler?.reallyRun(function) +// return true +// } else { +// return false +// } +// } else { +// throttleStart = Date() +// return false +// } +// } +// +// open override func finish(handler: DebounceHandler?) { +// if handler === last { +// last = nil +// throttleStart = nil +// } else { +// super.finish(handler: handler) +// } +// } +} diff --git a/Utilities/Utilities/_Utils/Weak.swift b/Utilities/Utilities/_Utils/Weak.swift new file mode 100644 index 000000000..5ad6c7268 --- /dev/null +++ b/Utilities/Utilities/_Utils/Weak.swift @@ -0,0 +1,16 @@ +// +// Weak.swift +// Utilities +// +// Created by Qiang Huang on 12/28/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import Foundation + +public final class Weak { + public weak var object: A? + public init(_ object: A? = nil) { + self.object = object + } +} diff --git a/Utilities/Utilities/_Utils/WebCrypto.swift b/Utilities/Utilities/_Utils/WebCrypto.swift new file mode 100755 index 000000000..69aaf022d --- /dev/null +++ b/Utilities/Utilities/_Utils/WebCrypto.swift @@ -0,0 +1,304 @@ +/* + +MIT License + +Copyright (c) 2017 Etienne Martin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ +/* + +import CryptoSwift +import WebKit + +private func loadJsFile( _ filename: String) -> String? { + let jsPath = Bundle.main.path(forResource: filename, ofType: "js") + if let jsFilePath = jsPath { + do{ + let js = try String(contentsOfFile: jsFilePath, encoding: String.Encoding.utf8) + return js + }catch{ + return nil + } + } + return nil +} + +open class WebCrypto: NSObject, WKScriptMessageHandler{ + + public enum Error: Swift.Error { + // JavaScript exception + case javaScriptException + // Invalid password length + case invalidPasswordLength + // Invalid key length + case invalidKeyLength + // Invalid IV length + case invalidIvLength + // Unknown + case unknown + } + + var webView:WKWebView + var callbackIndex:Int = 0 + var dataCallbacks: [String : (Data?, WebCrypto.Error?) -> ()] = [:] + var stringCallbacks: [String : (String?, WebCrypto.Error?) -> ()] = [:] + + private func registerDataCallback( _ callback: @escaping (Data?, WebCrypto.Error?) -> ()) -> Int { + callbackIndex += 1 + dataCallbacks["\(callbackIndex)"] = callback + return callbackIndex + } + private func registerStringCallback( _ callback: @escaping (String?, WebCrypto.Error?) -> ()) -> Int { + callbackIndex += 1 + stringCallbacks["\(callbackIndex)"] = callback + return callbackIndex + } + + private func convertErrorCode( _ errorCode: String) -> Error { + switch(errorCode){ + case "javaScriptException": + return Error.javaScriptException + case "invalidPasswordLength": + return Error.invalidPasswordLength + case "invalidKeyLength": + return Error.invalidKeyLength + case "invalidIvLength": + return Error.invalidIvLength + default: + return Error.unknown + } + } + + override public init(){ + + webView = WKWebView(frame: .zero) + super.init() + + let initializationErrorMessage = "Unable to load WebCrypto.js" + + let js = loadJsFile("WebCrypto") + if let jsFile = js { + webView.evaluate(script: jsFile, completion: { (result, error) in + if let errorMessage = error { + print(initializationErrorMessage) + print(errorMessage) + }else{ + self.webView.configuration.userContentController.add(self, name: "scriptHandler") + self.webView.evaluateJavaScript("initWebCrypto()") { (result, error) in + if let errorMessage = error { + print(initializationErrorMessage) + print(errorMessage) + } + } + } + }) + }else{ + // WebCrypto.js couldn't be found + print(initializationErrorMessage) + } + } + + // [ Callback handler ] + + public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage){ + if let dict = message.body as? Dictionary { + + let index = dict["callback"]! + let result = dict["result"] + let error = dict["error"] + let function = dict["func"]! + + switch("\(function)"){ + case "data": + + let callback = dataCallbacks["\(index)"]! + + // Deregister the callback + dataCallbacks["\(index)"] = nil + + if let errorCode = error { + callback(nil, convertErrorCode("\(errorCode)")) + return; + } + + if let unwrappedResult = result { + if let data = Data(base64Encoded: "\(unwrappedResult)", options: .ignoreUnknownCharacters) { + callback(data, nil) + }else{ + // Unable to decode the base64 data + callback(nil, Error.unknown) + } + } + + case "string": + + let callback = stringCallbacks["\(index)"]! + + // Deregister the callback + stringCallbacks["\(index)"] = nil + + if let errorCode = error { + callback(nil, convertErrorCode("\(errorCode)")) + return; + } + + if let string = result { + callback("\(string)", nil) + }else{ + // The result is empty + callback(nil, Error.unknown) + } + + default: break + } + } + } + + // [ AES functions ] + + open func encrypt(data: Data, password: String, callback: @escaping (Data?, WebCrypto.Error?) -> ()){ + aes(action: "encrypt", data: data, password: password, key: nil, iv: nil, callback: callback) + } + open func encrypt(data: Data, key: String, iv: String, callback: @escaping (Data?, WebCrypto.Error?) -> ()){ + aes(action: "encrypt", data: data, password: nil, key: key, iv: iv, callback: callback) + } + open func decrypt(data: Data, password: String, callback: @escaping (Data?, WebCrypto.Error?) -> ()){ + aes(action: "decrypt", data: data, password: password, key: nil, iv: nil, callback: callback) + } + open func decrypt(data: Data, key: String, iv: String, callback: @escaping (Data?, WebCrypto.Error?) -> ()){ + aes(action: "decrypt", data: data, password: nil, key: key, iv: iv, callback: callback) + } + + private func aes(action: String, data: Data, password: String?, key: String?, iv: String?, callback: @escaping (Data?, WebCrypto.Error?) -> ()){ + + let base64Data = data.base64EncodedString(options: []) + let index = registerDataCallback(callback) + var secret = "" + + if let unwrappedPassword = password { + secret = "password: '\(unwrappedPassword)'" + }else if let unwrappedKey = key { + secret = "key: '\(unwrappedKey)', iv: '\(iv!)'" + } + + webView.evaluateJavaScript("WebCrypto.\(action)({data: '\(base64Data)', \(secret), callback: \(index)})") { (result, error) in + if error != nil { + // Deregister the callback + self.dataCallbacks["\(index)"] = nil + callback(nil, Error.javaScriptException) + } + } + } + + open func generateKey(length:Int = 256, callback: @escaping (String?, WebCrypto.Error?) -> ()){ + let index = registerStringCallback(callback) + webView.evaluateJavaScript("WebCrypto.generateKey({length: \(length), callback: \(index)})") { (result, error) in + if error != nil { + // Deregister the callback + self.stringCallbacks["\(index)"] = nil + callback(nil, Error.javaScriptException) + } + } + } + open func generateRandomNumber(length:Int, callback: @escaping (String?, WebCrypto.Error?) -> ()){ + let index = registerStringCallback(callback) + webView.evaluateJavaScript("WebCrypto.generateRandomNumber({length: \(length), callback: \(index)})") { (result, error) in + if error != nil { + // Deregister the callback + self.stringCallbacks["\(index)"] = nil + callback(nil, Error.javaScriptException) + } + } + } + open func generateIv(callback: @escaping (String?, WebCrypto.Error?) -> ()){ + generateRandomNumber(length: 16, callback: callback) + } + + // [ Hashing functions ] + + open func sha1(data: Data, callback: @escaping (String?, WebCrypto.Error?) -> ()){ + hash(data: data, algorithm: "SHA-1", callback: callback) + } + open func sha256(data: Data, callback: @escaping (String?, WebCrypto.Error?) -> ()){ + hash(data: data, algorithm: "SHA-256", callback: callback) + } + open func sha384(data: Data, callback: @escaping (String?, WebCrypto.Error?) -> ()){ + hash(data: data, algorithm: "SHA-384", callback: callback) + } + open func sha512(data: Data, callback: @escaping (String?, WebCrypto.Error?) -> ()){ + hash(data: data, algorithm: "SHA-512", callback: callback) + } + + private func hash(data: Data, algorithm: String, callback: @escaping (String?, WebCrypto.Error?) -> ()){ + let base64Data = data.base64EncodedString(options: []) + let index = registerStringCallback(callback) + webView.evaluateJavaScript("WebCrypto.hash({data: '\(base64Data)', algorithm: '\(algorithm)', callback: \(index)})") { (result, error) in + if error != nil { + // Deregister the callback + self.stringCallbacks["\(index)"] = nil + callback(nil, Error.javaScriptException) + } + } + } + + // Data conversion functions (Helpers) + + open func hexEncodedStringFromData( _ data:Data) -> String { + return data.map { String(format: "%02hhx", $0) }.joined() + } + + open func dataFromHexEncodedString( _ hex: String) -> Data { + return Data(hex: hex) +// var hex = hex +// var data = Data() +// while(hex.characters.count > 0) { +// let c: String = hex.substring(to: hex.index(hex.startIndex, offsetBy: 2)) +// hex = hex.substring(from: hex.index(hex.startIndex, offsetBy: 2)) +// var ch: UInt32 = 0 +// Scanner(string: c).scanHexInt32(&ch) +// var char = UInt8(ch) +// data.append(&char, count: 1) +// } +// return data + } + +} + +private extension WKWebView{ + func evaluate(script: String, completion: @escaping (_ result: AnyObject?, _ error: NSError?) -> Void){ + var finished = false + evaluateJavaScript(script){( result, error ) in + if error == nil { + if result != nil { + completion(result as AnyObject?, nil) + } + }else{ + completion(nil, error as NSError?) + } + finished = true + } + while !finished{ + RunLoop.current.run(mode: .default, before: Date.distantFuture) + } + } +} + +*/ diff --git a/Utilities/Utilities/_Utils/XibLoader.swift b/Utilities/Utilities/_Utils/XibLoader.swift new file mode 100644 index 000000000..3ae304943 --- /dev/null +++ b/Utilities/Utilities/_Utils/XibLoader.swift @@ -0,0 +1,72 @@ +// +// XibLoader.swift +// Utilities +// +// Created by John Huang on 1/16/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public class XibLoader { + internal static var cache: [String: UINib] = [:] + + public static func load(from nib: String?) -> T? { + if let nib = nib { + Console.shared.log("Loading XIB: \(nib)") + var uiNib = cache[nib] + if uiNib == nil { + let bundles = Bundle.particles + for bundle in bundles { + uiNib = UINib.safeLoad(xib: nib, bundle: bundle) + if let uiNib = uiNib { + Console.shared.log("Loading XIB from resource \(nib)") + cache[nib] = uiNib + break + } + } + } + if let uiNib = uiNib { + Console.shared.log("Loading Content from XIB \(nib)") + let nibContents = uiNib.instantiate(withOwner: nil, options: nil) + let content = nibContents.first(where: { (object) -> Bool in + object is T + }) as? T + #if DEBUG + if let view = content as? UIView { + view.accessibilityIdentifier = "xib: \(nib)" + } + #endif + return content + } + } + return nil + } + + public static func loadObjects(from nib: String?) -> [T]? { + if let nib = nib { + var uiNib = cache[nib] + if uiNib == nil { + let bundles = Bundle.particles + for bundle in bundles { + uiNib = UINib.safeLoad(xib: nib, bundle: bundle) + if let uiNib = uiNib { + cache[nib] = uiNib + break + } + } + } + if let uiNib = uiNib { + let nibContents = uiNib.instantiate(withOwner: nil, options: nil) + var objects = [T]() + for i in 0 ..< nibContents.count { + if let object = nibContents[i] as? T { + objects.append(object) + } + } + return objects + } + } + return nil + } +} diff --git a/Utilities/Utilities/_Utils/_FeatureFlags/CompositeFeatureFlagsProvider.swift b/Utilities/Utilities/_Utils/_FeatureFlags/CompositeFeatureFlagsProvider.swift new file mode 100644 index 000000000..790a19bd0 --- /dev/null +++ b/Utilities/Utilities/_Utils/_FeatureFlags/CompositeFeatureFlagsProvider.swift @@ -0,0 +1,69 @@ +// +// CompositeFeatureFlagsProvider.swift +// Utilities +// +// Created by Qiang Huang on 10/3/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +import Combine + +public class CompositeFeatureFlagsProvider: NSObject & FeatureFlagsProtocol { + public var local: FeatureFlagsProtocol? + public var remote: FeatureFlagsProtocol? + + public func refresh(completion: @escaping () -> Void) { + if let local = local { + local.activate { [weak self] in + if let remote = self?.remote { + remote.refresh(completion: completion) + } else { + completion() + } + } + } else if let remote = remote { + remote.refresh(completion: completion) + } + } + + public func activate(completion: @escaping () -> Void) { + if let local = local { + local.activate { [weak self] in + if let remote = self?.remote { + remote.activate(completion: completion) + } else { + completion() + } + } + } else if let remote = remote { + remote.activate(completion: completion) + } + } + + public func value(feature: String) -> String? { + switch Installation.source { + case .appStore, .jailBroken: + return remote?.value(feature: feature) + case .debug, .testFlight: + if let localFlag = local?.value(feature: feature) { + return localFlag + } else { + return remote?.value(feature: feature) + } + } + } + + public func isOn(feature: String) -> Bool? { + switch Installation.source { + case .appStore, .jailBroken: + return remote?.isOn(feature: feature) + case .debug, .testFlight: + return local?.isOn(feature: feature) ?? remote?.isOn(feature: feature) + } + } + + public func customized() -> Bool { + return local?.customized() ?? false + } +} diff --git a/Utilities/Utilities/_Utils/_FeatureFlags/FeatureService.swift b/Utilities/Utilities/_Utils/_FeatureFlags/FeatureService.swift new file mode 100644 index 000000000..6027f10e6 --- /dev/null +++ b/Utilities/Utilities/_Utils/_FeatureFlags/FeatureService.swift @@ -0,0 +1,24 @@ +// +// FeatureService.swift +// Utilities +// +// Created by Qiang Huang on 12/19/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation +import Combine + +public protocol FeatureFlagsProtocol { + + func refresh(completion: @escaping () -> Void) + func activate(completion: @escaping () -> Void) + func value(feature: String) -> String? + func isOn(feature: String) -> Bool? + + func customized() -> Bool +} + +public class FeatureService { + public static var shared: FeatureFlagsProtocol? +} diff --git a/Utilities/Utilities/_Utils/_Parser/AppConfiguration.swift b/Utilities/Utilities/_Utils/_Parser/AppConfiguration.swift new file mode 100644 index 000000000..5509627e7 --- /dev/null +++ b/Utilities/Utilities/_Utils/_Parser/AppConfiguration.swift @@ -0,0 +1,13 @@ +// +// AppConfiguration.swift +// Utilities +// +// Created by Qiang Huang on 11/28/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Foundation + +public class AppConfiguration: NSObject { + public static var info: [String: String]? +} diff --git a/Utilities/Utilities/_Utils/_Parser/ConditionalParser.swift b/Utilities/Utilities/_Utils/_Parser/ConditionalParser.swift new file mode 100644 index 000000000..5847e70d3 --- /dev/null +++ b/Utilities/Utilities/_Utils/_Parser/ConditionalParser.swift @@ -0,0 +1,111 @@ +// +// ConditionalParser.swift +// Utilities +// +// Created by John Huang on 7/19/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public class ConditionalParser: Parser { + private let defaultTag = "_default" + + public var conditions: [String: String]? + + @objc override open func asString(_ data: Any?) -> String? { + return super.asString(conditioned(data)) + } + + @objc override open func asStrings(_ data: Any?) -> [String]? { + return super.asStrings(conditioned(data)) + } + + @objc override open func asNumber(_ data: Any?) -> NSNumber? { + return super.asNumber(conditioned(data)) + } + + @objc override open func asBoolean(_ data: Any?) -> NSNumber? { + return super.asBoolean(conditioned(data)) + } + + @objc override open func asDictionary(_ data: Any?) -> [String: Any]? { + return super.asDictionary(conditioned(data)) + } + + @objc override open func asArray(_ data: Any?) -> [Any]? { + return super.asArray(conditioned(data)) + } + + @objc override open func conditioned(_ data: Any?) -> Any? { + if let data = data { + var conditions = deviceRule().merging(self.conditions ?? [:]) { (string1, _) -> String in + string1 + } + if let appInfo = AppConfiguration.info { + conditions = conditions.merging(appInfo, uniquingKeysWith: { (string1, _) -> String in + string1 + }) + } + return conditioned(data, conditions: conditions) + } + return data + } + + @objc private func deviceRule() -> [String: String] { + let key = "device" + switch UIDevice.current.userInterfaceIdiom { + case .phone: + return [key: "phone"] + + case .pad: + return [key: "pad"] + + case .tv: + return [key: "tv"] + + case .carPlay: + return [key: "carPlay"] + + default: + return [:] + } + } + + @objc private func conditioned(_ data: Any, conditions: [String: String]?) -> Any? { + if let dictionary = super.asDictionary(data) { + return conditioned(dictionary: dictionary, conditions: conditions) ?? data + } + if var string = data as? String, string.contains("<"), string.contains(">"), let conditions = conditions { + for arg0 in conditions { + let (key, value) = arg0 + string = string.replacingOccurrences(of: "<\(key)>", with: value) + } + return string + } else { + return data + } + } + + @objc public func conditioned(dictionary: [String: Any], conditions: [String: String]?) -> Any? { + var narrowedResult: Any? + if let _ = dictionary.first(where: { (arg0) -> Bool in + let (key, value) = arg0 + if let node = value as? [String: Any] { + if let ruleValue = conditions?[key] { + narrowedResult = node[ruleValue] + } else { + narrowedResult = node[""] + } + if narrowedResult == nil { + narrowedResult = node[defaultTag] + } + } + return narrowedResult != nil + }), let narrowedResult = narrowedResult { + return conditioned(narrowedResult, conditions: conditions) + } else { + return dictionary + } + } +} diff --git a/Utilities/Utilities/_Utils/_Parser/DebugParser.swift b/Utilities/Utilities/_Utils/_Parser/DebugParser.swift new file mode 100644 index 000000000..b5ef456b5 --- /dev/null +++ b/Utilities/Utilities/_Utils/_Parser/DebugParser.swift @@ -0,0 +1,29 @@ +// +// DebugParser.swift +// Utilities +// +// Created by John Huang on 7/19/19. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +public class DebugParser: ConditionalParser { + @objc public override func conditioned(_ data: Any?) -> Any? { + var conditions = [String: String]() + if let debug = DebugSettings.shared?.debug { + for arg0 in debug { + let (key, value) = arg0 + conditions[key] = parser.asString(value) + } + } + self.conditions = conditions + return super.conditioned(data) + } +} + +extension Parser { + @objc public static var debug: Parser = { + DebugParser() + }() +} diff --git a/Utilities/Utilities/_Utils/_Parser/Parser.swift b/Utilities/Utilities/_Utils/_Parser/Parser.swift new file mode 100644 index 000000000..b062c94e4 --- /dev/null +++ b/Utilities/Utilities/_Utils/_Parser/Parser.swift @@ -0,0 +1,212 @@ +// +// Any+Types.swift +// Utilities +// +// Created by John Huang on 10/8/18. +// Copyright © 2019 dYdX. All rights reserved. +// + +import Foundation + +@objc open class Parser: NSObject { + static let inputFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.locale = Locale.current + return formatter + }() + + @objc public static var standard: Parser = { + Parser() + }() + + @objc open func conditioned(_ data: Any?) -> Any? { + return data + } + + @objc open func asString(_ data: Any?) -> String? { + var temp: String? + if let date = data as? Date { + temp = date.localDateString + } else if let string = data as? NSString { + temp = string as String + } else if let string = data as? String { + temp = string + } else if let convertible = data as? CustomStringConvertible { + temp = convertible.description + } + if temp == "" { + temp = nil + } + return temp?.trim() + } + + @objc open func asValidString(_ data: Any?) -> String? { + if let string = asString(data), string != "" { + return string + } + return nil + } + + @objc open func asStrings(_ data: Any?) -> [String]? { + if let strings = data as? [String] { + return strings + } else if let string = asString(data) { + let lines = string.components(separatedBy: ",") + var strings = [String]() + for line in lines { + if let trimmed = line.trim() { + strings.append(trimmed) + } + } + return strings + } + return nil + } + + @objc open func asNumber(_ data: Any?) -> NSNumber? { + if let number = data as? NSNumber { + return number + } else if let int = data as? Int { + return NSNumber(value: int) + } else if let float = data as? Float { + return NSNumber(value: float) + } else if let double = data as? Double { + return NSNumber(value: double) + } else if let string = data as? String { + if let int = Int(string) { + return NSNumber(integerLiteral: int) + } else if let float = Double(string) { + return NSNumber(floatLiteral: float) + } + } + return nil + } + + @objc open func asNumbers(_ data: Any?) -> [NSNumber]? { + if let numbers = data as? [NSNumber] { + return numbers + } else { + return asStrings(data)?.compactMap({ string in + return asNumber(string) + }) + } + } + + @objc open func asInputNumber(_ data: Any?) -> NSNumber? { + return asInputDecimal(data) +// if let string = asString(data) { +// return type(of: self).inputFormatter.number(from: string) +// } else { +// return asNumber(data) +// } + } + + @objc open func asDecimal(_ data: Any?) -> NSDecimalNumber? { + if let number = data as? NSDecimalNumber { + return number + } else if let number = data as? NSNumber { + return NSDecimalNumber(decimal: number.decimalValue) + } else if let int = data as? Int { + return NSDecimalNumber(value: int) + } else if let float = data as? Float { + return NSDecimalNumber(value: float) + } else if let double = data as? Double { + return NSDecimalNumber(value: double) + } else if let string = data as? String { + if let decimal = Decimal(string: string) { + return NSDecimalNumber(decimal: decimal) + } + } + return nil + } + + @objc open func asInputDecimal(_ data: Any?) -> NSNumber? { + if let string = asString(data) { + let number = NSDecimalNumber(string: string, locale: Locale.current) + return number.decimalValue.isFinite ? number : nil + } else { + return asDecimal(data) + } + } + + @objc open func asBoolean(_ data: Any?) -> NSNumber? { + if let string = (data as? String)?.lowercased() { + if string == "y" || string == "1" || string == "true" || string == "yes" || string == "on" { + return true + } else if string == "n" || string == "0" || string == "false" || string == "no" || string == "off" { + return false + } + } else if let boolean = data as? Bool { + return NSNumber(value: boolean) + } else if let boolean = data as? Int { + return NSNumber(value: boolean) + } + return nil + } + + @objc open func asDictionary(_ data: Any?) -> [String: Any]? { + return data as? [String: Any] + } + + @objc open func asArray(_ data: Any?) -> [Any]? { + if let data = data { + if data is Dictionary { + return nil + } else if let data = data as? [Any] { + return data + } else { + return [data] + } + } + return nil + } + + open func asInt(_ data: Any?) -> Int? { + if let number = data as? NSNumber { + return number.intValue + } else if let int = data as? Int { + return int + } else if let float = data as? Float { + return Int(float + 0.5) + } else if let double = data as? Double { + return Int(double + 0.5) + } else if let string = data as? String { + if let int = Int(string) { + return int + } else if let float = Double(string), float.isFinite, float < Double(Int.max), float > Double(Int.min) { + return Int(float) + } + } else if let date = data as? Date { + return Int(date.timeIntervalSince1970 * 1000) + } + return nil + } + + open func asDate(_ data: Any?) -> Date? { + if let date = data as? Date { + return date + } else if let timeSince1970 = asInt(data) { + return Date(timeIntervalSince1970: TimeInterval(timeSince1970 / 1000)) + } else if let string = data as? String { + if let datetime = Date.datetime(iso8601ServerString: string) { + return datetime + } else if let datetime = Date.datetime(gmtServerString: string) { + return datetime + } + } + return nil + } + + @objc open func asURL(_ data: Any?) -> URL? { + if let string = asString(data) { + return URL(string: string) + } + return nil + } +} + +extension NSObject { + @objc open var parser: Parser { + return Parser.standard + } +} diff --git a/Utilities/UtilitiesAppleTV/Info.plist b/Utilities/UtilitiesAppleTV/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/Utilities/UtilitiesAppleTV/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/Utilities/UtilitiesAppleTV/Utilities.h b/Utilities/UtilitiesAppleTV/Utilities.h new file mode 100644 index 000000000..ed5df193b --- /dev/null +++ b/Utilities/UtilitiesAppleTV/Utilities.h @@ -0,0 +1,18 @@ +// +// UtilitiesAppleTV.h +// UtilitiesAppleTV +// +// Created by Qiang Huang on 12/6/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for UtilitiesAppleTV. +FOUNDATION_EXPORT double UtilitiesAppleTVVersionNumber; + +//! Project version string for UtilitiesAppleTV. +FOUNDATION_EXPORT const unsigned char UtilitiesAppleTVVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import +#import diff --git a/Utilities/UtilitiesAppleTVTests/Info.plist b/Utilities/UtilitiesAppleTVTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/Utilities/UtilitiesAppleTVTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Utilities/UtilitiesAppleTVTests/UtilitiesAppleTVTests.swift b/Utilities/UtilitiesAppleTVTests/UtilitiesAppleTVTests.swift new file mode 100644 index 000000000..bef37d5d6 --- /dev/null +++ b/Utilities/UtilitiesAppleTVTests/UtilitiesAppleTVTests.swift @@ -0,0 +1,32 @@ +// +// UtilitiesAppleTVTests.swift +// UtilitiesAppleTVTests +// +// Created by Qiang Huang on 12/6/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +@testable import Utilities +import XCTest + +class UtilitiesAppleTVTests: XCTestCase { + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. + } + } +} diff --git a/Utilities/UtilitiesAppleWatch/Info.plist b/Utilities/UtilitiesAppleWatch/Info.plist new file mode 100644 index 000000000..e1fe4cfb7 --- /dev/null +++ b/Utilities/UtilitiesAppleWatch/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/Utilities/UtilitiesAppleWatch/Utilities.h b/Utilities/UtilitiesAppleWatch/Utilities.h new file mode 100644 index 000000000..1c1baacb7 --- /dev/null +++ b/Utilities/UtilitiesAppleWatch/Utilities.h @@ -0,0 +1,18 @@ +// +// UtilitiesAppleWatch.h +// UtilitiesAppleWatch +// +// Created by Qiang Huang on 12/4/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +#import + +//! Project version number for UtilitiesAppleWatch. +FOUNDATION_EXPORT double UtilitiesAppleWatchVersionNumber; + +//! Project version string for UtilitiesAppleWatch. +FOUNDATION_EXPORT const unsigned char UtilitiesAppleWatchVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import +#import diff --git a/Utilities/UtilitiesTests/Info.plist b/Utilities/UtilitiesTests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/Utilities/UtilitiesTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Utilities/UtilitiesTests/UtilitiesTests.swift b/Utilities/UtilitiesTests/UtilitiesTests.swift new file mode 100644 index 000000000..84a6852af --- /dev/null +++ b/Utilities/UtilitiesTests/UtilitiesTests.swift @@ -0,0 +1,102 @@ +// +// UtilitiesTests.swift +// UtilitiesTests +// +// Created by Qiang Huang on 10/8/18. +// Copyright © 2018 dYdX. All rights reserved. +// + +import XCTest +@testable import Utilities + +class UtilitiesTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testStrings() { + let string = "this/is/a/test.json" + XCTAssert(string.lastPathComponent == "test.json", "lastPathComponent error") + XCTAssert(string.pathExtension == "json", "pathExtension error") + XCTAssert(string.stringByDeletingLastPathComponent == "this/is/a", "stringByDeletingLastPathComponent error") + XCTAssert(string.stringByDeletingPathExtension == "this/is/a/test", "stringByDeletingPathExtension error") + XCTAssert(string.pathComponents == ["this", "is", "a", "test.json"], "pathComponents error") + XCTAssert(string.stringByAppendingPathComponent(path: "another") == "this/is/a/test.json/another", "stringByAppendingPathComponent error") + XCTAssert(string.stringByAppendingPathExtension(ext: "pdf") == "this/is/a/test.json.pdf", "stringByAppendingPathExtension error") + + XCTAssert(string.begins(with: "this"), "begins error") + XCTAssert(!string.begins(with: "thisx"), "begins error") + + XCTAssert(string.ends(with: "test.json"), "ends error") + XCTAssert(!string.ends(with: "test_json"), "ends error") + } + + func testStringNumberConversion() { + let decimal = "\(Locale.current.decimalSeparator ?? ".")" + XCTAssertEqual("-", "-".truncateToWholeNumber()) + XCTAssertEqual(nil, "--".truncateToWholeNumber()) + XCTAssertEqual(nil, "\(decimal)".truncateToWholeNumber()) + XCTAssertEqual("1", "1".truncateToWholeNumber()) + XCTAssertEqual("0", "0".truncateToWholeNumber()) + XCTAssertEqual("-0", "-0".truncateToWholeNumber()) + XCTAssertEqual("-1", "-1".truncateToWholeNumber()) + XCTAssertEqual("1", "1\(decimal)".truncateToWholeNumber()) + XCTAssertEqual("0", "0\(decimal)".truncateToWholeNumber()) + XCTAssertEqual("-1", "-1\(decimal)".truncateToWholeNumber()) + XCTAssertEqual("1", "1\(decimal)0".truncateToWholeNumber()) + XCTAssertEqual("0", "0\(decimal)0".truncateToWholeNumber()) + XCTAssertEqual("-1", "-1\(decimal)0".truncateToWholeNumber()) + XCTAssertEqual(nil, "--1\(decimal)0".truncateToWholeNumber()) + XCTAssertEqual("-1", "-01\(decimal)0".truncateToWholeNumber()) + XCTAssertEqual("63256", "63256".truncateToWholeNumber()) + XCTAssertEqual("63256", "63256\(decimal)".truncateToWholeNumber()) + XCTAssertEqual("63256", "63256\(decimal)0".truncateToWholeNumber()) + XCTAssertEqual("63256", "63256\(decimal)0123456789".truncateToWholeNumber()) + XCTAssertEqual("63256", "63256\(decimal)".truncateToWholeNumber()) + XCTAssertEqual("12345678912345678901234567891234567890", "0012345678912345678901234567891234567890\(decimal)12345678912345678901234567891234567890".truncateToWholeNumber()) + + XCTAssertEqual("-", "-".cleanAsDecimalNumber()) + XCTAssertEqual(nil, "--".cleanAsDecimalNumber()) + XCTAssertEqual("0\(decimal)", "\(decimal)".cleanAsDecimalNumber()) + XCTAssertEqual("1", "1".cleanAsDecimalNumber()) + XCTAssertEqual("0", "0".cleanAsDecimalNumber()) + XCTAssertEqual("-0", "-0".cleanAsDecimalNumber()) + XCTAssertEqual("-1", "-1".cleanAsDecimalNumber()) + XCTAssertEqual("1\(decimal)", "1\(decimal)".cleanAsDecimalNumber()) + XCTAssertEqual("0\(decimal)", "0\(decimal)".cleanAsDecimalNumber()) + XCTAssertEqual("-1\(decimal)", "-1\(decimal)".cleanAsDecimalNumber()) + XCTAssertEqual("1\(decimal)0", "1\(decimal)0".cleanAsDecimalNumber()) + XCTAssertEqual("0\(decimal)0", "0\(decimal)0".cleanAsDecimalNumber()) + XCTAssertEqual("-1\(decimal)0", "-1\(decimal)0".cleanAsDecimalNumber()) + XCTAssertEqual(nil, "--1\(decimal)0".cleanAsDecimalNumber()) + XCTAssertEqual("-1\(decimal)0", "-01\(decimal)0".cleanAsDecimalNumber()) + XCTAssertEqual("63256", "63256".cleanAsDecimalNumber()) + XCTAssertEqual("63256\(decimal)", "63256\(decimal)".cleanAsDecimalNumber()) + XCTAssertEqual("63256\(decimal)0", "63256\(decimal)0".cleanAsDecimalNumber()) + XCTAssertEqual("63256\(decimal)0123456789", "63256\(decimal)0123456789".cleanAsDecimalNumber()) + XCTAssertEqual("63256\(decimal)", "63256\(decimal)".cleanAsDecimalNumber()) + XCTAssertEqual("12345678912345678901234567891234567890\(decimal)12345678912345678901234567891234567890", "0012345678912345678901234567891234567890\(decimal)12345678912345678901234567891234567890".cleanAsDecimalNumber()) + + XCTAssertEqual(nil, "-a-3skl2dj1 ']\(decimal) EKD 1 JK 2 J 3KLS(@&#".cleanAsDecimalNumber()) + } + + func testJavascriptRunner() { + let javascriptRunner = JavascriptRunner(file: nil) + let script = """ + var testFunct = function(message) { return \"Test Message: \" + message;} + """ + javascriptRunner.run(script: script) { result in + DispatchQueue.main.async { + javascriptRunner.invoke(className:nil, function: "testFunct", params: ["my message"]) {result in + XCTAssert((result as? String) == "Test Message: my message") + } + } + } + } + +} diff --git a/Utilities/UtilitiesTests/_Extensions/NSObject+ObservingTests.swift b/Utilities/UtilitiesTests/_Extensions/NSObject+ObservingTests.swift new file mode 100644 index 000000000..e989aefad --- /dev/null +++ b/Utilities/UtilitiesTests/_Extensions/NSObject+ObservingTests.swift @@ -0,0 +1,63 @@ +// +// NSObject+ObservingTests.swift +// UtilitiesTests +// +// Created by Rui Huang on 5/9/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import XCTest +import Utilities + +class NSObject_ObservingTests: XCTestCase { + + fileprivate var publisher: PublisherMock! + fileprivate var observer: ObserverMock! + + override func setUp() { + super.setUp() + publisher = PublisherMock() + observer = ObserverMock() + } + + func disabled_testObservingPerformance() throws { + measure { + var receivedInitialVals = [Int?]() + var receivedChangeVals = [Int?]() + + observer.initialBlock = { _, val, _, _ in + receivedInitialVals.append(val as? Int) + } + + observer.changeBlock = { _, val, _, _ in + receivedChangeVals.append(val as? Int) + } + + // Setter called -> Should execute initial block with the current publisher value + observer.publisher = publisher + for i in 0..<1_000_000 { + publisher.count = i + } + } + } +} + +fileprivate class PublisherMock: NSObject { + @objc dynamic var count: Int = 0 + + override init() {} +} + +fileprivate class ObserverMock: NSObject { + + var initialBlock: KVONotificationBlock? + var changeBlock: KVONotificationBlock? + + var publisher: PublisherMock? { + didSet { + changeObservation(from: oldValue, to: publisher, keyPath: #keyPath(PublisherMock.count), initial: initialBlock, change: changeBlock) + } + } + + override init() {} +} diff --git a/Utilities/UtilitiesTests/_Observing/CombineObservingTests.swift b/Utilities/UtilitiesTests/_Observing/CombineObservingTests.swift new file mode 100644 index 000000000..09367694b --- /dev/null +++ b/Utilities/UtilitiesTests/_Observing/CombineObservingTests.swift @@ -0,0 +1,177 @@ +// +// CombineObservingTests.swift +// UtilitiesTests +// +// Created by Rui Huang on 5/8/22. +// Copyright © 2022 dYdX Trading Inc. All rights reserved. +// + +import XCTest +import Utilities +import Combine + +class CombineObservingTests: XCTestCase { + + fileprivate var publisher: PublisherMock! + fileprivate var observer: ObserverMock! + + override func setUp() { + super.setUp() + publisher = PublisherMock() + observer = ObserverMock() + } + + func testCombineObserving() throws { + var receivedInitialVals = [Int?]() + var receivedChangeVals = [Int?]() + + observer.initialBlock = { val, emitState in + receivedInitialVals.append(val) + XCTAssertEqual(val, self.publisher?.count) + XCTAssertEqual(emitState, .initial) + } + + observer.changeBlock = { val, emitState in + receivedChangeVals.append(val) + XCTAssertEqual(val, self.publisher?.count) + XCTAssertEqual(emitState, .change) + } + + // Setter called -> Should execute initial block with the current publisher value + observer.publisher = publisher + waitForDispatchMain() + + XCTAssertEqual(receivedInitialVals, [0]) + XCTAssertEqual(receivedChangeVals, []) + + // Publisher emits a new value -> should execute change block + publisher.count = 12 + waitForDispatchMain() + + XCTAssertEqual(receivedInitialVals, [0]) + XCTAssertEqual(receivedChangeVals, [12]) + + publisher.count = 12 + waitForDispatchMain() + + XCTAssertEqual(receivedInitialVals, [0]) + XCTAssertEqual(receivedChangeVals, [12, 12]) + + // Set the same publisher -> No change + observer.publisher = publisher + waitForDispatchMain() + + XCTAssertEqual(receivedInitialVals, [0]) + XCTAssertEqual(receivedChangeVals, [12, 12]) + + // Set to a new publisher -> initital gets called again + publisher = PublisherMock() + publisher.count = 1 + waitForDispatchMain() + + observer.publisher = publisher + waitForDispatchMain() + + publisher.count = 13 + waitForDispatchMain() + + XCTAssertEqual(receivedInitialVals, [0, 1]) + XCTAssertEqual(receivedChangeVals, [12, 12, 13]) + } + + func testCombineObserving_deDup() throws { + var receivedInitialVals = [Int?]() + var receivedChangeVals = [Int?]() + + observer.initialBlock = { val, emitState in + receivedInitialVals.append(val) + XCTAssertEqual(val, self.publisher?.count) + XCTAssertEqual(emitState, .initial) + } + + observer.changeBlock = { val, emitState in + receivedChangeVals.append(val) + XCTAssertEqual(val, self.publisher?.count) + XCTAssertEqual(emitState, .change) + } + + // Setter called -> Should execute initial block with the current publisher value + observer.deDupPublisher = publisher + waitForDispatchMain() + + XCTAssertEqual(receivedInitialVals, [0]) + XCTAssertEqual(receivedChangeVals, []) + + // Publisher emits a new value -> should execute change block + publisher.count = 12 + waitForDispatchMain() + + publisher.count = 12 + waitForDispatchMain() + + XCTAssertEqual(receivedInitialVals, [0]) + XCTAssertEqual(receivedChangeVals, [12]) + + } + + func disabled_testCombineObservingPerformance() throws { + measure { + var receivedInitialVals = [Int?]() + var receivedChangeVals = [Int?]() + + observer.initialBlock = { val, animated in + receivedInitialVals.append(val) + } + + observer.changeBlock = { val, animated in + receivedChangeVals.append(val) + } + + // Setter called -> Should execute initial block with the current publisher value + observer.publisher = publisher + for i in 0..<1_000_000 { + publisher.count = i + } + } + } +} + +extension XCTestCase { + func waitForDispatchMain() { + RunLoop.current.run(until: Date()) + } +} + +fileprivate class PublisherMock { + @Published var count: Int = 0 +} + +fileprivate class ObserverMock: CombineObserving { + var cancellableMap = [AnyKeyPath : AnyCancellable]() + + var initialBlock: ((_ obj: Int?, _ emitState: EmitState) -> ()) = { _, _ in } + var changeBlock: ((_ obj: Int?, _ emitState: EmitState) -> ()) = { _, _ in } + + var publisher: PublisherMock? { + didSet { + observeTo(publisher: publisher?.$count, + keyPath: \PublisherMock.count, + resetCondition: { oldValue !== publisher }, + initial: initialBlock, + change: changeBlock) + } + } + + var deDupPublisher: PublisherMock? { + didSet { + observeTo(publisher: deDupPublisher?.$count, + keyPath: \PublisherMock.count, + resetCondition: { oldValue !== deDupPublisher }, + dedupCondition: ==, + initial: initialBlock, + change: changeBlock) + } + } + + init() {} +} diff --git a/WebParticles/WebParticles.xcodeproj/project.pbxproj b/WebParticles/WebParticles.xcodeproj/project.pbxproj new file mode 100644 index 000000000..a7c92174b --- /dev/null +++ b/WebParticles/WebParticles.xcodeproj/project.pbxproj @@ -0,0 +1,1247 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 0D0099D26F496B5B2C346BBC /* Pods_iOS_WebParticles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 18A0A720E3EB2165646A9B7C /* Pods_iOS_WebParticles.framework */; }; + 3112B56E25264776009D19B6 /* ParticlesWebPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3112B56D25264776009D19B6 /* ParticlesWebPresenter.swift */; }; + 31471FE024BA392E00057221 /* WebParticles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31471FD624BA392E00057221 /* WebParticles.framework */; }; + 31471FE524BA392E00057221 /* WebParticlesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FE424BA392E00057221 /* WebParticlesTests.swift */; }; + 31471FE724BA392E00057221 /* WebParticles.h in Headers */ = {isa = PBXBuildFile; fileRef = 31471FD924BA392E00057221 /* WebParticles.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 31471FF324BA396100057221 /* WebCookieDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FF224BA396100057221 /* WebCookieDomain.swift */; }; + 31471FF524BA397C00057221 /* ParticlesWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FF424BA397C00057221 /* ParticlesWebView.swift */; }; + 31471FF824BA39C000057221 /* ParticlesWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FF724BA39C000057221 /* ParticlesWebViewController.swift */; }; + 31471FFA24BA39DC00057221 /* WebCookie.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FF924BA39DC00057221 /* WebCookie.swift */; }; + 31471FFC24BA39EF00057221 /* WebCookieStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FFB24BA39EF00057221 /* WebCookieStorage.swift */; }; + 31471FFE24BA3A0A00057221 /* WebProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31471FFD24BA3A0A00057221 /* WebProtocols.swift */; }; + 314C359724C7BFC700695F7E /* Web.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 314C359524C7BFC700695F7E /* Web.storyboard */; }; + 31F7605924BA3E06001EA293 /* ParticlesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31F7601B24BA3DC2001EA293 /* ParticlesKit.framework */; platformFilter = ios; }; + 31F7605D24BA3E06001EA293 /* RoutingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31F7602D24BA3DC2001EA293 /* RoutingKit.framework */; platformFilter = ios; }; + 31F7605F24BA3E06001EA293 /* UIToolkits.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31F75F8D24BA3DA2001EA293 /* UIToolkits.framework */; platformFilter = ios; }; + 31F7606124BA3E06001EA293 /* Utilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31F75FA324BA3DA2001EA293 /* Utilities.framework */; platformFilter = ios; }; + FD5CCD1840132D5A7D45F02B /* Pods_iOS_WebParticlesTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6E73FCA10092D3DAED1F4DF /* Pods_iOS_WebParticlesTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 31471FE124BA392E00057221 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31471FCD24BA392E00057221 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31471FD524BA392E00057221; + remoteInfo = WebParticles; + }; + 31F75F8C24BA3DA2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F8224BA3DA2001EA293 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65ADD216BC9E1008ABEE9; + remoteInfo = UIToolkits; + }; + 31F75F8E24BA3DA2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F8224BA3DA2001EA293 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB85D21BB730700BEF926; + remoteInfo = UIToolkitsAppleWatch; + }; + 31F75F9024BA3DA2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F8224BA3DA2001EA293 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB0421BB79AD00BEF926; + remoteInfo = UIToolkitsAppleTV; + }; + 31F75F9224BA3DA2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F8224BA3DA2001EA293 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310C3A0B21D9623E0081E56D; + remoteInfo = UIAppToolkits; + }; + 31F75F9424BA3DA2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F8224BA3DA2001EA293 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AE6216BC9E1008ABEE9; + remoteInfo = UIToolkitsTests; + }; + 31F75F9624BA3DA2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F8224BA3DA2001EA293 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB0C21BB79AD00BEF926; + remoteInfo = UIToolkitsAppleTVTests; + }; + 31F75F9824BA3DA2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F8224BA3DA2001EA293 /* UIToolkits.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 310C3A1321D9623F0081E56D; + remoteInfo = UIAppToolkitsTests; + }; + 31F75FA224BA3DA2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F9A24BA3DA2001EA293 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AB9216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 31F75FA424BA3DA2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F9A24BA3DA2001EA293 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196823721B791CF00AE0F28; + remoteInfo = UtilitiesAppleWatch; + }; + 31F75FA624BA3DA2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F9A24BA3DA2001EA293 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536521B9805F00A92D62; + remoteInfo = UtilitiesAppleTV; + }; + 31F75FA824BA3DA2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F9A24BA3DA2001EA293 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65AC2216BC9C9008ABEE9; + remoteInfo = UtilitiesTests; + }; + 31F75FAA24BA3DA2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F9A24BA3DA2001EA293 /* Utilities.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313A536D21B9805F00A92D62; + remoteInfo = UtilitiesAppleTVTests; + }; + 31F7601A24BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7601224BA3DC2001EA293 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FDA21B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 31F7601C24BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7601224BA3DC2001EA293 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 319682B421B7967B00AE0F28; + remoteInfo = ParticlesKitAppleWatch; + }; + 31F7601E24BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7601224BA3DC2001EA293 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16121BA246A00BEF926; + remoteInfo = ParticlesKitAppleTV; + }; + 31F7602024BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7601224BA3DC2001EA293 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 311C0FE321B0E9C4001775BA; + remoteInfo = ParticlesKitTests; + }; + 31F7602224BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7601224BA3DC2001EA293 /* ParticlesKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB16921BA246A00BEF926; + remoteInfo = ParticlesKitAppleTVTests; + }; + 31F7602C24BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7602424BA3DC2001EA293 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B01216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 31F7602E24BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7602424BA3DC2001EA293 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3196825B21B7947600AE0F28; + remoteInfo = RoutingKitAppleWatch; + }; + 31F7603024BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7602424BA3DC2001EA293 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22321BA26D700BEF926; + remoteInfo = RoutingKitAppleTV; + }; + 31F7603224BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7602424BA3DC2001EA293 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31E65B0A216BCA10008ABEE9; + remoteInfo = RoutingKitTests; + }; + 31F7603424BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7602424BA3DC2001EA293 /* RoutingKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB22B21BA26D800BEF926; + remoteInfo = RoutingKitAppleTVTests; + }; + 31F7604024BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7603624BA3DC2001EA293 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB72921B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 31F7604224BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7603624BA3DC2001EA293 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EB7C621BB592E00BEF926; + remoteInfo = PlatformParticlesAppleWatch; + }; + 31F7604424BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7603624BA3DC2001EA293 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB4321BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTV; + }; + 31F7604624BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7603624BA3DC2001EA293 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CFF7C121D7D1F200DE7A79; + remoteInfo = MessageParticles; + }; + 31F7604824BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7603624BA3DC2001EA293 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31ACB73221B0EE2D00391ADF; + remoteInfo = PlatformParticlesTests; + }; + 31F7604A24BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7603624BA3DC2001EA293 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 313EBB4B21BB7A4B00BEF926; + remoteInfo = PlatformParticlesAppleTVTests; + }; + 31F7604C24BA3DC2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7603624BA3DC2001EA293 /* PlatformParticles.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 31CFF7C921D7D1F300DE7A79; + remoteInfo = MessageParticlesTests; + }; + 31F7604E24BA3DE2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7601224BA3DC2001EA293 /* ParticlesKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 311C0FD921B0E9C4001775BA; + remoteInfo = ParticlesKit; + }; + 31F7605024BA3DE2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7602424BA3DC2001EA293 /* RoutingKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65B00216BCA10008ABEE9; + remoteInfo = RoutingKit; + }; + 31F7605224BA3DE2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F9A24BA3DA2001EA293 /* Utilities.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65AB8216BC9C9008ABEE9; + remoteInfo = Utilities; + }; + 31F7605424BA3DE2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F7603624BA3DC2001EA293 /* PlatformParticles.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31ACB72821B0EE2D00391ADF; + remoteInfo = PlatformParticles; + }; + 31F7605624BA3DE2001EA293 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31F75F8224BA3DA2001EA293 /* UIToolkits.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 31E65ADC216BC9E1008ABEE9; + remoteInfo = UIToolkits; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 114AAE436549410818938EE0 /* Pods-iOS-WebParticlesTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-WebParticlesTests.release.xcconfig"; path = "Target Support Files/Pods-iOS-WebParticlesTests/Pods-iOS-WebParticlesTests.release.xcconfig"; sourceTree = ""; }; + 18A0A720E3EB2165646A9B7C /* Pods_iOS_WebParticles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_WebParticles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3112B56D25264776009D19B6 /* ParticlesWebPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesWebPresenter.swift; sourceTree = ""; }; + 31471FD624BA392E00057221 /* WebParticles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WebParticles.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31471FD924BA392E00057221 /* WebParticles.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebParticles.h; sourceTree = ""; }; + 31471FDA24BA392E00057221 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31471FDF24BA392E00057221 /* WebParticlesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WebParticlesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 31471FE424BA392E00057221 /* WebParticlesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebParticlesTests.swift; sourceTree = ""; }; + 31471FE624BA392E00057221 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 31471FF224BA396100057221 /* WebCookieDomain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebCookieDomain.swift; sourceTree = ""; }; + 31471FF424BA397C00057221 /* ParticlesWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesWebView.swift; sourceTree = ""; }; + 31471FF724BA39C000057221 /* ParticlesWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticlesWebViewController.swift; sourceTree = ""; }; + 31471FF924BA39DC00057221 /* WebCookie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebCookie.swift; sourceTree = ""; }; + 31471FFB24BA39EF00057221 /* WebCookieStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebCookieStorage.swift; sourceTree = ""; }; + 31471FFD24BA3A0A00057221 /* WebProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebProtocols.swift; sourceTree = ""; }; + 314C359524C7BFC700695F7E /* Web.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Web.storyboard; sourceTree = ""; }; + 31F75F8224BA3DA2001EA293 /* UIToolkits.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = UIToolkits.xcodeproj; path = ../UIToolkits/UIToolkits.xcodeproj; sourceTree = ""; }; + 31F75F9A24BA3DA2001EA293 /* Utilities.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Utilities.xcodeproj; path = ../Utilities/Utilities.xcodeproj; sourceTree = ""; }; + 31F7601224BA3DC2001EA293 /* ParticlesKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParticlesKit.xcodeproj; path = ../ParticlesKit/ParticlesKit.xcodeproj; sourceTree = ""; }; + 31F7602424BA3DC2001EA293 /* RoutingKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RoutingKit.xcodeproj; path = ../RoutingKit/RoutingKit.xcodeproj; sourceTree = ""; }; + 31F7603624BA3DC2001EA293 /* PlatformParticles.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PlatformParticles.xcodeproj; path = ../PlatformParticles/PlatformParticles.xcodeproj; sourceTree = ""; }; + 780C0C0AE831784B5F295109 /* Pods-iOS-WebParticlesTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-WebParticlesTests.debug.xcconfig"; path = "Target Support Files/Pods-iOS-WebParticlesTests/Pods-iOS-WebParticlesTests.debug.xcconfig"; sourceTree = ""; }; + D576A3241A22A82F48623210 /* Pods-iOS-WebParticles.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-WebParticles.release.xcconfig"; path = "Target Support Files/Pods-iOS-WebParticles/Pods-iOS-WebParticles.release.xcconfig"; sourceTree = ""; }; + F14D93AB29DF0B44062804F9 /* Pods-iOS-WebParticles.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-WebParticles.debug.xcconfig"; path = "Target Support Files/Pods-iOS-WebParticles/Pods-iOS-WebParticles.debug.xcconfig"; sourceTree = ""; }; + F6E73FCA10092D3DAED1F4DF /* Pods_iOS_WebParticlesTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_WebParticlesTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 31471FD324BA392E00057221 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31F7606124BA3E06001EA293 /* Utilities.framework in Frameworks */, + 31F7605D24BA3E06001EA293 /* RoutingKit.framework in Frameworks */, + 31F7605924BA3E06001EA293 /* ParticlesKit.framework in Frameworks */, + 31F7605F24BA3E06001EA293 /* UIToolkits.framework in Frameworks */, + 0D0099D26F496B5B2C346BBC /* Pods_iOS_WebParticles.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31471FDC24BA392E00057221 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 31471FE024BA392E00057221 /* WebParticles.framework in Frameworks */, + FD5CCD1840132D5A7D45F02B /* Pods_iOS_WebParticlesTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02684EE528BD414C0007CEFF /* Dependencies */ = { + isa = PBXGroup; + children = ( + 31F7601224BA3DC2001EA293 /* ParticlesKit.xcodeproj */, + 31F7603624BA3DC2001EA293 /* PlatformParticles.xcodeproj */, + 31F7602424BA3DC2001EA293 /* RoutingKit.xcodeproj */, + 31F75F8224BA3DA2001EA293 /* UIToolkits.xcodeproj */, + 31F75F9A24BA3DA2001EA293 /* Utilities.xcodeproj */, + ); + name = Dependencies; + sourceTree = ""; + }; + 3112B54E2526475E009D19B6 /* _Presenters */ = { + isa = PBXGroup; + children = ( + 3112B56D25264776009D19B6 /* ParticlesWebPresenter.swift */, + ); + path = _Presenters; + sourceTree = ""; + }; + 31471FCC24BA392E00057221 = { + isa = PBXGroup; + children = ( + 02684EE528BD414C0007CEFF /* Dependencies */, + 31471FD724BA392E00057221 /* Products */, + 31471FD824BA392E00057221 /* WebParticles */, + 31471FE324BA392E00057221 /* WebParticlesTests */, + D44E6DB714D7AC53A350055B /* Pods */, + 93466743F9DE29AA46C29A29 /* Frameworks */, + ); + sourceTree = ""; + }; + 31471FD724BA392E00057221 /* Products */ = { + isa = PBXGroup; + children = ( + 31471FD624BA392E00057221 /* WebParticles.framework */, + 31471FDF24BA392E00057221 /* WebParticlesTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31471FD824BA392E00057221 /* WebParticles */ = { + isa = PBXGroup; + children = ( + 31471FF124BA394E00057221 /* _Cookies */, + 3112B54E2526475E009D19B6 /* _Presenters */, + 31471FF624BA39AC00057221 /* _ViewControllers */, + 31471FF024BA394100057221 /* _WebView */, + 31471FDA24BA392E00057221 /* Info.plist */, + 314C357924C7BF8D00695F7E /* Resources */, + 31471FD924BA392E00057221 /* WebParticles.h */, + ); + path = WebParticles; + sourceTree = ""; + }; + 31471FE324BA392E00057221 /* WebParticlesTests */ = { + isa = PBXGroup; + children = ( + 31471FE424BA392E00057221 /* WebParticlesTests.swift */, + 31471FE624BA392E00057221 /* Info.plist */, + ); + path = WebParticlesTests; + sourceTree = ""; + }; + 31471FF024BA394100057221 /* _WebView */ = { + isa = PBXGroup; + children = ( + 31471FF424BA397C00057221 /* ParticlesWebView.swift */, + ); + path = _WebView; + sourceTree = ""; + }; + 31471FF124BA394E00057221 /* _Cookies */ = { + isa = PBXGroup; + children = ( + 31471FF224BA396100057221 /* WebCookieDomain.swift */, + 31471FF924BA39DC00057221 /* WebCookie.swift */, + 31471FFB24BA39EF00057221 /* WebCookieStorage.swift */, + 31471FFD24BA3A0A00057221 /* WebProtocols.swift */, + ); + path = _Cookies; + sourceTree = ""; + }; + 31471FF624BA39AC00057221 /* _ViewControllers */ = { + isa = PBXGroup; + children = ( + 31471FF724BA39C000057221 /* ParticlesWebViewController.swift */, + ); + path = _ViewControllers; + sourceTree = ""; + }; + 314C357924C7BF8D00695F7E /* Resources */ = { + isa = PBXGroup; + children = ( + 314C359224C7BF9B00695F7E /* _Routing */, + ); + path = Resources; + sourceTree = ""; + }; + 314C359224C7BF9B00695F7E /* _Routing */ = { + isa = PBXGroup; + children = ( + 314C359324C7BFA300695F7E /* _Storyboards */, + ); + path = _Routing; + sourceTree = ""; + }; + 314C359324C7BFA300695F7E /* _Storyboards */ = { + isa = PBXGroup; + children = ( + 314C359524C7BFC700695F7E /* Web.storyboard */, + ); + path = _Storyboards; + sourceTree = ""; + }; + 31F75F8324BA3DA2001EA293 /* Products */ = { + isa = PBXGroup; + children = ( + 31F75F8D24BA3DA2001EA293 /* UIToolkits.framework */, + 31F75F8F24BA3DA2001EA293 /* UIToolkits.framework */, + 31F75F9124BA3DA2001EA293 /* UIToolkits.framework */, + 31F75F9324BA3DA2001EA293 /* UIAppToolkits.framework */, + 31F75F9524BA3DA2001EA293 /* UIToolkitsTests.xctest */, + 31F75F9724BA3DA2001EA293 /* UIToolkitsAppleTVTests.xctest */, + 31F75F9924BA3DA2001EA293 /* UIAppToolkitsTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31F75F9B24BA3DA2001EA293 /* Products */ = { + isa = PBXGroup; + children = ( + 31F75FA324BA3DA2001EA293 /* Utilities.framework */, + 31F75FA524BA3DA2001EA293 /* Utilities.framework */, + 31F75FA724BA3DA2001EA293 /* Utilities.framework */, + 31F75FA924BA3DA2001EA293 /* UtilitiesTests.xctest */, + 31F75FAB24BA3DA2001EA293 /* UtilitiesAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31F7601324BA3DC2001EA293 /* Products */ = { + isa = PBXGroup; + children = ( + 31F7601B24BA3DC2001EA293 /* ParticlesKit.framework */, + 31F7601D24BA3DC2001EA293 /* ParticlesKit.framework */, + 31F7601F24BA3DC2001EA293 /* ParticlesKit.framework */, + 31F7602124BA3DC2001EA293 /* ParticlesKitTests.xctest */, + 31F7602324BA3DC2001EA293 /* ParticlesKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31F7602524BA3DC2001EA293 /* Products */ = { + isa = PBXGroup; + children = ( + 31F7602D24BA3DC2001EA293 /* RoutingKit.framework */, + 31F7602F24BA3DC2001EA293 /* RoutingKit.framework */, + 31F7603124BA3DC2001EA293 /* RoutingKit.framework */, + 31F7603324BA3DC2001EA293 /* RoutingKitTests.xctest */, + 31F7603524BA3DC2001EA293 /* RoutingKitAppleTVTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 31F7603724BA3DC2001EA293 /* Products */ = { + isa = PBXGroup; + children = ( + 31F7604124BA3DC2001EA293 /* PlatformParticles.framework */, + 31F7604324BA3DC2001EA293 /* PlatformParticles.framework */, + 31F7604524BA3DC2001EA293 /* PlatformParticles.framework */, + 31F7604724BA3DC2001EA293 /* MessageParticles.framework */, + 31F7604924BA3DC2001EA293 /* PlatformParticlesTests.xctest */, + 31F7604B24BA3DC2001EA293 /* PlatformParticlesAppleTVTests.xctest */, + 31F7604D24BA3DC2001EA293 /* MessageParticlesTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 93466743F9DE29AA46C29A29 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 18A0A720E3EB2165646A9B7C /* Pods_iOS_WebParticles.framework */, + F6E73FCA10092D3DAED1F4DF /* Pods_iOS_WebParticlesTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D44E6DB714D7AC53A350055B /* Pods */ = { + isa = PBXGroup; + children = ( + F14D93AB29DF0B44062804F9 /* Pods-iOS-WebParticles.debug.xcconfig */, + D576A3241A22A82F48623210 /* Pods-iOS-WebParticles.release.xcconfig */, + 780C0C0AE831784B5F295109 /* Pods-iOS-WebParticlesTests.debug.xcconfig */, + 114AAE436549410818938EE0 /* Pods-iOS-WebParticlesTests.release.xcconfig */, + ); + name = Pods; + path = ../dydx/Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 31471FD124BA392E00057221 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 31471FE724BA392E00057221 /* WebParticles.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 31471FD524BA392E00057221 /* WebParticles */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31471FEA24BA392E00057221 /* Build configuration list for PBXNativeTarget "WebParticles" */; + buildPhases = ( + 9C006DF0C02E748E890F1B80 /* [CP] Check Pods Manifest.lock */, + 31471FD124BA392E00057221 /* Headers */, + 31471FD224BA392E00057221 /* Sources */, + 31471FD324BA392E00057221 /* Frameworks */, + 31471FD424BA392E00057221 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 31F7604F24BA3DE2001EA293 /* PBXTargetDependency */, + 31F7605124BA3DE2001EA293 /* PBXTargetDependency */, + 31F7605324BA3DE2001EA293 /* PBXTargetDependency */, + 31F7605524BA3DE2001EA293 /* PBXTargetDependency */, + 31F7605724BA3DE2001EA293 /* PBXTargetDependency */, + ); + name = WebParticles; + productName = WebParticles; + productReference = 31471FD624BA392E00057221 /* WebParticles.framework */; + productType = "com.apple.product-type.framework"; + }; + 31471FDE24BA392E00057221 /* WebParticlesTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31471FED24BA392E00057221 /* Build configuration list for PBXNativeTarget "WebParticlesTests" */; + buildPhases = ( + 057F8599EE109D5C8D2E6B1E /* [CP] Check Pods Manifest.lock */, + 31471FDB24BA392E00057221 /* Sources */, + 31471FDC24BA392E00057221 /* Frameworks */, + 31471FDD24BA392E00057221 /* Resources */, + 74D1502CC820DA166852A563 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 31471FE224BA392E00057221 /* PBXTargetDependency */, + ); + name = WebParticlesTests; + productName = WebParticlesTests; + productReference = 31471FDF24BA392E00057221 /* WebParticlesTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 31471FCD24BA392E00057221 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1150; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "dYdX Trading Inc"; + TargetAttributes = { + 31471FD524BA392E00057221 = { + CreatedOnToolsVersion = 11.5; + LastSwiftMigration = 1150; + }; + 31471FDE24BA392E00057221 = { + CreatedOnToolsVersion = 11.5; + }; + }; + }; + buildConfigurationList = 31471FD024BA392E00057221 /* Build configuration list for PBXProject "WebParticles" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 31471FCC24BA392E00057221; + productRefGroup = 31471FD724BA392E00057221 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 31F7601324BA3DC2001EA293 /* Products */; + ProjectRef = 31F7601224BA3DC2001EA293 /* ParticlesKit.xcodeproj */; + }, + { + ProductGroup = 31F7603724BA3DC2001EA293 /* Products */; + ProjectRef = 31F7603624BA3DC2001EA293 /* PlatformParticles.xcodeproj */; + }, + { + ProductGroup = 31F7602524BA3DC2001EA293 /* Products */; + ProjectRef = 31F7602424BA3DC2001EA293 /* RoutingKit.xcodeproj */; + }, + { + ProductGroup = 31F75F8324BA3DA2001EA293 /* Products */; + ProjectRef = 31F75F8224BA3DA2001EA293 /* UIToolkits.xcodeproj */; + }, + { + ProductGroup = 31F75F9B24BA3DA2001EA293 /* Products */; + ProjectRef = 31F75F9A24BA3DA2001EA293 /* Utilities.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 31471FD524BA392E00057221 /* WebParticles */, + 31471FDE24BA392E00057221 /* WebParticlesTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 31F75F8D24BA3DA2001EA293 /* UIToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIToolkits.framework; + remoteRef = 31F75F8C24BA3DA2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F75F8F24BA3DA2001EA293 /* UIToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIToolkits.framework; + remoteRef = 31F75F8E24BA3DA2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F75F9124BA3DA2001EA293 /* UIToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIToolkits.framework; + remoteRef = 31F75F9024BA3DA2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F75F9324BA3DA2001EA293 /* UIAppToolkits.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = UIAppToolkits.framework; + remoteRef = 31F75F9224BA3DA2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F75F9524BA3DA2001EA293 /* UIToolkitsTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UIToolkitsTests.xctest; + remoteRef = 31F75F9424BA3DA2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F75F9724BA3DA2001EA293 /* UIToolkitsAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UIToolkitsAppleTVTests.xctest; + remoteRef = 31F75F9624BA3DA2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F75F9924BA3DA2001EA293 /* UIAppToolkitsTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UIAppToolkitsTests.xctest; + remoteRef = 31F75F9824BA3DA2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F75FA324BA3DA2001EA293 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 31F75FA224BA3DA2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F75FA524BA3DA2001EA293 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 31F75FA424BA3DA2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F75FA724BA3DA2001EA293 /* Utilities.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Utilities.framework; + remoteRef = 31F75FA624BA3DA2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F75FA924BA3DA2001EA293 /* UtilitiesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesTests.xctest; + remoteRef = 31F75FA824BA3DA2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F75FAB24BA3DA2001EA293 /* UtilitiesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = UtilitiesAppleTVTests.xctest; + remoteRef = 31F75FAA24BA3DA2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7601B24BA3DC2001EA293 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31F7601A24BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7601D24BA3DC2001EA293 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31F7601C24BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7601F24BA3DC2001EA293 /* ParticlesKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ParticlesKit.framework; + remoteRef = 31F7601E24BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7602124BA3DC2001EA293 /* ParticlesKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitTests.xctest; + remoteRef = 31F7602024BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7602324BA3DC2001EA293 /* ParticlesKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ParticlesKitAppleTVTests.xctest; + remoteRef = 31F7602224BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7602D24BA3DC2001EA293 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 31F7602C24BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7602F24BA3DC2001EA293 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 31F7602E24BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7603124BA3DC2001EA293 /* RoutingKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = RoutingKit.framework; + remoteRef = 31F7603024BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7603324BA3DC2001EA293 /* RoutingKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitTests.xctest; + remoteRef = 31F7603224BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7603524BA3DC2001EA293 /* RoutingKitAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = RoutingKitAppleTVTests.xctest; + remoteRef = 31F7603424BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7604124BA3DC2001EA293 /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 31F7604024BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7604324BA3DC2001EA293 /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 31F7604224BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7604524BA3DC2001EA293 /* PlatformParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PlatformParticles.framework; + remoteRef = 31F7604424BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7604724BA3DC2001EA293 /* MessageParticles.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MessageParticles.framework; + remoteRef = 31F7604624BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7604924BA3DC2001EA293 /* PlatformParticlesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformParticlesTests.xctest; + remoteRef = 31F7604824BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7604B24BA3DC2001EA293 /* PlatformParticlesAppleTVTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PlatformParticlesAppleTVTests.xctest; + remoteRef = 31F7604A24BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 31F7604D24BA3DC2001EA293 /* MessageParticlesTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = MessageParticlesTests.xctest; + remoteRef = 31F7604C24BA3DC2001EA293 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 31471FD424BA392E00057221 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 314C359724C7BFC700695F7E /* Web.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31471FDD24BA392E00057221 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 057F8599EE109D5C8D2E6B1E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-WebParticlesTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 74D1502CC820DA166852A563 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-WebParticlesTests/Pods-iOS-WebParticlesTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iOS-WebParticlesTests/Pods-iOS-WebParticlesTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-WebParticlesTests/Pods-iOS-WebParticlesTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9C006DF0C02E748E890F1B80 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iOS-WebParticles-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 31471FD224BA392E00057221 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31471FFE24BA3A0A00057221 /* WebProtocols.swift in Sources */, + 31471FF824BA39C000057221 /* ParticlesWebViewController.swift in Sources */, + 31471FFC24BA39EF00057221 /* WebCookieStorage.swift in Sources */, + 3112B56E25264776009D19B6 /* ParticlesWebPresenter.swift in Sources */, + 31471FF324BA396100057221 /* WebCookieDomain.swift in Sources */, + 31471FFA24BA39DC00057221 /* WebCookie.swift in Sources */, + 31471FF524BA397C00057221 /* ParticlesWebView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 31471FDB24BA392E00057221 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31471FE524BA392E00057221 /* WebParticlesTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 31471FE224BA392E00057221 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31471FD524BA392E00057221 /* WebParticles */; + targetProxy = 31471FE124BA392E00057221 /* PBXContainerItemProxy */; + }; + 31F7604F24BA3DE2001EA293 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ParticlesKit; + targetProxy = 31F7604E24BA3DE2001EA293 /* PBXContainerItemProxy */; + }; + 31F7605124BA3DE2001EA293 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RoutingKit; + targetProxy = 31F7605024BA3DE2001EA293 /* PBXContainerItemProxy */; + }; + 31F7605324BA3DE2001EA293 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Utilities; + targetProxy = 31F7605224BA3DE2001EA293 /* PBXContainerItemProxy */; + }; + 31F7605524BA3DE2001EA293 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = PlatformParticles; + targetProxy = 31F7605424BA3DE2001EA293 /* PBXContainerItemProxy */; + }; + 31F7605724BA3DE2001EA293 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = UIToolkits; + targetProxy = 31F7605624BA3DE2001EA293 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 31471FE824BA392E00057221 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 31471FE924BA392E00057221 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 31471FEB24BA392E00057221 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F14D93AB29DF0B44062804F9 /* Pods-iOS-WebParticles.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = WebParticles/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.WebParticles; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31471FEC24BA392E00057221 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D576A3241A22A82F48623210 /* Pods-iOS-WebParticles.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = WebParticles/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.particles.WebParticles; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 31471FEE24BA392E00057221 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 780C0C0AE831784B5F295109 /* Pods-iOS-WebParticlesTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = WebParticlesTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.WebParticlesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 31471FEF24BA392E00057221 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 114AAE436549410818938EE0 /* Pods-iOS-WebParticlesTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75C6UARB5H; + INFOPLIST_FILE = WebParticlesTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = www.craziapps.WebParticlesTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 31471FD024BA392E00057221 /* Build configuration list for PBXProject "WebParticles" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31471FE824BA392E00057221 /* Debug */, + 31471FE924BA392E00057221 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31471FEA24BA392E00057221 /* Build configuration list for PBXNativeTarget "WebParticles" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31471FEB24BA392E00057221 /* Debug */, + 31471FEC24BA392E00057221 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 31471FED24BA392E00057221 /* Build configuration list for PBXNativeTarget "WebParticlesTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31471FEE24BA392E00057221 /* Debug */, + 31471FEF24BA392E00057221 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 31471FCD24BA392E00057221 /* Project object */; +} diff --git a/WebParticles/WebParticles/Info.plist b/WebParticles/WebParticles/Info.plist new file mode 100644 index 000000000..9bcb24442 --- /dev/null +++ b/WebParticles/WebParticles/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/WebParticles/WebParticles/Resources/_Routing/_Storyboards/Web.storyboard b/WebParticles/WebParticles/Resources/_Routing/_Storyboards/Web.storyboard new file mode 100644 index 000000000..158be0cc3 --- /dev/null +++ b/WebParticles/WebParticles/Resources/_Routing/_Storyboards/Web.storyboard @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WebParticles/WebParticles/WebParticles.h b/WebParticles/WebParticles/WebParticles.h new file mode 100644 index 000000000..6e47fc585 --- /dev/null +++ b/WebParticles/WebParticles/WebParticles.h @@ -0,0 +1,19 @@ +// +// WebParticles.h +// WebParticles +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +#import + +//! Project version number for WebParticles. +FOUNDATION_EXPORT double WebParticlesVersionNumber; + +//! Project version string for WebParticles. +FOUNDATION_EXPORT const unsigned char WebParticlesVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/WebParticles/WebParticles/_Cookies/WebCookie.swift b/WebParticles/WebParticles/_Cookies/WebCookie.swift new file mode 100644 index 000000000..8f1489c60 --- /dev/null +++ b/WebParticles/WebParticles/_Cookies/WebCookie.swift @@ -0,0 +1,33 @@ +// +// WebCookie.swift +// WebParticles +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import Utilities +import WebKit + +public class WebCookie: NSObject, WebCookieProtocol { + public var domain: String + public var path: String + public var isSecure: Bool = true + public var isSessionOnly: Bool = false + public var isHttpOnly: Bool = false + public var name: String + public var value: String? + public var expires: Date? + + public init(domain: String, path: String = "/", isSecure: Bool = true, isSessionOnly: Bool = false, isHttpOnly: Bool = false, name: String, value: String?, expires: Date?) { + self.domain = domain + self.path = path + self.isSecure = isSecure + self.isSessionOnly = isSessionOnly + self.isHttpOnly = isHttpOnly + self.name = name + self.value = value + self.expires = expires + super.init() + } +} diff --git a/WebParticles/WebParticles/_Cookies/WebCookieDomain.swift b/WebParticles/WebParticles/_Cookies/WebCookieDomain.swift new file mode 100644 index 000000000..e60c796e4 --- /dev/null +++ b/WebParticles/WebParticles/_Cookies/WebCookieDomain.swift @@ -0,0 +1,83 @@ +// +// WebCookieDomain.swift +// WebParticles +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import CoreLocation +import ParticlesKit +import Utilities +import WebKit + +public class WebCookieDomain: NSObject, WebCookieDomainProtocol, WKScriptMessageHandler { +// var locationPermission: LocationPermission? { +// didSet { +// changeObservation(from: oldValue, to: locationPermission, keyPath: #keyPath(LocationPermission.authorization)) { [weak self] _, _, _, _ in +// self?.locationPermissionChanged() +// } +// } +// } +// +// var locationProvider: LocationProviderProtocol? { +// didSet { +// changeObservation(from: oldValue, to: locationProvider, keyPath: #keyPath(LocationProviderProtocol.location)) { [weak self] _, _, _, _ in +// self?.locationChanged() +// } +// } +// } +// +// @objc public dynamic var location: CLLocation? + + public var domain: String + public var userAgent: String? + public var configuration: WKWebViewConfiguration = WKWebViewConfiguration() + public var cookieStorage: WebCookieStorageProtocol? + + public init(domain: String, userAgent: String?) { + self.domain = domain + self.userAgent = userAgent + + super.init() + + if let userAgent = userAgent { + configuration.applicationNameForUserAgent = userAgent + } + cookieStorage = WebCookieStorage(configuration: configuration, domain: domain) + + let controller = WKUserContentController() + controller.add(self, name: "locationHandler") + + configuration.userContentController = controller + + let scriptSource = "navigator.geolocation.getCurrentPosition = function(success, error, options) {window.webkit.messageHandlers.locationHandler.postMessage('getCurrentPosition');};" + let script = WKUserScript(source: scriptSource, injectionTime: .atDocumentStart, forMainFrameOnly: true) + controller.addUserScript(script) + } + + public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + if message.name == "locationHandler", let messageBody = message.body as? String { + if messageBody == "getCurrentPosition" { +// locationProvider = LocationProvider.shared +// locationPermission = LocationPermission.shared + } + } + } + +// func locationPermissionChanged() { +// if let locationPermission = locationPermission { +// switch locationPermission.authorization { +// case .notDetermined: +// locationPermission.promptToAuthorize() +// +// default: +// break +// } +// } +// } +// +// func locationChanged() { +// location = locationProvider?.location +// } +} diff --git a/WebParticles/WebParticles/_Cookies/WebCookieStorage.swift b/WebParticles/WebParticles/_Cookies/WebCookieStorage.swift new file mode 100644 index 000000000..f5c9db2bc --- /dev/null +++ b/WebParticles/WebParticles/_Cookies/WebCookieStorage.swift @@ -0,0 +1,48 @@ +// +// WebCookieStorage.swift +// WebParticles +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import WebKit + +public class WebCookieStorage: NSObject, WebCookieStorageProtocol { + public var domain: String + public var storage: WKHTTPCookieStore + + public init(configuration: WKWebViewConfiguration, domain: String) { + self.domain = domain + storage = configuration.websiteDataStore.httpCookieStore + super.init() + } + + public func cookies(path: String, secure: Bool, completion: @escaping WebCookieGetCompletion) { + storage.getAllCookies { cookies in + completion(cookies) + } + } + + public func set(cookie: WebCookieProtocol, completion: WebCookieCompletion?) { + var properties = [HTTPCookiePropertyKey: Any]() + properties[.domain] = cookie.domain + properties[.path] = cookie.path + properties[.secure] = cookie.isSecure + properties[.name] = cookie.name + if let value = cookie.value { + properties[.value] = value + } + if let expires = cookie.expires { + properties[.expires] = expires + } + if let cookie = HTTPCookie(properties: properties) { + storage.setCookie(cookie) { + completion?() + } + } + } + + public func delete(cookie: WebCookieProtocol, completion: WebCookieCompletion?) { + } +} diff --git a/WebParticles/WebParticles/_Cookies/WebProtocols.swift b/WebParticles/WebParticles/_Cookies/WebProtocols.swift new file mode 100644 index 000000000..a6df0e0d4 --- /dev/null +++ b/WebParticles/WebParticles/_Cookies/WebProtocols.swift @@ -0,0 +1,35 @@ +// +// WebProtocols.swift +// WebParticles +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import WebKit + +public protocol WebCookieProtocol: NSObject { + var domain: String { get set } + var path: String { get set } + var isSecure: Bool { get set } + var isSessionOnly: Bool { get set } + var isHttpOnly: Bool { get set } + var name: String { get set } + var value: String? { get set } + var expires: Date? { get set } +} + +public typealias WebCookieCompletion = () -> Void +public typealias WebCookieGetCompletion = (_ cookies: [HTTPCookie]?) -> Void + +public protocol WebCookieStorageProtocol: NSObjectProtocol { + var domain: String { get set } + func cookies(path: String, secure: Bool, completion: @escaping WebCookieGetCompletion) + func set(cookie: WebCookieProtocol, completion: WebCookieCompletion?) + func delete(cookie: WebCookieProtocol, completion: WebCookieCompletion?) +} + +public protocol WebCookieDomainProtocol: NSObjectProtocol { + var domain: String { get set } + var cookieStorage: WebCookieStorageProtocol? { get set } +} diff --git a/WebParticles/WebParticles/_Presenters/ParticlesWebPresenter.swift b/WebParticles/WebParticles/_Presenters/ParticlesWebPresenter.swift new file mode 100644 index 000000000..19cda51e1 --- /dev/null +++ b/WebParticles/WebParticles/_Presenters/ParticlesWebPresenter.swift @@ -0,0 +1,211 @@ +// +// ParticlesWebPresenter.swift +// WebParticles +// +// Created by Qiang Huang on 10/1/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import PlatformRouting +import RoutingKit +import SDWebImage +import UIToolkits +import Utilities +import WebKit + +public typealias SharingHandler = ([Any]?) -> Void + +open class ParticlesWebPresenter: NSObject, WKNavigationDelegate, UIScrollViewDelegate { + @objc public dynamic var title: String? + @objc public dynamic var url: URL? + @IBInspectable var navigateToSafari: Bool = false + + @IBInspectable open var domain: String? { + didSet { + if domain != oldValue { + webview?.domain = domain + } + } + } + + open var sharingImageUrl: String? + + @IBOutlet var goBackButton: UIBarButtonItem? { + didSet { + if goBackButton !== oldValue { + oldValue?.removeTarget() + goBackButton?.addTarget(self, action: #selector(goBack(_:))) + } + } + } + + @IBOutlet var goForwadButton: UIBarButtonItem? { + didSet { + if goForwadButton !== oldValue { + oldValue?.removeTarget() + goForwadButton?.addTarget(self, action: #selector(goForward(_:))) + } + } + } + + @IBOutlet var reloadButton: UIBarButtonItem? { + didSet { + if reloadButton !== oldValue { + oldValue?.removeTarget() + reloadButton?.addTarget(self, action: #selector(reload(_:))) + } + } + } + + @IBOutlet @objc open dynamic var webview: ParticlesWebView? { + didSet { + webview?.domain = domain + changeObservation(from: oldValue, to: webview, keyPath: #keyPath(ParticlesWebView.title)) { [weak self] _, _, _, _ in + if let self = self { + self.title = self.webview?.title + } + } + changeObservation(from: oldValue, to: webview, keyPath: #keyPath(ParticlesWebView.url)) { [weak self] _, _, _, _ in + if let self = self { + self.url = self.webview?.url + } + } + changeObservation(from: oldValue, to: webview, keyPath: "_webView") { [weak self] _, _, _, _ in + if let self = self { + self.webview?._webView?.scrollView.delegate = self + self.webview?._webView?.navigationDelegate = self + } + } + } + } + + open var htmlString: String? + + open var urlRequest: URLRequest? { + didSet { + if urlRequest != oldValue { + isUrlLoaded = false + reachedEnd = false + navigate() + } + } + } + + @objc open dynamic var isUrlLoaded: Bool = false + @objc open dynamic var reachedEnd: Bool = false + + open func prepare(completion: @escaping WebCookieCompletion) { + completion() + } + + open func navigate() { + prepare { [weak self] in + self?.loadWeb() + } + } + + open func loadWeb() { + if let webview = webview { + if let htmlString = htmlString { + webview.load(htmlString: htmlString, baseUrl: nil) + } else if let urlRequest = transform(request: urlRequest) { + webview.load(urlRequest) + } + } + } + + open func transform(request: URLRequest?) -> URLRequest? { + return request + } + + @IBAction func goBack(_ sender: Any?) { + webview?.goBack() + } + + @IBAction func goForward(_ sender: Any?) { + webview?.goForward() + } + + @IBAction func reload(_ sender: Any?) { + webview?.reload() + } + + @IBAction open func share(_ sender: Any?) { + sharing { [weak self] items in + self?.share(sender: sender, items: items) + } + } + + open func share(sender: Any?, items: [Any]?) { + if let items = items { + UserInteraction.shared.sender = sender + let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil) + activityVC.excludedActivityTypes = [ + UIActivity.ActivityType.airDrop, + UIActivity.ActivityType.addToReadingList, + ] + + activityVC.popoverPresentationController?.sourceView = UserInteraction.shared.sender as? UIView + activityVC.popoverPresentationController?.barButtonItem = UserInteraction.shared.sender as? UIBarButtonItem + ViewControllerStack.shared?.topmost()?.present(activityVC, animated: true, completion: nil) + } + } + + open func sharing(share: @escaping SharingHandler) { + if let url = urlRequest?.url { + if let sharingImageUrl = sharingImageUrl, let imageUrl = URL(string: sharingImageUrl) { + SDWebImageManager.shared.loadImage(with: imageUrl, options: .retryFailed, progress: nil) { [weak self] image, _, _, _, _, _ in + self?.sharing(url: url, title: self?.webview?.title, image: image, share: share) + } + } else { + sharing(url: url, title: webview?.title, image: nil, share: share) + } + } else { + share(nil) + } + } + + open func sharing(url: URL?, title: String?, image: UIImage?, share: @escaping SharingHandler) { + if let url = url { + var toShare = [Any]() + if let title = title { + toShare.append(title) + } + if let image = image { + toShare.append(image) + } + toShare.append(url) + share(toShare) + } else { + share(nil) + } + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + if isUrlLoaded { + if scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height) { + reachedEnd = true + } else if scrollView.contentOffset.y < scrollView.contentSize.height { + reachedEnd = false + } + } + } + + public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + isUrlLoaded = true + } + + public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if navigateToSafari && isUrlLoaded { + if let url = navigationAction.request.url { + URLHandler.shared?.open(url, completionHandler: { _ in + decisionHandler(.cancel) + }) + } else { + decisionHandler(.cancel) + } + } else { + decisionHandler(.allow) + } + } +} diff --git a/WebParticles/WebParticles/_ViewControllers/ParticlesWebViewController.swift b/WebParticles/WebParticles/_ViewControllers/ParticlesWebViewController.swift new file mode 100644 index 000000000..ad9576319 --- /dev/null +++ b/WebParticles/WebParticles/_ViewControllers/ParticlesWebViewController.swift @@ -0,0 +1,194 @@ +// +// ParticlesWebViewController.swift +// WebParticles +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import PlatformRouting +import RoutingKit +import UIToolkits +import WebKit + +open class ParticlesWebViewController: NavigableViewController, WKNavigationDelegate { + @IBOutlet open var presenter: ParticlesWebPresenter? { + didSet { + didSetPresenter(oldValue: oldValue) + } + } + + @IBInspectable open var domain: String? { + didSet { + if domain != oldValue { + presenter?.domain = domain + } + } + } + + @IBInspectable open var path: String? { + didSet { + if path != oldValue { + paths = path?.components(separatedBy: ",") + } + } + } + + private var paths: [String]? + + private var shown: Bool = false + private var previousPath: String? + + @IBOutlet var doneButton: UIBarButtonItem? { + didSet { + if doneButton !== oldValue { + oldValue?.removeTarget() + doneButton?.addTarget(self, action: #selector(dismiss(_:))) + } + } + } + + @IBOutlet var shareButton: UIBarButtonItem? { + didSet { + if shareButton !== oldValue { + oldValue?.removeTarget() + shareButton?.addTarget(self, action: #selector(share(_:))) + } + } + } + + open var htmlString: String? { + didSet { + if htmlString != oldValue { + presenter?.htmlString = htmlString + } + } + } + + open var urlRequest: URLRequest? { + didSet { + if urlRequest != oldValue { + if isViewLoaded { + presenter?.urlRequest = urlRequest + } + } + } + } + + open func setupWebview() { + } + + open func didSetPresenter(oldValue: ParticlesWebPresenter?) { + changeObservation(from: oldValue, to: presenter, keyPath: #keyPath(ParticlesWebPresenter.webview)) { [weak self] _, _, _, _ in + self?.setupWebview() + } + changeObservation(from: oldValue, to: presenter, keyPath: #keyPath(ParticlesWebPresenter.title)) { [weak self] _, _, _, _ in + self?.updateTitle() + } + changeObservation(from: oldValue, to: presenter, keyPath: #keyPath(ParticlesWebPresenter.url)) { [weak self] _, _, _, _ in + self?.updateUrl() + } + if presenter !== oldValue { + presenter?.domain = domain + presenter?.htmlString = htmlString + presenter?.urlRequest = urlRequest + } + } + + override open func arrive(to request: RoutingRequest?, animated: Bool) -> Bool { + if let request = request { + let path = request.path ?? "/" + var pass = path == previousPath + if !pass { + if previousPath == nil { + if let paths = paths { + pass = paths.contains(path) + } else { + pass = true + } + } + } + if pass { + urlRequest = urlRequest(from: request) + if urlRequest != nil { + htmlString = htmlString(from: request) + previousPath = request.path + return true + } + } + } + return false + } + + override open func viewDidLoad() { + super.viewDidLoad() + navigationItem.title = presenter?.title + } + + override open func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if !shown { + var buttons = [UIBarButtonItem]() + if let doneButton = doneButton, presenting() { + buttons.append(doneButton) + } + if let shareButton = shareButton { + buttons.append(shareButton) + } + navigationItem.rightBarButtonItems = buttons + presenter?.urlRequest = urlRequest + shown = true + } else { +// webview?.reload() + } + } + + open func htmlString(from routingRequest: RoutingRequest?) -> String? { + if let path = routingRequest?.path { + let bundlePath = Bundle.main.bundlePath + let webPath = bundlePath.stringByAppendingPathComponent(path: "_Web") + let filePath = webPath.stringByAppendingPathComponent(path: path) + return try? String(contentsOfFile: filePath) + } + return nil + } + + open func urlRequest(from routingRequest: RoutingRequest?) -> URLRequest? { + if let routingRequest = routingRequest { + if let urlString = parser.asString(routingRequest.params?["url"]), let url = URL(string: urlString) { + return URLRequest(url: url) + } else if let scheme = routingRequest.scheme, let host = routingRequest.host { + let path = routingRequest.path ?? "/" + var urlComponents = URLComponents() + urlComponents.scheme = scheme + urlComponents.host = host + urlComponents.path = path + if let params = routingRequest.params { + var queryItems = [URLQueryItem]() + for (key, value) in params { + let queryItem = URLQueryItem(name: key, value: parser.asString(value)) + queryItems.append(queryItem) + } + if queryItems.count > 0 { + urlComponents.queryItems = queryItems + } + } + if let url = urlComponents.url { + return URLRequest(url: url) + } + } + } + return nil + } + + open func updateTitle() { + navigationItem.title = presenter?.title + } + + open func updateUrl() { + } + + @IBAction open func share(_ sender: Any?) { + presenter?.share(sender) + } +} diff --git a/WebParticles/WebParticles/_WebView/ParticlesWebView.swift b/WebParticles/WebParticles/_WebView/ParticlesWebView.swift new file mode 100644 index 000000000..3d56cc6cc --- /dev/null +++ b/WebParticles/WebParticles/_WebView/ParticlesWebView.swift @@ -0,0 +1,247 @@ +// +// ParticlesWebView.swift +// WebParticles +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import ParticlesKit +import UIToolkits +import Utilities +import WebKit + +@objc open class ParticlesWebView: UIView { + public static var initialized: Bool = false + + public static func setup(urlString: String?) { + if !initialized { + if let view = ViewControllerStack.shared?.root()?.view { + let webview = WKWebView(frame: view.bounds) + webview.isHidden = true + view.addSubview(webview) + if let urlString = urlString, let url = URL(string: urlString) { + webview.load(URLRequest(url: url)) + } + ParticlesWebView.initialized = true + } + } + } + + public var cookieInjections: [WebApiRequestInjectionProtocol]? + + @objc public dynamic var canGoBack: Bool = false + @objc public dynamic var canGoForward: Bool = false + @objc public dynamic var title: String? + @objc public dynamic var url: URL? + + @IBInspectable var domain: String? { + didSet { + if domain != oldValue { + if let domain = domain { + cookieDomain = WebCookieDomain(domain: domain, userAgent: UserAgentProvider.shared?.userAgent()) + } else { + cookieDomain = nil + } + } + } + } + + open var cookieDomain: WebCookieDomain? { + didSet { + if cookieDomain !== oldValue { + _webView = nil + } +// changeObservation(from: oldValue, to: cookieDomain, keyPath: #keyPath(WebCookieDomain.location)) { [weak self] _, _, _, _ in +// self?.locationChanged() +// } + } + } + + @objc open dynamic var _webView: WKWebView? { + didSet { + changeObservation(from: oldValue, to: _webView, keyPath: #keyPath(WKWebView.canGoBack)) { [weak self] _, _, _, _ in + if let self = self { + self.canGoBack = self._webView?.canGoBack ?? false + } + } + changeObservation(from: oldValue, to: _webView, keyPath: #keyPath(WKWebView.canGoForward)) { [weak self] _, _, _, _ in + if let self = self { + self.canGoForward = self._webView?.canGoForward ?? false + } + } + changeObservation(from: oldValue, to: _webView, keyPath: #keyPath(WKWebView.title)) { [weak self] _, _, _, _ in + if let self = self { + self.title = self._webView?.title + } + } + changeObservation(from: oldValue, to: _webView, keyPath: #keyPath(WKWebView.url)) { [weak self] _, _, _, _ in + if let self = self { + self.url = self._webView?.url + } + } + } + } + + open var webView: WKWebView? { + if _webView == nil { + let configuration = cookieDomain?.configuration ?? WKWebViewConfiguration() + let view = WKWebView(frame: self.bounds, configuration: configuration) + view.autoresizingMask = [.flexibleHeight, .flexibleWidth] + view.allowsBackForwardNavigationGestures = true + view.backgroundColor = backgroundColor + + self.addSubview(view) + + leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true + trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true + topAnchor.constraint(equalTo: view.topAnchor).isActive = true + bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + _webView = view + } + return _webView + } + + open func set(cookies: [WebCookieProtocol], completion: WebCookieCompletion?) { + set(cookies: cookies, index: 0, completion: completion) + } + + open func set(cookies: [WebCookieProtocol], index: Int, completion: WebCookieCompletion?) { + if index >= cookies.count { + completion?() + } else { + let cookie = cookies[index] + if let storage = cookieDomain?.cookieStorage { + storage.set(cookie: cookie) { [weak self] in + self?.set(cookies: cookies, index: index + 1, completion: completion) + } + } else { + completion?() + } + } + } + + open func navigate(to path: String?) { + if let path = path, let url = URL(string: path) { + let request = URLRequest(url: url) + load(request) + } + } + + open func load(_ request: URLRequest) { + _ = webView + prepare(request: request) { [weak self] modified in + self?.webView?.load(modified) + } + } + + open func prepare(request: URLRequest, completion: @escaping (_: URLRequest) -> Void) { + if let path = request.url?.absoluteString { + injectCookies(request: request) { [weak self] in + if let self = self { + if let cookieStorage = self.cookieDomain?.cookieStorage { + cookieStorage.cookies(path: path, secure: false, completion: { cookies in + if let cookies = cookies { + var modified = request + modified.allHTTPHeaderFields = HTTPCookie.requestHeaderFields(with: cookies) + + completion(modified) + } else { + completion(request) + } + }) + } else { + completion(request) + } + } + } + } else { + completion(request) + } + } + + open func injectCookies(request: URLRequest, completion: @escaping () -> Void) { + if let domain = request.url?.host { + injectCookies(index: 0, cookies: [String: String]()) { [weak self] cookies in + if let self = self, cookies.count > 0 { + var webCookies: [WebCookie] = [WebCookie]() + for (key, value) in cookies { + webCookies.append(self.cookie(domain: domain, name: key, value: value)) + } + self.set(cookies: webCookies) { + completion() + } + } else { + completion() + } + } + } else { + completion() + } + } + + open func cookie(domain: String, name: String, value: String) -> WebCookie { + let expiration = Date().add(day: 20) + let cookie = WebCookie(domain: domain, isSecure: false, name: name, value: value, expires: expiration) + cookie.isSessionOnly = false + return cookie + } + + open func injectCookies(index: Int, cookies: [String: String], completion: @escaping ([String: String]) -> Void) { + if let cookieInjections = cookieInjections, cookieInjections.count > index { + let injection = cookieInjections[index] + injection.cookies { injectedCookies in + if let injectedCookies = injectedCookies { + completion(cookies.merging(injectedCookies, uniquingKeysWith: { (_, value2) -> String in + value2 + })) + } else { + completion(cookies) + } + } + } else { + completion(cookies) + } + } + + open func load(htmlString: String, baseUrl: URL?) { + webView?.loadHTMLString(htmlString, baseURL: baseUrl) + } + +// open func locationChanged() { +// if let location = cookieDomain?.location { +// let script = "getLocation(\(location.coordinate.latitude) ,\(location.coordinate.longitude))" +// webView?.evaluateJavaScript(script) +// } +// } + + open func goBack() { + webView?.goBack() + } + + open func goForward() { + webView?.goForward() + } + + open func reload() { + webView?.reload() + } +} + +extension ParticlesWebView: WKNavigationDelegate { +} + +extension ParticlesWebView: WKUIDelegate { + open func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { + let popup = WKWebView(frame: webView.frame, configuration: webView.configuration) + popup.autoresizingMask = [.flexibleWidth, .flexibleHeight] + popup.navigationDelegate = self + popup.uiDelegate = self + webView.addSubview(popup) + return popup + } + + open func webViewDidClose(_ webView: WKWebView) { + webView.removeFromSuperview() + } +} diff --git a/WebParticles/WebParticlesTests/Info.plist b/WebParticles/WebParticlesTests/Info.plist new file mode 100644 index 000000000..64d65ca49 --- /dev/null +++ b/WebParticles/WebParticlesTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/WebParticles/WebParticlesTests/WebParticlesTests.swift b/WebParticles/WebParticlesTests/WebParticlesTests.swift new file mode 100644 index 000000000..67c704383 --- /dev/null +++ b/WebParticles/WebParticlesTests/WebParticlesTests.swift @@ -0,0 +1,34 @@ +// +// WebParticlesTests.swift +// WebParticlesTests +// +// Created by Qiang Huang on 7/11/20. +// Copyright © 2020 dYdX. All rights reserved. +// + +import XCTest +@testable import WebParticles + +class WebParticlesTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/docs/QR_app_store_submission.png b/docs/QR_app_store_submission.png new file mode 100644 index 000000000..74fa4b83e Binary files /dev/null and b/docs/QR_app_store_submission.png differ diff --git a/docs/branching_strategy.md b/docs/branching_strategy.md new file mode 100644 index 000000000..fdbf549d0 --- /dev/null +++ b/docs/branching_strategy.md @@ -0,0 +1,158 @@ +# Project Branching and Hotfix Strategy Guide + +This document outlines the branching strategy our project adopts, accommodating regular development, releases, and hotfixes. Following this strategy ensures an organized development process and stable production code. + +## 1. General Branching Strategy + +Our branching method involves several branches serving distinct roles in the code changes' lifecycle. Here's what our branch structure looks like: + +### 1.1 `main` Branch + +- Stores official release history. +- Every commit represents a production-ready state. + +### 1.2 `develop` Branch + +- Serves as a pre-production staging area. +- It's where code merges before it's ready for production. + +### 1.3 Feature Branches + +- Branch off from `develop` and integrate back into it when the feature is complete. +- Used for single feature development or improvements. + +### 1.4 Release Branches + +- Branch off from `develop` when it reaches a production-ready state. +- These branches are long-lived and serve for creating a release history, enabling referencing or hotfixing in the future. + +**Workflow Summary:** + +1. Regular work (features and non-critical fixes) is done in feature branches. +2. These are merged into `develop` upon completion. +3. When `develop` is ready for production, a new `release/...` branch is created. +4. Release branches may receive minor polishing and bug fixing. +5. When finalized, the `release` branch merges into `main` and is tagged with a version number if commits were made in step 4. The release branch also merges back any changes into `develop`. + +## 2. Hotfix Strategy + +Hotfixes address critical production issues, requiring immediate resolution outside the regular cycle. + +### 2.1 Hotfix Branches + +- These are created from the appropriate `release/...` branch, providing a controlled area to fix the issue. +- These are no different than a normal release branch aside from being based on a previous `release/...` branch instead of `main` +- After testing, hotfixes merge into both `main` and `develop` to update the production version and include fixes in the upcoming releases. + +### Hotfix Workflow + +Let's say that an issue needing a hotfix was discovered in released version `1.0.1` + +1. Locate the `release/1.0.1` branch. +2. Branch off `release/1.0.1` into a new hotfix `release/1.0.2` branch. + ```sh + git checkout release/1.0.1 + git pull + git checkout -b release/1.0.2 + ``` +3. Implement and test the fix rigorously on the hotfix branch. +4. If hotfix release version is **greater** than latest main version tag, follow workflow A. Otherwise follow workflow B. + +#### **Workflow A** +1. merge the hotfix branch into `main` + ```sh + git checkout main + git merge release/1.0.2 + ``` +2. Tag this new release merge commit on `main` with an updated version number. + ```sh + git tag -a v(new_version) -m "v1.0.2" + git push origin --tags + ``` +3. Merge the hotfix into `develop` to ensure it's part of future releases. + ```sh + git checkout develop + git merge release/1.0.2 + ``` + +#### **Workflow B** +1. Branch off `release/` into another hotfix `release/` branch. + ```sh + # e.g. LATEST is 2.1.1 + git checkout release/ + git pull + # LATEST_PATCH_INCREMENTED would then be 2.1.2 + git checkout -b release/ + ``` +2. Merge the `release/1.0.2` hotfix changes into `release/` + ```sh + git checkout release/ + git merge release/1.0.2 + ``` +3. merge the `release/` hotfix branch into `main` + ```sh + git checkout main + git merge release/ + ``` +4. Tag this new release merge commit on `main` with an updated version number. + ```sh + git tag -a v(new_version) -m "v" + git push origin --tags + ``` +5. Merge the hotfix into `develop` to ensure it's part of future releases. + ```sh + git checkout develop + git merge release/ + ``` + + + +## 3. Example +The following branching history visualization depicts a project which: +1. Released 1.0.0 based off the latest develop at the time +2. Released 1.0.1 based off 1.0.0 for a hotfix +3. Released 1.1.0 based off the latest develop at the time + + +This example can be recreated with [mermaid.live's tool](https://mermaid.live/) and the following code. +``` +gitGraph + commit id:"1.0.0" + branch "develop" + commit id:"commit_a" + commit id:"commit_b" + branch release/1.0.0 + checkout release/1.0.0 + commit id:"commit_b (same HEAD)" + checkout develop + commit id:"commit_c" + commit id:"commit_d" + checkout main + merge release/1.0.0 tag:"v1.0.0" + checkout release/1.0.0 + branch release/1.0.1 + commit id:"commit_b (same HEAD) " + commit id:"commit_e (polish)" + checkout "develop" + merge release/1.0.1 + checkout main + merge release/1.0.1 tag: "v1.0.1" + checkout develop + commit id: "commit_f" + commit id: "commit_g" + commit id: "commit_h" + branch release/1.1.0 + checkout release/1.1.0 + commit id:"commit_h (same head)" + checkout main + merge release/1.1.0 tag: "v1.1.0" +``` + +## 4. Release Management + +The presence of release branches adds an extra layer of stability, as they remain available for any future needs for referencing or hotfixing that specific release. + +## 5. Conclusion + +This branching strategy and hotfix procedure ensure a robust framework for continuous development, stable production releases, and efficient deployment of critical fixes. It emphasizes the importance of team collaboration, communication, and a structured approach to managing code lifecycles. + diff --git a/docs/forced_update.md b/docs/forced_update.md new file mode 100644 index 000000000..72b21f78b --- /dev/null +++ b/docs/forced_update.md @@ -0,0 +1,62 @@ +# Forced Update Guide + +This document outlines how to force app users to update to a particular version. + +## 1. When to force an update + +When Blockchain contract changes, FE app may need to be updated to be compatible. While web apps can be updated in sync with the contracts, native app users may not always update to the latest version. +
+
+Forced update is a mechanism to make sure the native app is compatible with the contracts, and Indexer endpoints. + +## 2. Forced update strategy + +Remote configuration is used to inform the app the minimum build number required. + +### 2.1 Build number + +Each app deployment has a build number, which is automatically incremented at build time. When the remote configuration contains a higher build number than the running app, app shows an UI to force the user to update the app. + +### 2.2 Update URL + +An URL is provided in the remote configuration. This URL should lead to the App Store to either + +#### 2.2.1 + +Update the existing app + +#### 2.2.2 + +Download a different app. This is a mechanism to release completely new app and prompt users of older app to migrate to the new app. + +## 3. Remote Configuration + +The remote configuration resides inside the Environment payload in the web app deployment, which should reside in **\public\configs\env.json** + +Having the endpoint to the deployed web app is a necessary step to configure the native app deployment. + +Different environments may have different app requirements. This enables the native apps to be deployed and tested with testnets before production network is deployed. + +## 4. Sample Payload + +In each environment, there is an optional **apps** payload. + +``` +"apps": { + "ios": { + "minimalVersion": "1.0", + "build":40000, + "url": "https://apps.apple.com/app/dydx/id1234567890" + } + } + ``` + + +**ios** and **android** is used to identify the requirments for iOS or Android apps. + +**minimalVersion** used by the app to display required version. It is used for displaying only. + +**build** is the minimum build number to be compatible with the environment. + +**url** is the URL to the app on the App Store or Google Play Store. + diff --git a/docs/pull_request_template.md b/docs/pull_request_template.md new file mode 100644 index 000000000..93b21295e --- /dev/null +++ b/docs/pull_request_template.md @@ -0,0 +1,42 @@ +## Links (dYdX Internal Use Only) +Linear Ticket: _[Provide link to Linear ticket issue]_ + +Figma Design: _[Provide link to Figma design here if applicable]_ + + + + +
+ +## Description / Intuition +_Provide a clear and concise description of the fix(es) or change(s) you made. If possible, give some intuition behind why the changes were made or how the solution was achieved._ + + + + +
+ +## Before/After Screenshots or Videos + +| Before | After | +|--------|-------| +| | | +| | | +| | | +| | | +| | | +|

+ + + +
+

+ +# Amplitude-Swift + +This is Amplitude's latest version of the iOS SDK, covering iOS/tvOS/macOS/watchOS. + +## Installation and Quick Start + +* For using the SDK, please visit our :100:[Developer Center](https://www.docs.developers.amplitude.com/data/sdks/ios-swift/). +* For developing the SDK, please visit our [CONTRIBUTING.md](https://github.com/amplitude/Amplitude-swift/blob/main/CONTRIBUTING.md). + +## Changelog + +Click [here](https://github.com/amplitude/Amplitude-swift/blob/main/CHANGELOG.md) to view the iOS SDK Changelog. + +## Need Help? + +If you have any issues using our SDK, feel free to [create a GitHub issue](https://github.com/amplitude/Amplitude-Swift/issues/new) or submit a request on [Amplitude Help](https://help.amplitude.com/hc/en-us/requests/new). diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Amplitude.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Amplitude.swift new file mode 100644 index 000000000..62667d8ea --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Amplitude.swift @@ -0,0 +1,445 @@ +import Foundation + +public class Amplitude { + public private(set) var configuration: Configuration + private var inForeground = false + + var sessionId: Int64 { + sessions.sessionId + } + + var state: State = State() + var contextPlugin: ContextPlugin + + lazy var storage: any Storage = { + return self.configuration.storageProvider + }() + + lazy var identifyStorage: any Storage = { + return self.configuration.identifyStorageProvider + }() + + lazy var timeline: Timeline = { + return Timeline() + }() + + lazy var sessions: Sessions = { + return Sessions(amplitude: self) + }() + + public lazy var logger: (any Logger)? = { + return self.configuration.loggerProvider + }() + + let trackingQueue = DispatchQueue(label: "com.amplitude.analytics", target: .global(qos: .utility)) + + public init( + configuration: Configuration + ) { + self.configuration = configuration + + let contextPlugin = ContextPlugin() + self.contextPlugin = contextPlugin + + migrateApiKeyStorages() + migrateDefaultInstanceStorages() + if configuration.migrateLegacyData && getStorageVersion() < .API_KEY_AND_INSTANCE_NAME && isSandboxEnabled() { + RemnantDataMigration(self).execute() + } + migrateInstanceOnlyStorages() + + if let deviceId: String? = configuration.storageProvider.read(key: .DEVICE_ID) { + state.deviceId = deviceId + } + if let userId: String? = configuration.storageProvider.read(key: .USER_ID) { + state.userId = userId + } + + if configuration.offline != NetworkConnectivityCheckerPlugin.Disabled, + VendorSystem.current.networkConnectivityCheckingEnabled { + _ = add(plugin: NetworkConnectivityCheckerPlugin()) + } + // required plugin for specific platform, only has lifecyclePlugin now + if let requiredPlugin = VendorSystem.current.requiredPlugin { + _ = add(plugin: requiredPlugin) + } + _ = add(plugin: contextPlugin) + _ = add(plugin: AnalyticsConnectorPlugin()) + _ = add(plugin: AnalyticsConnectorIdentityPlugin()) + _ = add(plugin: AmplitudeDestinationPlugin()) + } + + convenience init(apiKey: String, configuration: Configuration) { + configuration.apiKey = apiKey + self.init(configuration: configuration) + } + + @discardableResult + public func track(event: BaseEvent, options: EventOptions? = nil, callback: EventCallback? = nil) -> Amplitude { + if options != nil { + event.mergeEventOptions(eventOptions: options!) + } + if callback != nil { + event.callback = callback + } + process(event: event) + return self + } + + @discardableResult + public func track(eventType: String, eventProperties: [String: Any]? = nil, options: EventOptions? = nil) -> Amplitude { + let event = BaseEvent(eventType: eventType) + event.eventProperties = eventProperties + if let eventOptions = options { + event.mergeEventOptions(eventOptions: eventOptions) + } + process(event: event) + return self + } + + @discardableResult + @available(*, deprecated, message: "use 'track' instead") + public func logEvent(event: BaseEvent) -> Amplitude { + return track(event: event) + } + + @discardableResult + public func identify(userProperties: [String: Any]?, options: EventOptions? = nil) -> Amplitude { + return identify(identify: convertPropertiesToIdentify(userProperties: userProperties), options: options) + } + + @discardableResult + public func identify(identify: Identify, options: EventOptions? = nil) -> Amplitude { + let event = IdentifyEvent() + event.userProperties = identify.properties as [String: Any] + if let eventOptions = options { + event.mergeEventOptions(eventOptions: eventOptions) + if eventOptions.userId != nil { + setUserId(userId: eventOptions.userId) + } + if eventOptions.deviceId != nil { + setDeviceId(deviceId: eventOptions.deviceId) + } + } + process(event: event) + return self + } + + private func convertPropertiesToIdentify(userProperties: [String: Any]?) -> Identify { + let identify = Identify() + userProperties?.forEach { key, value in + _ = identify.set(property: key, value: value) + } + return identify + } + + @discardableResult + public func groupIdentify( + groupType: String, + groupName: String, + groupProperties: [String: Any]?, + options: EventOptions? = nil + ) -> Amplitude { + return groupIdentify( + groupType: groupType, + groupName: groupName, + identify: convertPropertiesToIdentify(userProperties: groupProperties), + options: options + ) + } + + @discardableResult + public func groupIdentify( + groupType: String, + groupName: String, + identify: Identify, + options: EventOptions? = nil + ) -> Amplitude { + let event = GroupIdentifyEvent() + var groups = [String: Any]() + groups[groupType] = groupName + event.groups = groups + event.groupProperties = identify.properties + if let eventOptions = options { + event.mergeEventOptions(eventOptions: eventOptions) + } + process(event: event) + return self + } + + @discardableResult + public func setGroup( + groupType: String, + groupName: String, + options: EventOptions? = nil + ) -> Amplitude { + let identify = Identify().set(property: groupType, value: groupName) + let event = IdentifyEvent() + event.groups = [groupType: groupName] + event.userProperties = identify.properties + track(event: event, options: options) + return self + } + + @discardableResult + public func setGroup( + groupType: String, + groupName: [String], + options: EventOptions? = nil + ) -> Amplitude { + let identify = Identify().set(property: groupType, value: groupName) + let event = IdentifyEvent() + event.groups = [groupType: groupName] + event.userProperties = identify.properties + track(event: event, options: options) + return self + } + + @discardableResult + @available(*, deprecated, message: "use 'revenue' instead") + public func logRevenue() -> Amplitude { + return self + } + + @discardableResult + public func revenue( + revenue: Revenue, + options: EventOptions? = nil + ) -> Amplitude { + guard revenue.isValid() else { + logger?.warn(message: "Invalid revenue object, missing required fields") + return self + } + + let event = revenue.toRevenueEvent() + if let eventOptions = options { + event.mergeEventOptions(eventOptions: eventOptions) + } + _ = self.revenue(event: event) + return self + } + + @discardableResult + public func revenue(event: RevenueEvent) -> Amplitude { + process(event: event) + return self + } + + @discardableResult + public func add(plugin: Plugin) -> Amplitude { + plugin.setup(amplitude: self) + if let _plugin = plugin as? ObservePlugin { + state.add(plugin: _plugin) + } else { + timeline.add(plugin: plugin) + } + return self + } + + @discardableResult + public func remove(plugin: Plugin) -> Amplitude { + if let _plugin = plugin as? ObservePlugin { + state.remove(plugin: _plugin) + } else { + timeline.remove(plugin: plugin) + } + return self + } + + @discardableResult + public func flush() -> Amplitude { + trackingQueue.async { + self.timeline.apply { plugin in + if let _plugin = plugin as? EventPlugin { + _plugin.flush() + } + } + } + return self + } + + @discardableResult + public func setUserId(userId: String?) -> Amplitude { + try? storage.write(key: .USER_ID, value: userId) + state.userId = userId + return self + } + + @discardableResult + public func setDeviceId(deviceId: String?) -> Amplitude { + try? storage.write(key: .DEVICE_ID, value: deviceId) + state.deviceId = deviceId + return self + } + + public func getUserId() -> String? { + return state.userId + } + + public func getDeviceId() -> String? { + return state.deviceId + } + + public func getSessionId() -> Int64 { + return sessions.sessionId + } + + @discardableResult + public func setSessionId(timestamp: Int64) -> Amplitude { + trackingQueue.async { [self] in + let sessionEvents: [BaseEvent] + if timestamp >= 0 { + sessionEvents = self.sessions.startNewSession(timestamp: timestamp) + } else { + sessionEvents = self.sessions.endCurrentSession() + } + self.sessions.assignEventId(events: sessionEvents).forEach { e in + self.timeline.processEvent(event: e) + } + } + return self + } + + @discardableResult + public func setSessionId(date: Date) -> Amplitude { + let timestamp = Int64(date.timeIntervalSince1970 * 1000) + setSessionId(timestamp: timestamp) + return self + } + + @discardableResult + public func reset() -> Amplitude { + setUserId(userId: nil) + contextPlugin.initializeDeviceId(forceReset: true) + return self + } + + public func apply(closure: (Plugin) -> Void) { + timeline.apply(closure) + } + + private func process(event: BaseEvent) { + if configuration.optOut { + logger?.log(message: "Skip event based on opt out configuration") + return + } + let inForeground = inForeground + trackingQueue.async { [self] in + let events = self.sessions.processEvent(event: event, inForeground: inForeground) + events.forEach { e in self.timeline.processEvent(event: e) } + } + } + + func onEnterForeground(timestamp: Int64) { + inForeground = true + let dummySessionStartEvent = BaseEvent( + timestamp: timestamp, + eventType: Constants.AMP_SESSION_START_EVENT + ) + trackingQueue.async { [self] in + // set inForeground to false to represent state before event was fired + let events = self.sessions.processEvent(event: dummySessionStartEvent, inForeground: false) + events.forEach { e in self.timeline.processEvent(event: e) } + } + } + + func onExitForeground(timestamp: Int64) { + inForeground = false + trackingQueue.async { [self] in + self.sessions.lastEventTime = timestamp + } + if configuration.flushEventsOnClose == true { + flush() + } + } + + private func getStorageVersion() -> PersistentStorageVersion { + let storageVersionInt: Int? = configuration.storageProvider.read(key: .STORAGE_VERSION) + let storageVersion: PersistentStorageVersion = (storageVersionInt == nil) ? PersistentStorageVersion.NO_VERSION : PersistentStorageVersion(rawValue: storageVersionInt!)! + return storageVersion + } + + private func migrateApiKeyStorages() { + if getStorageVersion() >= PersistentStorageVersion.API_KEY { + return + } + configuration.loggerProvider.debug(message: "Running migrateApiKeyStorages") + if let persistentStorage = configuration.storageProvider as? PersistentStorage { + let apiKeyStorage = PersistentStorage(storagePrefix: "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-\(configuration.apiKey)", logger: self.logger, diagonostics: configuration.diagonostics) + StoragePrefixMigration(source: apiKeyStorage, destination: persistentStorage, logger: logger).execute() + } + + if let persistentIdentifyStorage = configuration.identifyStorageProvider as? PersistentStorage { + let apiKeyIdentifyStorage = PersistentStorage(storagePrefix: "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-identify-\(configuration.apiKey)", logger: self.logger, diagonostics: configuration.diagonostics) + StoragePrefixMigration(source: apiKeyIdentifyStorage, destination: persistentIdentifyStorage, logger: logger).execute() + } + } + + private func migrateDefaultInstanceStorages() { + if getStorageVersion() >= PersistentStorageVersion.INSTANCE_NAME || + configuration.instanceName != Constants.Configuration.DEFAULT_INSTANCE { + return + } + configuration.loggerProvider.debug(message: "Running migrateDefaultInstanceStorages") + let legacyDefaultInstanceName = "default_instance" + if let persistentStorage = configuration.storageProvider as? PersistentStorage { + let legacyStorage = PersistentStorage(storagePrefix: "storage-\(legacyDefaultInstanceName)", logger: self.logger, diagonostics: configuration.diagonostics) + StoragePrefixMigration(source: legacyStorage, destination: persistentStorage, logger: logger).execute() + } + + if let persistentIdentifyStorage = configuration.identifyStorageProvider as? PersistentStorage { + let legacyIdentifyStorage = PersistentStorage(storagePrefix: "identify-\(legacyDefaultInstanceName)", logger: self.logger, diagonostics: configuration.diagonostics) + StoragePrefixMigration(source: legacyIdentifyStorage, destination: persistentIdentifyStorage, logger: logger).execute() + } + } + + internal func migrateInstanceOnlyStorages() { + if getStorageVersion() >= .API_KEY_AND_INSTANCE_NAME { + configuration.loggerProvider.debug(message: "Skipping migrateInstanceOnlyStorages based on STORAGE_VERSION") + return + } + configuration.loggerProvider.debug(message: "Running migrateInstanceOnlyStorages") + + let skipEventMigration = !isSandboxEnabled() + // Only migrate sandboxed apps to avoid potential data pollution + if skipEventMigration { + configuration.loggerProvider.debug(message: "Skipping event migration in non-sandboxed app. Transfering UserDefaults only.") + } + + let instanceName = configuration.getNormalizeInstanceName() + if let persistentStorage = configuration.storageProvider as? PersistentStorage { + let instanceOnlyEventPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-storage-\(instanceName)" + let instanceNameOnlyStorage = PersistentStorage(storagePrefix: instanceOnlyEventPrefix, logger: self.logger, diagonostics: configuration.diagonostics) + StoragePrefixMigration( + source: instanceNameOnlyStorage, + destination: persistentStorage, + logger: logger + ).execute(skipEventFiles: skipEventMigration) + } + + if let persistentIdentifyStorage = configuration.identifyStorageProvider as? PersistentStorage { + let instanceOnlyIdentifyPrefix = "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-identify-\(instanceName)" + let instanceNameOnlyIdentifyStorage = PersistentStorage(storagePrefix: instanceOnlyIdentifyPrefix, logger: self.logger, diagonostics: configuration.diagonostics) + StoragePrefixMigration( + source: instanceNameOnlyIdentifyStorage, + destination: persistentIdentifyStorage, + logger: logger + ).execute(skipEventFiles: skipEventMigration) + } + + do { + // Store the current storage version + try configuration.storageProvider.write( + key: .STORAGE_VERSION, + value: PersistentStorageVersion.API_KEY_AND_INSTANCE_NAME.rawValue as Int + ) + configuration.loggerProvider.debug(message: "Updated STORAGE_VERSION to .API_KEY_AND_INSTANCE_NAME") + } catch { + configuration.loggerProvider.error(message: "Unable to set STORAGE_VERSION in storageProvider during migration") + } + } + + internal func isSandboxEnabled() -> Bool { + return SandboxHelper().isSandboxEnabled() + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/AutocaptureOptions.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/AutocaptureOptions.swift new file mode 100644 index 000000000..cd1133cec --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/AutocaptureOptions.swift @@ -0,0 +1,14 @@ +import Foundation + +public struct AutocaptureOptions: OptionSet { + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public static let sessions = AutocaptureOptions(rawValue: 1 << 0) + public static let appLifecycles = AutocaptureOptions(rawValue: 1 << 1) + public static let screenViews = AutocaptureOptions(rawValue: 1 << 2) + public static let elementInteractions = AutocaptureOptions(rawValue: 1 << 3) +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Configuration.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Configuration.swift new file mode 100644 index 000000000..65c568cb7 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Configuration.swift @@ -0,0 +1,191 @@ +// +// Configuration.swift +// +// +// Created by Marvin Liu on 10/27/22. +// + +import Foundation + +public class Configuration { + public internal(set) var apiKey: String + public var flushQueueSize: Int + public var flushIntervalMillis: Int + public internal(set) var instanceName: String + public var optOut: Bool + public let storageProvider: any Storage + public let identifyStorageProvider: any Storage + public var logLevel: LogLevelEnum + public var loggerProvider: any Logger + public var minIdLength: Int? + public var partnerId: String? + public var callback: EventCallback? + public var flushMaxRetries: Int + public var useBatch: Bool + public var serverZone: ServerZone + public var serverUrl: String? + public var plan: Plan? + public var ingestionMetadata: IngestionMetadata? + public var trackingOptions: TrackingOptions + public var enableCoppaControl: Bool + public var flushEventsOnClose: Bool + public var minTimeBetweenSessionsMillis: Int + public var identifyBatchIntervalMillis: Int + public internal(set) var migrateLegacyData: Bool + @available(*, deprecated, renamed: "autocapture", message: "Please use `autocapture` instead.") + public lazy var defaultTracking: DefaultTrackingOptions = { + DefaultTrackingOptions(delegate: self) + }() { + didSet { + defaultTracking.delegate = self + autocapture = defaultTracking.autocaptureOptions + } + } + public internal(set) var autocapture: AutocaptureOptions + public var offline: Bool? + internal let diagonostics: Diagnostics + + @available(*, deprecated, message: "Please use the `autocapture` parameter instead.") + public convenience init( + apiKey: String, + flushQueueSize: Int = Constants.Configuration.FLUSH_QUEUE_SIZE, + flushIntervalMillis: Int = Constants.Configuration.FLUSH_INTERVAL_MILLIS, + instanceName: String = "", + optOut: Bool = false, + storageProvider: (any Storage)? = nil, + identifyStorageProvider: (any Storage)? = nil, + logLevel: LogLevelEnum = LogLevelEnum.WARN, + loggerProvider: any Logger = ConsoleLogger(), + minIdLength: Int? = nil, + partnerId: String? = nil, + callback: EventCallback? = nil, + flushMaxRetries: Int = Constants.Configuration.FLUSH_MAX_RETRIES, + useBatch: Bool = false, + serverZone: ServerZone = ServerZone.US, + serverUrl: String? = nil, + plan: Plan? = nil, + ingestionMetadata: IngestionMetadata? = nil, + trackingOptions: TrackingOptions = TrackingOptions(), + enableCoppaControl: Bool = false, + flushEventsOnClose: Bool = true, + minTimeBetweenSessionsMillis: Int = Constants.Configuration.MIN_TIME_BETWEEN_SESSIONS_MILLIS, + // `trackingSessionEvents` has been replaced by `defaultTracking.sessions` + defaultTracking: DefaultTrackingOptions, + identifyBatchIntervalMillis: Int = Constants.Configuration.IDENTIFY_BATCH_INTERVAL_MILLIS, + migrateLegacyData: Bool = true, + offline: Bool? = false + ) { + self.init(apiKey: apiKey, + flushQueueSize: flushQueueSize, + flushIntervalMillis: flushIntervalMillis, + instanceName: instanceName, + optOut: optOut, + storageProvider: storageProvider, + identifyStorageProvider: identifyStorageProvider, + logLevel: logLevel, + loggerProvider: loggerProvider, + minIdLength: minIdLength, + partnerId: partnerId, + callback: callback, + flushMaxRetries: flushMaxRetries, + useBatch: useBatch, + serverZone: serverZone, + serverUrl: serverUrl, + plan: plan, + ingestionMetadata: ingestionMetadata, + trackingOptions: trackingOptions, + enableCoppaControl: enableCoppaControl, + flushEventsOnClose: flushEventsOnClose, + minTimeBetweenSessionsMillis: minTimeBetweenSessionsMillis, + autocapture: defaultTracking.autocaptureOptions, + identifyBatchIntervalMillis: identifyBatchIntervalMillis, + migrateLegacyData: migrateLegacyData, + offline: offline) + self.defaultTracking = defaultTracking + } + + public init( + apiKey: String, + flushQueueSize: Int = Constants.Configuration.FLUSH_QUEUE_SIZE, + flushIntervalMillis: Int = Constants.Configuration.FLUSH_INTERVAL_MILLIS, + instanceName: String = "", + optOut: Bool = false, + storageProvider: (any Storage)? = nil, + identifyStorageProvider: (any Storage)? = nil, + logLevel: LogLevelEnum = LogLevelEnum.WARN, + loggerProvider: any Logger = ConsoleLogger(), + minIdLength: Int? = nil, + partnerId: String? = nil, + callback: EventCallback? = nil, + flushMaxRetries: Int = Constants.Configuration.FLUSH_MAX_RETRIES, + useBatch: Bool = false, + serverZone: ServerZone = ServerZone.US, + serverUrl: String? = nil, + plan: Plan? = nil, + ingestionMetadata: IngestionMetadata? = nil, + trackingOptions: TrackingOptions = TrackingOptions(), + enableCoppaControl: Bool = false, + flushEventsOnClose: Bool = true, + minTimeBetweenSessionsMillis: Int = Constants.Configuration.MIN_TIME_BETWEEN_SESSIONS_MILLIS, + // `trackingSessionEvents` has been replaced by `defaultTracking.sessions` + autocapture: AutocaptureOptions = .sessions, + identifyBatchIntervalMillis: Int = Constants.Configuration.IDENTIFY_BATCH_INTERVAL_MILLIS, + migrateLegacyData: Bool = true, + offline: Bool? = false + ) { + let normalizedInstanceName = Configuration.getNormalizeInstanceName(instanceName) + + self.apiKey = apiKey + self.flushQueueSize = flushQueueSize + self.flushIntervalMillis = flushIntervalMillis + self.instanceName = normalizedInstanceName + self.optOut = optOut + self.diagonostics = Diagnostics() + self.logLevel = logLevel + self.loggerProvider = loggerProvider + self.storageProvider = storageProvider + ?? PersistentStorage(storagePrefix: PersistentStorage.getEventStoragePrefix(apiKey, normalizedInstanceName), logger: self.loggerProvider, diagonostics: self.diagonostics) + self.identifyStorageProvider = identifyStorageProvider + ?? PersistentStorage(storagePrefix: PersistentStorage.getIdentifyStoragePrefix(apiKey, normalizedInstanceName), logger: self.loggerProvider, diagonostics: self.diagonostics) + self.minIdLength = minIdLength + self.partnerId = partnerId + self.callback = callback + self.flushMaxRetries = flushMaxRetries + self.useBatch = useBatch + self.serverZone = serverZone + self.serverUrl = serverUrl + self.plan = plan + self.ingestionMetadata = ingestionMetadata + self.trackingOptions = trackingOptions + self.enableCoppaControl = enableCoppaControl + self.flushEventsOnClose = flushEventsOnClose + self.minTimeBetweenSessionsMillis = minTimeBetweenSessionsMillis + self.autocapture = autocapture + self.identifyBatchIntervalMillis = identifyBatchIntervalMillis + self.migrateLegacyData = migrateLegacyData + // Logging is OFF by default + self.loggerProvider.logLevel = logLevel.rawValue + self.offline = offline + } + + func isValid() -> Bool { + return !apiKey.isEmpty && flushQueueSize > 0 && flushIntervalMillis > 0 + && minTimeBetweenSessionsMillis > 0 + && (minIdLength == nil || minIdLength! > 0) + } + + private class func getNormalizeInstanceName(_ instanceName: String) -> String { + return instanceName == "" ? Constants.Configuration.DEFAULT_INSTANCE : instanceName + } + + internal func getNormalizeInstanceName() -> String { + return Configuration.getNormalizeInstanceName(self.instanceName) + } +} + +extension Configuration: DefaultTrackingOptionsDelegate { + @available(*, deprecated) + func didChangeOptions(options: DefaultTrackingOptions) { + autocapture = options.autocaptureOptions + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ConsoleLogger.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ConsoleLogger.swift new file mode 100644 index 000000000..ef499264c --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ConsoleLogger.swift @@ -0,0 +1,45 @@ +// +// ConsoleLogger.swift +// +// +// Created by Marvin Liu on 10/28/22. +// + +import Foundation +import os.log + +public class ConsoleLogger: Logger { + public typealias LogLevel = LogLevelEnum + + public var logLevel: Int + private var logger: OSLog + + public init(logLevel: Int = LogLevelEnum.OFF.rawValue) { + self.logLevel = logLevel + self.logger = OSLog(subsystem: "Amplitude", category: "Logging") + } + + public func error(message: String) { + if logLevel >= LogLevel.ERROR.rawValue { + os_log("Error: %@", log: logger, type: .error, message) + } + } + + public func warn(message: String) { + if logLevel >= LogLevel.WARN.rawValue { + os_log("Warn: %@", log: logger, type: .default, message) + } + } + + public func log(message: String) { + if logLevel >= LogLevel.LOG.rawValue { + os_log("Log: %@", log: logger, type: .info, message) + } + } + + public func debug(message: String) { + if logLevel >= LogLevel.DEBUG.rawValue { + os_log("Debug: %@", log: logger, type: .debug, message) + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Constants.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Constants.swift new file mode 100644 index 000000000..d9dfa6dee --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Constants.swift @@ -0,0 +1,119 @@ +// +// Constants.swift +// +// +// Created by Marvin Liu on 10/27/22. +// + +import Foundation + +@objc(AMPLogLevel) +public enum LogLevelEnum: Int { + case OFF + case ERROR + case WARN + case LOG + case DEBUG +} + +@objc(AMPServerZone) +public enum ServerZone: Int { + case US + case EU + + public typealias RawValue = String + + public var rawValue: RawValue { + switch self { + case .US: + return "US" + case .EU: + return "EU" + } + } + + public init?(rawValue: RawValue) { + switch rawValue { + case "US": + self = .US + case "EU": + self = .EU + default: + return nil + } + } +} + +public struct Constants { + static let SDK_LIBRARY = "amplitude-swift" + static let SDK_VERSION = "1.8.0" + public static let DEFAULT_API_HOST = "https://api2.amplitude.com/2/httpapi" + public static let EU_DEFAULT_API_HOST = "https://api.eu.amplitude.com/2/httpapi" + static let BATCH_API_HOST = "https://api2.amplitude.com/batch" + static let EU_BATCH_API_HOST = "https://api.eu.amplitude.com/batch" + static let IDENTIFY_EVENT = "$identify" + static let GROUP_IDENTIFY_EVENT = "$groupidentify" + static let MAX_PROPERTY_KEYS = 1024 + static let MAX_STRING_LENGTH = 1024 + public static let MIN_IDENTIFY_BATCH_INTERVAL_MILLIS = 30 * 1000 // 30s + + static let AMP_TRACKING_OPTION_CARRIER = "carrier" + static let AMP_TRACKING_OPTION_CITY = "city" + static let AMP_TRACKING_OPTION_COUNTRY = "country" + static let AMP_TRACKING_OPTION_DEVICE_MANUFACTURER = "device_manufacturer" + static let AMP_TRACKING_OPTION_DEVICE_MODEL = "device_model" + static let AMP_TRACKING_OPTION_DMA = "dma" + static let AMP_TRACKING_OPTION_IDFA = "idfa" + static let AMP_TRACKING_OPTION_IDFV = "idfv" + static let AMP_TRACKING_OPTION_IP_ADDRESS = "ip_address" + static let AMP_TRACKING_OPTION_LANGUAGE = "language" + static let AMP_TRACKING_OPTION_OS_NAME = "os_name" + static let AMP_TRACKING_OPTION_OS_VERSION = "os_version" + static let AMP_TRACKING_OPTION_PLATFORM = "platform" + static let AMP_TRACKING_OPTION_REGION = "region" + static let AMP_TRACKING_OPTION_VERSION_NAME = "version_name" + + static let AMP_AMPLITUDE_PREFIX = "[Amplitude] " + + static let AMP_SESSION_END_EVENT = "session_end" + static let AMP_SESSION_START_EVENT = "session_start" + static let AMP_APPLICATION_INSTALLED_EVENT = "\(AMP_AMPLITUDE_PREFIX)Application Installed" + static let AMP_APPLICATION_UPDATED_EVENT = "\(AMP_AMPLITUDE_PREFIX)Application Updated" + static let AMP_APPLICATION_OPENED_EVENT = "\(AMP_AMPLITUDE_PREFIX)Application Opened" + static let AMP_APPLICATION_BACKGROUNDED_EVENT = "\(AMP_AMPLITUDE_PREFIX)Application Backgrounded" + static let AMP_DEEP_LINK_OPENED_EVENT = "\(AMP_AMPLITUDE_PREFIX)Deep Link Opened" + static let AMP_SCREEN_VIEWED_EVENT = "\(AMP_AMPLITUDE_PREFIX)Screen Viewed" + static let AMP_ELEMENT_INTERACTED_EVENT = "\(AMP_AMPLITUDE_PREFIX)Element Interacted" + + static let AMP_REVENUE_EVENT = "revenue_amount" + + static let AMP_APP_VERSION_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Version" + static let AMP_APP_BUILD_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Build" + static let AMP_APP_PREVIOUS_VERSION_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Previous Version" + static let AMP_APP_PREVIOUS_BUILD_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Previous Build" + static let AMP_APP_FROM_BACKGROUND_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)From Background" + static let AMP_APP_LINK_URL_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Link URL" + static let AMP_APP_LINK_REFERRER_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Link Referrer" + static let AMP_APP_SCREEN_NAME_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Screen Name" + static let AMP_APP_TARGET_AXLABEL_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Target Accessibility Label" + static let AMP_APP_TARGET_AXIDENTIFIER_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Target Accessibility Identifier" + static let AMP_APP_ACTION_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Action" + static let AMP_APP_TARGET_VIEW_CLASS_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Target View Class" + static let AMP_APP_TARGET_TEXT_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Target Text" + static let AMP_APP_HIERARCHY_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Hierarchy" + static let AMP_APP_ACTION_METHOD_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Action Method" + static let AMP_APP_GESTURE_RECOGNIZER_PROPERTY = "\(AMP_AMPLITUDE_PREFIX)Gesture Recognizer" + + public struct Configuration { + public static let FLUSH_QUEUE_SIZE = 30 + public static let FLUSH_INTERVAL_MILLIS = 30 * 1000 // 30s + public static let DEFAULT_INSTANCE = "$default_instance" + public static let FLUSH_MAX_RETRIES = 5 + public static let MIN_TIME_BETWEEN_SESSIONS_MILLIS = 300000 + public static let IDENTIFY_BATCH_INTERVAL_MILLIS = 30 * 1000 // 30s + } + + public struct Storage { + public static let STORAGE_PREFIX = "amplitude-swift" + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/DefaultTrackingOptions.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/DefaultTrackingOptions.swift new file mode 100644 index 000000000..bf5a0142a --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/DefaultTrackingOptions.swift @@ -0,0 +1,59 @@ +import Foundation + +protocol DefaultTrackingOptionsDelegate: AnyObject { + @available(*, deprecated) + func didChangeOptions(options: DefaultTrackingOptions) +} + +@available(*, deprecated, renamed: "AutocaptureOptions", message: "Please use `AutocaptureOptions` instead") +public class DefaultTrackingOptions { + public static var ALL: DefaultTrackingOptions { + DefaultTrackingOptions(sessions: true, appLifecycles: true, screenViews: true) + } + public static var NONE: DefaultTrackingOptions { + DefaultTrackingOptions(sessions: false, appLifecycles: false, screenViews: false) + } + + public var sessions: Bool { + didSet { + delegate?.didChangeOptions(options: self) + } + } + + public var appLifecycles: Bool { + didSet { + delegate?.didChangeOptions(options: self) + } + } + + public var screenViews: Bool { + didSet { + delegate?.didChangeOptions(options: self) + } + } + + weak var delegate: DefaultTrackingOptionsDelegate? + + var autocaptureOptions: AutocaptureOptions { + return [ + sessions ? .sessions : [], + appLifecycles ? .appLifecycles : [], + screenViews ? .screenViews : [] + ].reduce(into: []) { $0.formUnion($1) } + } + + public init( + sessions: Bool = true, + appLifecycles: Bool = false, + screenViews: Bool = false + ) { + self.sessions = sessions + self.appLifecycles = appLifecycles + self.screenViews = screenViews + } + + convenience init(delegate: DefaultTrackingOptionsDelegate) { + self.init() + self.delegate = delegate + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/EventBridge.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/EventBridge.swift new file mode 100644 index 000000000..58559cd5f --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/EventBridge.swift @@ -0,0 +1,37 @@ +// +// EventBridge.swift +// +// +// Created by Marvin Liu on 10/27/22. +// + +import Foundation + +struct Event { + var eventType: String + var eventProperties: [String: Any]? + var userProperties: [String: Any]? + var groups: [String: Any]? + var groupProperties: [String: Any]? +} + +enum EventChannel { + +} + +protocol EventReceiver { + func receive(channel: EventChannel, event: Event) +} + +protocol EventBridgable { + func sendEvent(channel: EventChannel, event: Event) + func setReceiver(channel: EventChannel, receiver: EventReceiver) +} + +class EventBridge: EventBridgable { + func sendEvent(channel: EventChannel, event: Event) { + } + + func setReceiver(channel: EventChannel, receiver: EventReceiver) { + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/BaseEvent.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/BaseEvent.swift new file mode 100644 index 000000000..1d5daa64b --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/BaseEvent.swift @@ -0,0 +1,273 @@ +// +// BaseEvent.swift +// +// +// Created by Marvin Liu on 11/3/22. +// + +import Foundation + +open class BaseEvent: EventOptions, Codable { + public var eventType: String + public var eventProperties: [String: Any?]? + public var userProperties: [String: Any?]? + public var groups: [String: Any?]? + public var groupProperties: [String: Any?]? + + enum CodingKeys: String, CodingKey { + case eventType = "event_type" + case eventProperties = "event_properties" + case userProperties = "user_properties" + case groups + case groupProperties = "group_properties" + case userId = "user_id" + case deviceId = "device_id" + case timestamp = "time" + case eventId = "event_id" + case sessionId = "session_id" + case insertId = "insert_id" + case locationLat = "location_lat" + case locationLng = "location_lng" + case appVersion = "app_version" + case versionName = "version_name" + case platform + case osName = "os_name" + case osVersion = "os_version" + case deviceBrand = "device_brand" + case deviceManufacturer = "device_manufacturer" + case deviceModel = "device_model" + case carrier + case country + case region + case city + case dma + case idfa + case idfv + case adid + case language + case library + case ip + case plan + case ingestionMetadata = "ingestion_metadata" + case revenue + case price + case quantity + case productId = "product_id" + case revenueType = "revenue_type" + case partnerId = "partner_id" + } + + public init( + userId: String? = nil, + deviceId: String? = nil, + timestamp: Int64? = nil, + eventId: Int64? = nil, + sessionId: Int64? = nil, + insertId: String? = nil, + locationLat: Double? = nil, + locationLng: Double? = nil, + appVersion: String? = nil, + versionName: String? = nil, + platform: String? = nil, + osName: String? = nil, + osVersion: String? = nil, + deviceBrand: String? = nil, + deviceManufacturer: String? = nil, + deviceModel: String? = nil, + carrier: String? = nil, + country: String? = nil, + region: String? = nil, + city: String? = nil, + dma: String? = nil, + idfa: String? = nil, + idfv: String? = nil, + adid: String? = nil, + language: String? = nil, + library: String? = nil, + ip: String? = nil, + plan: Plan? = nil, + ingestionMetadata: IngestionMetadata? = nil, + revenue: Double? = nil, + price: Double? = nil, + quantity: Int? = nil, + productId: String? = nil, + revenueType: String? = nil, + extra: [String: Any]? = nil, + callback: EventCallback? = nil, + partnerId: String? = nil, + eventType: String, + eventProperties: [String: Any?]? = nil, + userProperties: [String: Any?]? = nil, + groups: [String: Any?]? = nil, + groupProperties: [String: Any?]? = nil + ) { + self.eventType = eventType + self.eventProperties = eventProperties + self.userProperties = userProperties + self.groups = groups + self.groupProperties = groupProperties + super.init( + userId: userId, + deviceId: deviceId, + timestamp: timestamp, + eventId: eventId, + sessionId: sessionId, + insertId: insertId, + locationLat: locationLat, + locationLng: locationLng, + appVersion: appVersion, + versionName: versionName, + platform: platform, + osName: osName, + osVersion: osVersion, + deviceBrand: deviceBrand, + deviceManufacturer: deviceManufacturer, + deviceModel: deviceModel, + carrier: carrier, + country: country, + region: region, + city: city, + dma: dma, + idfa: idfa, + idfv: idfv, + adid: adid, + language: language, + library: library, + ip: ip, + plan: plan, + ingestionMetadata: ingestionMetadata, + revenue: revenue, + price: price, + quantity: quantity, + productId: productId, + revenueType: revenueType, + extra: extra, + callback: callback, + partnerId: partnerId + ) + } + + func isValid() -> Bool { + return userId != nil || deviceId != nil + } + + required public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + eventType = try values.decode(String.self, forKey: .eventType) + eventProperties = try values.decodeIfPresent([String: Any].self, forKey: .eventProperties) + userProperties = try values.decodeIfPresent([String: Any].self, forKey: .userProperties) + groups = try values.decodeIfPresent([String: Any].self, forKey: .groups) + groupProperties = try values.decodeIfPresent([String: Any].self, forKey: .groupProperties) + super.init() + userId = try values.decodeIfPresent(String.self, forKey: .userId) + deviceId = try values.decodeIfPresent(String.self, forKey: .deviceId) + timestamp = try values.decodeIfPresent(Int64.self, forKey: .timestamp) + eventId = try values.decodeIfPresent(Int64.self, forKey: .eventId) + sessionId = try values.decodeIfPresent(Int64.self, forKey: .sessionId) + insertId = try values.decodeIfPresent(String.self, forKey: .insertId) + locationLat = try values.decodeIfPresent(Double.self, forKey: .locationLat) + locationLng = try values.decodeIfPresent(Double.self, forKey: .locationLng) + appVersion = try values.decodeIfPresent(String.self, forKey: .appVersion) + versionName = try values.decodeIfPresent(String.self, forKey: .versionName) + platform = try values.decodeIfPresent(String.self, forKey: .platform) + osName = try values.decodeIfPresent(String.self, forKey: .osName) + osVersion = try values.decodeIfPresent(String.self, forKey: .osVersion) + deviceBrand = try values.decodeIfPresent(String.self, forKey: .deviceBrand) + deviceManufacturer = try values.decodeIfPresent(String.self, forKey: .deviceManufacturer) + deviceModel = try values.decodeIfPresent(String.self, forKey: .deviceModel) + carrier = try values.decodeIfPresent(String.self, forKey: .carrier) + country = try values.decodeIfPresent(String.self, forKey: .country) + region = try values.decodeIfPresent(String.self, forKey: .region) + city = try values.decodeIfPresent(String.self, forKey: .city) + dma = try values.decodeIfPresent(String.self, forKey: .dma) + idfa = try values.decodeIfPresent(String.self, forKey: .idfa) + idfv = try values.decodeIfPresent(String.self, forKey: .idfv) + adid = try values.decodeIfPresent(String.self, forKey: .adid) + language = try values.decodeIfPresent(String.self, forKey: .language) + library = try values.decodeIfPresent(String.self, forKey: .library) + ip = try values.decodeIfPresent(String.self, forKey: .ip) + plan = try values.decodeIfPresent(Plan.self, forKey: .plan) + ingestionMetadata = try values.decodeIfPresent(IngestionMetadata.self, forKey: .ingestionMetadata) + revenue = try values.decodeIfPresent(Double.self, forKey: .revenue) + price = try values.decodeIfPresent(Double.self, forKey: .price) + quantity = try values.decodeIfPresent(Int.self, forKey: .quantity) + productId = try values.decodeIfPresent(String.self, forKey: .productId) + revenueType = try values.decodeIfPresent(String.self, forKey: .revenueType) + partnerId = try values.decodeIfPresent(String.self, forKey: .partnerId) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(eventType, forKey: .eventType) + try container.encodeAny(eventProperties, forKey: .eventProperties) + try container.encodeAny(userProperties, forKey: .userProperties) + try container.encodeAny(groups, forKey: .groups) + try container.encodeAny(groupProperties, forKey: .groupProperties) + try container.encode(userId, forKey: .userId) + try container.encode(deviceId, forKey: .deviceId) + try container.encode(timestamp, forKey: .timestamp) + try container.encode(eventId, forKey: .eventId) + try container.encode(sessionId, forKey: .sessionId) + try container.encode(insertId, forKey: .insertId) + try container.encode(locationLat, forKey: .locationLat) + try container.encode(locationLng, forKey: .locationLng) + try container.encode(appVersion, forKey: .appVersion) + try container.encode(versionName, forKey: .versionName) + try container.encode(platform, forKey: .platform) + try container.encode(osName, forKey: .osName) + try container.encode(osVersion, forKey: .osVersion) + try container.encode(deviceBrand, forKey: .deviceBrand) + try container.encode(deviceManufacturer, forKey: .deviceManufacturer) + try container.encode(deviceModel, forKey: .deviceModel) + try container.encode(carrier, forKey: .carrier) + try container.encode(country, forKey: .country) + try container.encode(region, forKey: .region) + try container.encode(city, forKey: .city) + try container.encode(dma, forKey: .dma) + try container.encode(idfa, forKey: .idfa) + try container.encode(idfv, forKey: .idfv) + try container.encode(adid, forKey: .adid) + try container.encode(language, forKey: .language) + try container.encode(library, forKey: .library) + try container.encode(ip, forKey: .ip) + try container.encodeIfPresent(plan, forKey: .plan) + try container.encodeIfPresent(ingestionMetadata, forKey: .ingestionMetadata) + try container.encode(revenue, forKey: .revenue) + try container.encode(price, forKey: .price) + try container.encode(quantity, forKey: .quantity) + try container.encode(productId, forKey: .productId) + try container.encode(revenueType, forKey: .revenueType) + try container.encode(partnerId, forKey: .partnerId) + } +} + +extension BaseEvent { + func toString() -> String { + var returnString = "" + do { + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + let json = try encoder.encode(self) + if let printed = String(data: json, encoding: .utf8) { + returnString = printed + } + } catch { + returnString = error.localizedDescription + } + return returnString + } +} + +extension BaseEvent { + static func fromArrayString(jsonString: String) -> [T]? { + let jsonData = jsonString.data(using: .utf8)! + let decoder = JSONDecoder() + return try? decoder.decode([T].self, from: jsonData) + } + + static func fromString(jsonString: String) -> T? { + let jsonData = jsonString.data(using: .utf8)! + let decoder = JSONDecoder() + return try? decoder.decode(T.self, from: jsonData) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/DeepLinkOpenedEvent.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/DeepLinkOpenedEvent.swift new file mode 100644 index 000000000..835dab19b --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/DeepLinkOpenedEvent.swift @@ -0,0 +1,42 @@ +import Foundation + +public class DeepLinkOpenedEvent: BaseEvent { + override public var eventType: String { + get { + Constants.AMP_DEEP_LINK_OPENED_EVENT + } + set { + } + } + + public convenience init(url: URL) { + self.init(url: url.absoluteString) + } + + public convenience init(url: NSURL) { + self.init(url: url.absoluteString) + } + + public convenience init(activity: NSUserActivity) { + let url = activity.webpageURL?.absoluteString + var referrer: String? + if #available(iOS 11, tvOS 11.0, macOS 10.13, watchOS 4.0, *) { + referrer = activity.referrerURL?.absoluteString + } + self.init(url: url, referrer: referrer) + } + + public init(url: String?, referrer: String? = nil) { + var eventProperties = [ + Constants.AMP_APP_LINK_URL_PROPERTY: url ?? "", + ] + if let referrer = referrer { + eventProperties[Constants.AMP_APP_LINK_REFERRER_PROPERTY] = referrer + } + super.init(eventType: Constants.AMP_DEEP_LINK_OPENED_EVENT, eventProperties: eventProperties) + } + + required public init(from decoder: Decoder) throws { + try super.init(from: decoder) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/ElementInteractionEvent.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/ElementInteractionEvent.swift new file mode 100644 index 000000000..4dbd0e862 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/ElementInteractionEvent.swift @@ -0,0 +1,27 @@ +import Foundation + +public class ElementInteractionEvent: BaseEvent { + convenience init( + screenName: String? = nil, + accessibilityLabel: String? = nil, + accessibilityIdentifier: String? = nil, + action: String, + targetViewClass: String, + targetText: String? = nil, + hierarchy: String, + actionMethod: String? = nil, + gestureRecognizer: String? = nil + ) { + self.init(eventType: Constants.AMP_ELEMENT_INTERACTED_EVENT, eventProperties: [ + Constants.AMP_APP_SCREEN_NAME_PROPERTY: screenName, + Constants.AMP_APP_TARGET_AXLABEL_PROPERTY: accessibilityLabel, + Constants.AMP_APP_TARGET_AXIDENTIFIER_PROPERTY: accessibilityIdentifier, + Constants.AMP_APP_ACTION_PROPERTY: action, + Constants.AMP_APP_TARGET_VIEW_CLASS_PROPERTY: targetViewClass, + Constants.AMP_APP_TARGET_TEXT_PROPERTY: targetText, + Constants.AMP_APP_HIERARCHY_PROPERTY: hierarchy, + Constants.AMP_APP_ACTION_METHOD_PROPERTY: actionMethod, + Constants.AMP_APP_GESTURE_RECOGNIZER_PROPERTY: gestureRecognizer + ]) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/EventOptions.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/EventOptions.swift new file mode 100644 index 000000000..0389c4dd9 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/EventOptions.swift @@ -0,0 +1,169 @@ +// +// EventOptions.swift +// +// +// Created by Marvin Liu on 11/3/22. +// + +import Foundation + +open class EventOptions { + public var userId: String? + public var deviceId: String? + public var timestamp: Int64? + public var eventId: Int64? + public var sessionId: Int64? + public var insertId: String? + public var locationLat: Double? + public var locationLng: Double? + public var appVersion: String? + public var versionName: String? + public var platform: String? + public var osName: String? + public var osVersion: String? + public var deviceBrand: String? + public var deviceManufacturer: String? + public var deviceModel: String? + public var carrier: String? + public var country: String? + public var region: String? + public var city: String? + public var dma: String? + public var idfa: String? + public var idfv: String? + public var adid: String? + public var language: String? + public var library: String? + public var ip: String? + public var plan: Plan? + public var ingestionMetadata: IngestionMetadata? + public var revenue: Double? + public var price: Double? + public var quantity: Int? + public var productId: String? + public var revenueType: String? + public var extra: [String: Any]? + public var callback: EventCallback? + public var partnerId: String? + internal var attempts: Int + + public init( + userId: String? = nil, + deviceId: String? = nil, + timestamp: Int64? = nil, + eventId: Int64? = nil, + sessionId: Int64? = nil, + insertId: String? = nil, + locationLat: Double? = nil, + locationLng: Double? = nil, + appVersion: String? = nil, + versionName: String? = nil, + platform: String? = nil, + osName: String? = nil, + osVersion: String? = nil, + deviceBrand: String? = nil, + deviceManufacturer: String? = nil, + deviceModel: String? = nil, + carrier: String? = nil, + country: String? = nil, + region: String? = nil, + city: String? = nil, + dma: String? = nil, + idfa: String? = nil, + idfv: String? = nil, + adid: String? = nil, + language: String? = nil, + library: String? = nil, + ip: String? = nil, + plan: Plan? = nil, + ingestionMetadata: IngestionMetadata? = nil, + revenue: Double? = nil, + price: Double? = nil, + quantity: Int? = nil, + productId: String? = nil, + revenueType: String? = nil, + extra: [String: Any]? = nil, + callback: EventCallback? = nil, + partnerId: String? = nil, + attempts: Int = 0 + ) { + self.userId = userId + self.deviceId = deviceId + self.timestamp = timestamp + self.eventId = eventId + self.sessionId = sessionId + self.insertId = insertId + self.locationLat = locationLat + self.locationLng = locationLng + self.appVersion = appVersion + self.versionName = versionName + self.platform = platform + self.osName = osName + self.osVersion = osVersion + self.deviceBrand = deviceBrand + self.deviceManufacturer = deviceManufacturer + self.deviceModel = deviceModel + self.carrier = carrier + self.country = country + self.region = region + self.city = city + self.dma = dma + self.idfa = idfa + self.idfv = idfv + self.adid = adid + self.language = language + self.library = library + self.ip = ip + self.plan = plan + self.ingestionMetadata = ingestionMetadata + self.revenue = revenue + self.price = price + self.quantity = quantity + self.productId = productId + self.revenueType = revenueType + self.extra = extra + self.callback = callback + self.partnerId = partnerId + self.attempts = attempts + } + + public func mergeEventOptions(eventOptions: EventOptions) { + userId = eventOptions.userId ?? userId + deviceId = eventOptions.deviceId ?? deviceId + timestamp = eventOptions.timestamp ?? timestamp + eventId = eventOptions.eventId ?? eventId + sessionId = eventOptions.sessionId ?? sessionId + insertId = eventOptions.insertId ?? insertId + locationLat = eventOptions.locationLat ?? locationLat + locationLng = eventOptions.locationLng ?? locationLng + appVersion = eventOptions.appVersion ?? appVersion + versionName = eventOptions.versionName ?? versionName + platform = eventOptions.platform ?? platform + osName = eventOptions.osName ?? osName + osVersion = eventOptions.osVersion ?? osVersion + deviceBrand = eventOptions.deviceBrand ?? deviceBrand + deviceManufacturer = eventOptions.deviceManufacturer ?? deviceManufacturer + deviceModel = eventOptions.deviceModel ?? deviceModel + carrier = eventOptions.carrier ?? carrier + country = eventOptions.country ?? country + region = eventOptions.region ?? region + city = eventOptions.city ?? city + dma = eventOptions.dma ?? dma + idfa = eventOptions.idfa ?? idfa + idfv = eventOptions.idfv ?? idfv + adid = eventOptions.adid ?? adid + language = eventOptions.language ?? language + library = eventOptions.library ?? library + ip = eventOptions.ip ?? ip + plan = eventOptions.plan ?? plan + ingestionMetadata = eventOptions.ingestionMetadata ?? ingestionMetadata + revenue = eventOptions.revenue ?? revenue + price = eventOptions.price ?? price + quantity = eventOptions.quantity ?? quantity + productId = eventOptions.productId ?? productId + revenueType = eventOptions.revenueType ?? revenueType + extra = eventOptions.extra ?? extra + callback = eventOptions.callback ?? callback + partnerId = eventOptions.partnerId ?? partnerId + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/GroupIdentifyEvent.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/GroupIdentifyEvent.swift new file mode 100644 index 000000000..3a2026696 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/GroupIdentifyEvent.swift @@ -0,0 +1,26 @@ +// +// GroupIdentifyEvent.swift +// +// +// Created by Marvin Liu on 11/3/22. +// + +import Foundation + +public class GroupIdentifyEvent: BaseEvent { + override public var eventType: String { + get { + return "$groupidentify" + } + set { + } + } + + convenience init() { + self.init(eventType: "$groupidentify") + } + + override func isValid() -> Bool { + return groups != nil && groupProperties != nil + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/Identify.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/Identify.swift new file mode 100644 index 000000000..d2f77adb6 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/Identify.swift @@ -0,0 +1,747 @@ +// +// Identify.swift +// +// +// Created by Marvin Liu on 12/8/22. +// + +import Foundation + +open class Identify { + static let UNSET_VALUE = "-" + + enum Operation: String { + case SET = "$set" + case SET_ONCE = "$setOnce" + case ADD = "$add" + case APPEND = "$append" + case CLEAR_ALL = "$clearAll" + case PREPEND = "$prepend" + case UNSET = "$unset" + case PRE_INSERT = "$preInsert" + case POST_INSERT = "$postInsert" + case REMOVE = "$remove" + } + + public init() {} + + var propertySet = Set() + var properties = [String: Any?]() + var logger = ConsoleLogger(logLevel: LogLevelEnum.WARN.rawValue) + + // $set operation + @discardableResult + public func set(property: String, value: Bool) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: Int) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: Float) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: Double) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: Int64) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: String) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: [String: Any]) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: [Any]) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: [Bool]) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: [Int]) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: [Float]) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: [Double]) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: [Int64]) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: [String]) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + @discardableResult + public func set(property: String, value: Any?) -> Identify { + setUserProperty(operation: .SET, property: property, value: value) + return self + } + + // $setOnce operation + @discardableResult + public func setOnce(property: String, value: Bool) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: Int) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: Float) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: Double) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: Int64) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: String) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: Any?) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: [String: Any]) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: [Any]) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: [Bool]) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: [Int]) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: [Float]) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: [Double]) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: [Int64]) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + @discardableResult + public func setOnce(property: String, value: [String]) -> Identify { + setUserProperty(operation: .SET_ONCE, property: property, value: value) + return self + } + + // $prepend operation + @discardableResult + public func prepend(property: String, value: Bool) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: Int) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: Float) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: Double) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: Int64) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: String) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: Any?) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: [String: Any]) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: [Any]) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: [Bool]) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: [Int]) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: [Float]) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: [Double]) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: [Int64]) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + @discardableResult + public func prepend(property: String, value: [String]) -> Identify { + setUserProperty(operation: .PREPEND, property: property, value: value) + return self + } + + // $append operation + @discardableResult + public func append(property: String, value: Bool) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: Int) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: Float) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: Double) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: Int64) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: String) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: Any?) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: [String: Any]) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: [Any]) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: [Bool]) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: [Int]) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: [Float]) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: [Double]) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: [Int64]) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + @discardableResult + public func append(property: String, value: [String]) -> Identify { + setUserProperty(operation: .APPEND, property: property, value: value) + return self + } + + // $postInsert operation + @discardableResult + public func postInsert(property: String, value: Bool) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: Int) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: Float) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: Double) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: Int64) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: String) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: Any?) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: [String: Any]) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: [Any]) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: [Bool]) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: [Int]) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: [Float]) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: [Double]) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: [Int64]) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func postInsert(property: String, value: [String]) -> Identify { + setUserProperty(operation: .POST_INSERT, property: property, value: value) + return self + } + + // $preInsert operation + @discardableResult + public func preInsert(property: String, value: Bool) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: Int) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: Float) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: Double) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: Int64) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: String) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: Any?) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: [String: Any]) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: [Any]) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: [Bool]) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: [Int]) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: [Float]) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: [Double]) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: [Int64]) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + @discardableResult + public func preInsert(property: String, value: [String]) -> Identify { + setUserProperty(operation: .PRE_INSERT, property: property, value: value) + return self + } + + // $remove operation + @discardableResult + public func remove(property: String, value: Bool) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: Int) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: Float) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: Double) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: Int64) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: String) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: Any?) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: [String: Any]) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: [Any]) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: [Bool]) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: [Int]) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: [Float]) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: [Double]) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: [Int64]) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + @discardableResult + public func remove(property: String, value: [String]) -> Identify { + setUserProperty(operation: .REMOVE, property: property, value: value) + return self + } + + // $add operation + @discardableResult + public func add(property: String, value: Int) -> Identify { + setUserProperty(operation: .ADD, property: property, value: value) + return self + } + + @discardableResult + public func add(property: String, value: Float) -> Identify { + setUserProperty(operation: .ADD, property: property, value: value) + return self + } + + @discardableResult + public func add(property: String, value: Double) -> Identify { + setUserProperty(operation: .ADD, property: property, value: value) + return self + } + + @discardableResult + public func add(property: String, value: Int64) -> Identify { + setUserProperty(operation: .ADD, property: property, value: value) + return self + } + + // $unset operation + @discardableResult + public func unset(property: String) -> Identify { + setUserProperty(operation: .UNSET, property: property, value: Identify.UNSET_VALUE) + return self + } + + // $clearAll operation + @discardableResult + public func clearAll() -> Identify { + properties.removeAll() + properties[Operation.CLEAR_ALL.rawValue] = Identify.UNSET_VALUE + return self + } + + func setUserProperty(operation: Operation, property: String, value: Any?) { + guard !property.isEmpty else { + logger.warn( + message: + "Attempting to perform operation \(operation.rawValue) with a null or empty string property, ignoring" + ) + return + } + guard value != nil else { + logger.warn( + message: + "Attempting to perform operation \(operation.rawValue) with null value for property \(property), ignoring" + ) + return + } + guard properties[Operation.CLEAR_ALL.rawValue] == nil else { + logger.warn( + message: + "This Identify already contains a $clearAll operation, ignoring operation \(operation.rawValue)" + ) + return + } + guard !propertySet.contains(property) else { + logger.warn( + message: + "Already used property \(property) in previous operation, ignoring operation \(operation.rawValue)" + ) + return + } + if properties[operation.rawValue] == nil { + properties[operation.rawValue] = [String: Any]() + } + if var prop = properties[operation.rawValue] as? [String: Any] { + prop[property] = value! + properties[operation.rawValue] = prop // need to assign back for nested dict + propertySet.insert(property) + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/IdentifyEvent.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/IdentifyEvent.swift new file mode 100644 index 000000000..bd90d7388 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/IdentifyEvent.swift @@ -0,0 +1,22 @@ +// +// IdentifyEvent.swift +// +// +// Created by Marvin Liu on 11/3/22. +// + +import Foundation + +public class IdentifyEvent: BaseEvent { + override public var eventType: String { + get { + return "$identify" + } + set { + } + } + + convenience init() { + self.init(eventType: "$identify") + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/Revenue.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/Revenue.swift new file mode 100644 index 000000000..d97b06e84 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/Revenue.swift @@ -0,0 +1,115 @@ +// +// Revenue.swift +// +// +// Created by Marvin Liu on 12/8/22. +// + +import Foundation + +public class Revenue { + enum Property: String { + case REVENUE_PRODUCT_ID = "$productId" + case REVENUE_QUANTITY = "$quantity" + case REVENUE_PRICE = "$price" + case REVENUE_TYPE = "$revenueType" + case REVENUE_RECEIPT = "$receipt" + case REVENUE_RECEIPT_SIG = "$receiptSig" + case REVENUE = "$revenue" + } + + public init() {} + + private var _productId: String? + public var productId: String? { + get { + return _productId + } + set(value) { + if value != nil && !value!.isEmpty { + _productId = value + } + } + } + + private var _quantity: Int = 1 + public var quantity: Int { + get { + return _quantity + } + set(value) { + if value > 0 { + _quantity = value + } + } + } + + private var _price: Double? + public var price: Double? { + get { + return _price + } + set(value) { + if value != nil { + _price = value + } + } + } + + private var _revenue: Double? + public var revenue: Double? { + get { + return _revenue + } + set(value) { + if value != nil { + _revenue = value + } + } + } + + public var revenueType: String? + + public var receipt: String? + + public var receiptSig: String? + + public var properties: [String: Any?]? + + @discardableResult + public func setReceipt(receipt: String, receiptSignature: String) -> Revenue { + self.receipt = receipt + self.receiptSig = receiptSignature + return self + } + + func isValid() -> Bool { + return price != nil + } + + func toRevenueEvent() -> RevenueEvent { + let event = RevenueEvent() + var eventProperties = properties ?? [String: Any?]() + if productId != nil { + eventProperties[Property.REVENUE_PRODUCT_ID.rawValue] = productId + } + eventProperties[Property.REVENUE_QUANTITY.rawValue] = quantity + if price != nil { + eventProperties[Property.REVENUE_PRICE.rawValue] = price + } + if revenueType != nil { + eventProperties[Property.REVENUE_TYPE.rawValue] = revenueType + } + if receipt != nil { + eventProperties[Property.REVENUE_RECEIPT.rawValue] = receipt + } + if receiptSig != nil { + eventProperties[Property.REVENUE_RECEIPT_SIG.rawValue] = receiptSig + } + if revenue != nil { + eventProperties[Property.REVENUE.rawValue] = revenue + } + event.eventProperties = eventProperties + return event + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/RevenueEvent.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/RevenueEvent.swift new file mode 100644 index 000000000..4f4701b06 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/RevenueEvent.swift @@ -0,0 +1,22 @@ +// +// RevenueEvent.swift +// +// +// Created by Marvin Liu on 11/3/22. +// + +import Foundation + +public class RevenueEvent: BaseEvent { + override public var eventType: String { + get { + return "revenue_amount" + } + set { + } + } + + convenience init() { + self.init(eventType: "revenue_amount") + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/ScreenViewedEvent.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/ScreenViewedEvent.swift new file mode 100644 index 000000000..6e74f33ef --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Events/ScreenViewedEvent.swift @@ -0,0 +1,21 @@ +import Foundation + +public class ScreenViewedEvent: BaseEvent { + override public var eventType: String { + get { + Constants.AMP_SCREEN_VIEWED_EVENT + } + set { + } + } + + public init(screenName: String) { + super.init(eventType: Constants.AMP_SCREEN_VIEWED_EVENT, eventProperties: [ + Constants.AMP_APP_SCREEN_NAME_PROPERTY: screenName + ]) + } + + required public init(from decoder: Decoder) throws { + try super.init(from: decoder) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Mediator.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Mediator.swift new file mode 100644 index 000000000..7dbfaae3d --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Mediator.swift @@ -0,0 +1,57 @@ +// +// Mediator.swift +// +// +// Created by Hao Yu on 11/9/22. +// + +internal class Mediator { + // create an array with certain type. + internal var plugins = [Plugin]() + + internal func add(plugin: Plugin) { + plugins.append(plugin) + } + + internal func remove(plugin: Plugin) { + plugins.removeAll { (storedPlugin) -> Bool in + if storedPlugin === plugin { + storedPlugin.teardown() + return true + } + return false + } + } + + internal func execute(event: BaseEvent?) -> BaseEvent? { + var result: BaseEvent? = event + plugins.forEach { plugin in + if let r = result { + if let p = plugin as? DestinationPlugin { + _ = p.execute(event: r) + } else if let p = plugin as? EventPlugin { + if let rr = result { + if let identifyEvent = rr as? IdentifyEvent { + result = p.identify(event: identifyEvent) + } else if let groupIdentifyEvent = rr as? GroupIdentifyEvent { + result = p.groupIdentify(event: groupIdentifyEvent) + } else if let revenueEvent = rr as? RevenueEvent { + result = p.revenue(event: revenueEvent) + } else { + result = p.track(event: rr) + } + } + } else { + result = plugin.execute(event: r) + } + } + } + return result + } + + internal func applyClosure(_ closure: (Plugin) -> Void) { + plugins.forEach { plugin in + closure(plugin) + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Migration/LegacyDatabaseStorage.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Migration/LegacyDatabaseStorage.swift new file mode 100644 index 000000000..168507ad6 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Migration/LegacyDatabaseStorage.swift @@ -0,0 +1,207 @@ +import Foundation +import SQLite3 + +class LegacyDatabaseStorage { + private static let DATABASE_NAME = "com.amplitude.database" + private static let EVENT_TABLE_NAME = "events" + private static let IDENTIFY_TABLE_NAME = "identifys" + private static let INTERCEPTED_IDENTIFY_TABLE_NAME = "intercepted_identifys" + private static let STORE_TABLE_NAME = "store" + private static let LONG_STORE_TABLE_NAME = "long_store" + private static let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) + + private static var instances: [String: LegacyDatabaseStorage] = [:] + private static let instanceQueue = DispatchQueue(label: "legacyDatabaseStorage.amplitude.com") + + let databasePath: String + let logger: (any Logger)? + + public static func getStorage(_ instanceName: String, _ logger: (any Logger)?) -> LegacyDatabaseStorage { + instanceQueue.sync { + var normalizedInstanceName = instanceName.lowercased() + if normalizedInstanceName == Constants.Configuration.DEFAULT_INSTANCE { + normalizedInstanceName = "" + } + if let storage = instances[normalizedInstanceName] { + return storage + } + let storage = LegacyDatabaseStorage(getDatabasePath(normalizedInstanceName).path, logger) + instances[normalizedInstanceName] = storage + return storage + } + } + + static func getDatabasePath(_ instanceName: String) -> URL { + #if os(tvOS) + let searchPathDirectory = FileManager.SearchPathDirectory.cachesDirectory + #else + let searchPathDirectory = FileManager.SearchPathDirectory.libraryDirectory + #endif + + let urls = FileManager.default.urls(for: searchPathDirectory, in: .userDomainMask) + var databaseUrl = urls[0] + + var databaseName = DATABASE_NAME + if instanceName != "" { + databaseName += "_\(instanceName)" + } + databaseUrl.appendPathComponent(databaseName) + return databaseUrl + } + + public init(_ databasePath: String, _ logger: (any Logger)?) { + self.databasePath = databasePath + self.logger = logger + } + + func getValue(_ key: String) -> String? { + getValueFromTable(LegacyDatabaseStorage.STORE_TABLE_NAME, key) as? String + } + + func getLongValue(_ key: String) -> Int64? { + getValueFromTable(LegacyDatabaseStorage.LONG_STORE_TABLE_NAME, key) as? Int64 + } + + private func getValueFromTable(_ table: String, _ key: String) -> Any? { + let query = "SELECT key, value FROM \(table) WHERE key = ?;" + return executeQuery(query) { stmt in + let bindResult = sqlite3_bind_text(stmt, 1, key, -1, LegacyDatabaseStorage.SQLITE_TRANSIENT) + if bindResult != SQLITE_OK { + logger?.error(message: "bind query parameter failed with result: \(bindResult)") + return + } + + let stepResult = sqlite3_step(stmt) + if stepResult != SQLITE_ROW { + logger?.error(message: "execute query '\(query)' failed with result: \(stepResult)") + return + } + + if sqlite3_column_type(stmt, 1) != SQLITE_NULL { + if table == LegacyDatabaseStorage.STORE_TABLE_NAME { + guard let rawValue = sqlite3_column_text(stmt, 1) else { + return + } + return String(cString: rawValue) + } else { + return sqlite3_column_int64(stmt, 1) + } + } else { + return + } + } + } + + func removeValue(_ key: String) { + removeValueFromTable(LegacyDatabaseStorage.STORE_TABLE_NAME, key) + } + + func removeLongValue(_ key: String) { + removeValueFromTable(LegacyDatabaseStorage.LONG_STORE_TABLE_NAME, key) + } + + private func removeValueFromTable(_ table: String, _ key: String) { + let query = "DELETE FROM \(table) WHERE key = ?;" + _ = executeQuery(query) { stmt in + let bindResult = sqlite3_bind_text(stmt, 1, key, -1, LegacyDatabaseStorage.SQLITE_TRANSIENT) + if bindResult != SQLITE_OK { + logger?.error(message: "bind query parameter failed with result: \(bindResult)") + return + } + + let stepResult = sqlite3_step(stmt) + if stepResult != SQLITE_DONE { + logger?.error(message: "execute query '\(query)' failed with result: \(stepResult)") + return + } + } + } + + func removeEvent(_ rowId: Int64) { + removeEventFromTable(LegacyDatabaseStorage.EVENT_TABLE_NAME, rowId) + } + + func removeIdentify(_ rowId: Int64) { + removeEventFromTable(LegacyDatabaseStorage.IDENTIFY_TABLE_NAME, rowId) + } + + func removeInterceptedIdentify(_ rowId: Int64) { + removeEventFromTable(LegacyDatabaseStorage.INTERCEPTED_IDENTIFY_TABLE_NAME, rowId) + } + + private func removeEventFromTable(_ table: String, _ rowId: Int64) { + let query = "DELETE FROM \(table) WHERE id = ?;" + _ = executeQuery(query) { stmt in + let bindResult = sqlite3_bind_int64(stmt, 1, rowId) + if bindResult != SQLITE_OK { + logger?.error(message: "bind query parameter failed with result: \(bindResult)") + return + } + + let stepResult = sqlite3_step(stmt) + if stepResult != SQLITE_DONE { + logger?.error(message: "execute query '\(query)' failed with result: \(stepResult)") + return + } + } + } + + func readEvents() -> [[String: Any]] { + readEventsFromTable(LegacyDatabaseStorage.EVENT_TABLE_NAME) + } + + func readIdentifies() -> [[String: Any]] { + readEventsFromTable(LegacyDatabaseStorage.IDENTIFY_TABLE_NAME) + } + + func readInterceptedIdentifies() -> [[String: Any]] { + readEventsFromTable(LegacyDatabaseStorage.INTERCEPTED_IDENTIFY_TABLE_NAME) + } + + private func readEventsFromTable(_ table: String) -> [[String: Any]] { + let query = "SELECT id, event FROM \(table) ORDER BY id;" + return executeQuery(query) { stmt in + var events: [[String: Any]] = [] + while sqlite3_step(stmt) == SQLITE_ROW { + let rowId = sqlite3_column_int64(stmt, 0) + let rawEventData = sqlite3_column_text(stmt, 1) + if rawEventData != nil { + let eventData = String(cString: rawEventData!).data(using: .utf8) + if eventData != nil && eventData!.count > 0 { + let event = try? JSONSerialization.jsonObject(with: eventData!, options: []) as? [String: Any] + if var event { + event["$rowId"] = rowId + events.append(event) + } + } + } + } + return events + } ?? [] + } + + private func executeQuery(_ query: String, _ block: (_ stmt: OpaquePointer) -> T) -> T? { + if !FileManager.default.fileExists(atPath: databasePath) { + return nil + } + + var db: OpaquePointer? + let openResult = sqlite3_open(databasePath, &db) + if openResult != SQLITE_OK { + logger?.error(message: "open database failed with result: \(openResult)") + sqlite3_close(db) + return nil + } + + var stmt: OpaquePointer? + let prepareResult = sqlite3_prepare(db, query, -1, &stmt, nil) + if prepareResult != SQLITE_OK { + logger?.error(message: "prepare query failed with result: \(prepareResult)") + return nil + } + let value = block(stmt!) + sqlite3_finalize(stmt) + sqlite3_close(db) + return value + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Migration/RemnantDataMigration.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Migration/RemnantDataMigration.swift new file mode 100644 index 000000000..6de6208d6 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Migration/RemnantDataMigration.swift @@ -0,0 +1,191 @@ +import Foundation + +class RemnantDataMigration { + private static let DEVICE_ID_KEY = "device_id" + private static let USER_ID_KEY = "user_id" + private static let PREVIOUS_SESSION_TIME_KEY = "previous_session_time" + private static let PREVIOUS_SESSION_ID_KEY = "previous_session_id" + + private let logger: (any Logger)? + private let storage: Storage + private let identifyStorage: Storage + private let legacyStorage: LegacyDatabaseStorage + + init(_ amplitude: Amplitude) { + logger = amplitude.logger + storage = amplitude.storage + identifyStorage = amplitude.identifyStorage + legacyStorage = LegacyDatabaseStorage.getStorage(amplitude.configuration.instanceName, amplitude.logger) + } + + func execute() { + let firstRunSinceUpgrade = storage.read(key: StorageKey.LAST_EVENT_TIME) == nil + + moveDeviceAndUserId() + moveSessionData() + + var maxIdentifyId: Int64 = -1 + if firstRunSinceUpgrade { + moveInterceptedIdentifies() + maxIdentifyId = moveIdentifies() + } + var maxEventId = moveEvents() + if maxEventId < maxIdentifyId { + maxEventId = maxIdentifyId + } + + if maxEventId > 0 { + let currentLastEventId: Int64? = storage.read(key: StorageKey.LAST_EVENT_ID) + if currentLastEventId == nil || currentLastEventId! <= 0 { + try? storage.write(key: StorageKey.LAST_EVENT_ID, value: maxEventId) + } + } + } + + private func moveDeviceAndUserId() { + let currentDeviceId: String? = storage.read(key: StorageKey.DEVICE_ID) + if currentDeviceId == nil || currentDeviceId! == "" { + if let deviceId = legacyStorage.getValue(RemnantDataMigration.DEVICE_ID_KEY) { + try? storage.write(key: StorageKey.DEVICE_ID, value: deviceId) + } + } + + let currentUserId: String? = storage.read(key: StorageKey.USER_ID) + if currentUserId == nil || currentUserId == "" { + if let userId = legacyStorage.getValue(RemnantDataMigration.USER_ID_KEY) { + try? storage.write(key: StorageKey.USER_ID, value: userId) + } + } + } + + private func moveSessionData() { + let currentSessionId: Int64? = storage.read(key: StorageKey.PREVIOUS_SESSION_ID) + let currentLastEventTime: Int64? = storage.read(key: StorageKey.LAST_EVENT_TIME) + + let previousSessionId = legacyStorage.getLongValue(RemnantDataMigration.PREVIOUS_SESSION_ID_KEY) + let lastEventTime = legacyStorage.getLongValue(RemnantDataMigration.PREVIOUS_SESSION_TIME_KEY) + + if (currentSessionId == nil || currentSessionId! < 0) && previousSessionId != nil && previousSessionId! >= 0 { + try? storage.write(key: StorageKey.PREVIOUS_SESSION_ID, value: previousSessionId) + legacyStorage.removeLongValue(RemnantDataMigration.PREVIOUS_SESSION_ID_KEY) + } + + if (currentLastEventTime == nil || currentLastEventTime! < 0) && lastEventTime != nil && lastEventTime! >= 0 { + try? storage.write(key: StorageKey.LAST_EVENT_TIME, value: lastEventTime) + legacyStorage.removeLongValue(RemnantDataMigration.PREVIOUS_SESSION_TIME_KEY) + } + } + + private func moveEvents() -> Int64 { + var maxEventId: Int64 = -1 + let remnantEvents = legacyStorage.readEvents() + remnantEvents.forEach { event in + let eventId = moveEvent(event, storage, legacyStorage.removeEvent) + if maxEventId < eventId { + maxEventId = eventId + } + } + return maxEventId + } + + private func moveIdentifies() -> Int64 { + var maxEventId: Int64 = -1 + let remnantEvents = legacyStorage.readIdentifies() + remnantEvents.forEach { event in + let eventId = moveEvent(event, storage, legacyStorage.removeIdentify) + if maxEventId < eventId { + maxEventId = eventId + } + } + return maxEventId + } + + private func moveInterceptedIdentifies() { + let remnantEvents = legacyStorage.readInterceptedIdentifies() + remnantEvents.forEach { event in + _ = moveEvent(event, identifyStorage, legacyStorage.removeInterceptedIdentify) + } + } + + private func moveEvent(_ event: [String: Any], _ destinationStorage: Storage, _ removeFromSource: (_ rowId: Int64) -> Void) -> Int64 { + do { + let rowId = event["$rowId"] as? Int64 + let converted = convertLegacyEvent(rowId!, event) + let jsonData = try JSONSerialization.data(withJSONObject: converted) + let convertedEvent = BaseEvent.fromString(jsonString: String(data: jsonData, encoding: .utf8)!) + try destinationStorage.write(key: StorageKey.EVENTS, value: convertedEvent) + removeFromSource(rowId!) + return rowId! + } catch { + logger?.error(message: "event migration failed: \(error)") + return -1 + } + } + + private func convertLegacyEvent(_ eventId: Int64, _ event: [String: Any]) -> [String: Any] { + var convertedEvent = event + + convertedEvent["event_id"] = eventId + + if let library = event["library"] as? [String: Any] { + convertedEvent["library"] = "\(library["name"] ?? "unknown")/\(library["version"] ?? "unknown")" + } + + if let timestamp = event["timestamp"] { + convertedEvent["time"] = timestamp + } + + if let uuid = event["uuid"] { + convertedEvent["insert_id"] = uuid + } + + if let apiProperties = event["api_properties"] as? [String: Any] { + if let idfa = apiProperties["ios_idfa"] { + convertedEvent["idfa"] = idfa + } + + if let idfv = apiProperties["ios_idfv"] { + convertedEvent["idfv"] = idfv + } + + if let productId = apiProperties["productId"] { + convertedEvent["productId"] = productId + } + + if let quantity = apiProperties["quantity"] { + convertedEvent["quantity"] = quantity + } + + if let price = apiProperties["price"] { + convertedEvent["price"] = price + } + + if let location = apiProperties["location"] as? [String: Any] { + if let lat = location["lat"] { + convertedEvent["location_lat"] = lat + } + if let lng = location["lng"] { + convertedEvent["location_lng"] = lng + } + } + } + + if let productId = event["$productId"] { + convertedEvent["productId"] = productId + } + + if let quantity = event["$quantity"] { + convertedEvent["quantity"] = quantity + } + + if let price = event["$price"] { + convertedEvent["price"] = price + } + + if let revenueType = event["$revenueType"] { + convertedEvent["revenueType"] = revenueType + } + + return convertedEvent + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Migration/StoragePrefixMigration.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Migration/StoragePrefixMigration.swift new file mode 100644 index 000000000..91ce11e86 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Migration/StoragePrefixMigration.swift @@ -0,0 +1,111 @@ +import Foundation + +class StoragePrefixMigration { + let source: PersistentStorage + let destination: PersistentStorage + let logger: (any Logger)? + + init(source: PersistentStorage, destination: PersistentStorage, logger: (any Logger)?) { + self.source = source + self.destination = destination + self.logger = logger + } + + func execute(skipEventFiles: Bool = false) { + if source.storagePrefix == destination.storagePrefix { + return + } + + if !skipEventFiles { + moveSourceEventFilesToDestination() + } + + moveUserDefaults() + } + + private func moveUserDefaults() { + moveStringProperty(StorageKey.DEVICE_ID) + moveStringProperty(StorageKey.USER_ID) + moveIntegerProperty(StorageKey.PREVIOUS_SESSION_ID) + moveIntegerProperty(StorageKey.LAST_EVENT_TIME) + moveIntegerProperty(StorageKey.LAST_EVENT_ID) + moveEventsFileKey() + } + + private func moveSourceEventFilesToDestination() { + let sourceEventFiles = source.getEventFiles(includeUnfinished: true) + if sourceEventFiles.count == 0 { + return + } + // Ensure destination directory exists. + _ = destination.getEventsStorageDirectory(createDirectory: true) + + let fileManager = FileManager.default + for sourceEventFile in sourceEventFiles { + var destinationEventFile = sourceEventFile.path.replacingOccurrences(of: "/\(source.eventsFileKey)/", with: "/\(destination.eventsFileKey)/") + if fileManager.fileExists(atPath: destinationEventFile) { + var fileExtension = sourceEventFile.pathExtension + if fileExtension != "" { + fileExtension = ".\(fileExtension)" + } + destinationEventFile = "\((destinationEventFile as NSString).deletingPathExtension)-\(NSUUID().uuidString)\(fileExtension)" + } + do { + try fileManager.moveItem(atPath: sourceEventFile.path, toPath: destinationEventFile) + } catch { + logger?.warn(message: "Can't move \(sourceEventFile) to \(destinationEventFile): \(error)") + } + } + } + + private func moveStringProperty(_ key: StorageKey) { + guard let sourceValue: String = source.read(key: key) else { + return + } + + if destination.read(key: key) == nil { + do { + try destination.write(key: key, value: sourceValue) + } catch { + logger?.warn(message: "can't write destination \(key): \(error)") + } + } + + do { + try source.write(key: key, value: nil) + } catch { + logger?.warn(message: "can't write source \(key): \(error)") + } + } + + private func moveIntegerProperty(_ key: StorageKey) { + guard let sourceValue: Int = source.read(key: key) else { + return + } + + let destinationValue: Int? = destination.read(key: key) + if destinationValue == nil || destinationValue! < sourceValue { + do { + try destination.write(key: key, value: sourceValue) + } catch { + logger?.warn(message: "can't write destination \(key): \(error)") + } + } + + do { + try source.write(key: key, value: nil) + } catch { + logger?.warn(message: "can't clear source \(key): \(error)") + } + } + + private func moveEventsFileKey() { + if let sourceEventFileKey: Int = source.userDefaults?.integer(forKey: source.eventsFileKey) { + let destinationEventFileKey: Int? = destination.userDefaults?.integer(forKey: destination.eventsFileKey) + if destinationEventFileKey == nil || destinationEventFileKey! < sourceEventFileKey { + destination.userDefaults?.set(sourceEventFileKey, forKey: destination.eventsFileKey) + } + } + source.userDefaults?.removeObject(forKey: source.eventsFileKey) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCAmplitude.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCAmplitude.swift new file mode 100644 index 000000000..df9473494 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCAmplitude.swift @@ -0,0 +1,223 @@ +import Foundation + +@objc(Amplitude) +public class ObjCAmplitude: NSObject { + private let amplitude: Amplitude + private var plugins: [ObjCPluginWrapper] = [] + + @objc(initWithConfiguration:) + public static func initWithConfiguration( + configuration: ObjCConfiguration + ) -> ObjCAmplitude { + ObjCAmplitude(configuration: configuration) + } + + @objc(initWithConfiguration:) + public init( + configuration: ObjCConfiguration + ) { + amplitude = Amplitude(configuration: configuration.configuration) + } + + @objc + public var configuration: ObjCConfiguration { + ObjCConfiguration(configuration: amplitude.configuration) + } + + @objc + public var storage: ObjCStorage { + ObjCStorage(amplitude: amplitude) + } + + @objc(track:) + @discardableResult + public func track(event: ObjCBaseEvent) -> ObjCAmplitude { + amplitude.track(event: event.event) + return self + } + + @objc(track:callback:) + @discardableResult + public func track(event: ObjCBaseEvent, callback: ObjCEventCallback?) -> ObjCAmplitude { + amplitude.track(event: event.event, callback: callback == nil ? nil : { (event, code, message) in + callback!(ObjCBaseEvent(event: event), code, message) + }) + return self + } + + @objc(track:options:) + @discardableResult + public func track(event: ObjCBaseEvent, options: ObjCEventOptions?) -> ObjCAmplitude { + amplitude.track(event: event.event, options: options?.options) + return self + } + + @objc(track:options:callback:) + @discardableResult + public func track(event: ObjCBaseEvent, options: ObjCEventOptions?, callback: ObjCEventCallback?) -> ObjCAmplitude { + amplitude.track(event: event.event, options: options?.options, callback: callback == nil ? nil : { (event, code, message) in + callback!(ObjCBaseEvent(event: event), code, message) + }) + return self + } + + @objc(track:eventProperties:) + @discardableResult + public func track(eventType: String, eventProperties: [String: Any]?) -> ObjCAmplitude { + amplitude.track(eventType: eventType, eventProperties: eventProperties) + return self + } + + @objc(track:eventProperties:options:) + @discardableResult + public func track(eventType: String, eventProperties: [String: Any]?, options: ObjCEventOptions?) -> ObjCAmplitude { + amplitude.track(eventType: eventType, eventProperties: eventProperties, options: options?.options) + return self + } + + @objc(identify:) + @discardableResult + public func identify(identify: ObjCIdentify) -> ObjCAmplitude { + amplitude.identify(identify: identify.identify) + return self + } + + @objc(identify:options:) + @discardableResult + public func identify(identify: ObjCIdentify, options: ObjCEventOptions?) -> ObjCAmplitude { + amplitude.identify(identify: identify.identify, options: options?.options) + return self + } + + @objc(groupIdentify:groupName:identify:) + @discardableResult + public func groupIdentify(groupType: String, groupName: String, identify: ObjCIdentify) -> ObjCAmplitude { + amplitude.groupIdentify(groupType: groupType, groupName: groupName, identify: identify.identify) + return self + } + + @objc(groupIdentify:groupName:identify:options:) + @discardableResult + public func groupIdentify(groupType: String, groupName: String, identify: ObjCIdentify, options: ObjCEventOptions?) -> ObjCAmplitude { + amplitude.groupIdentify(groupType: groupType, groupName: groupName, identify: identify.identify, options: options?.options) + return self + } + + @objc(setGroup:groupName:) + @discardableResult + public func setGroup(groupType: String, groupName: String) -> ObjCAmplitude { + amplitude.setGroup(groupType: groupType, groupName: groupName) + return self + } + + @objc(setGroup:groupName:options:) + @discardableResult + public func setGroup(groupType: String, groupName: String, options: ObjCEventOptions?) -> ObjCAmplitude { + amplitude.setGroup(groupType: groupType, groupName: groupName, options: options?.options) + return self + } + + @objc(setGroup:groupNames:) + @discardableResult + public func setGroup(groupType: String, groupNames: [String]) -> ObjCAmplitude { + amplitude.setGroup(groupType: groupType, groupName: groupNames) + return self + } + + @objc(setGroup:groupNames:options:) + @discardableResult + public func setGroup(groupType: String, groupNames: [String], options: ObjCEventOptions?) -> ObjCAmplitude { + amplitude.setGroup(groupType: groupType, groupName: groupNames, options: options?.options) + return self + } + + @objc(revenue:) + @discardableResult + public func revenue(revenue: ObjCRevenue) -> ObjCAmplitude { + amplitude.revenue(revenue: revenue.instance) + return self + } + + @objc(revenue:options:) + @discardableResult + public func revenue(revenue: ObjCRevenue, options: ObjCEventOptions? = nil) -> ObjCAmplitude { + amplitude.revenue(revenue: revenue.instance, options: options?.options) + return self + } + + @objc(add:) + @discardableResult + public func add(plugin: ObjCPlugin) -> ObjCAmplitude { + let wrapper = ObjCPluginWrapper(amplitude: self, wrapped: plugin) + plugins.append(wrapper) + amplitude.add(plugin: wrapper) + return self + } + + @objc(remove:) + @discardableResult + public func remove(plugin: ObjCPlugin) -> ObjCAmplitude { + guard let pluginIndex = plugins.firstIndex(where: { wrapper in wrapper.wrapped == plugin }) else { return self } + let wrapper = plugins[pluginIndex] + plugins.remove(at: pluginIndex) + amplitude.remove(plugin: wrapper) + return self + } + + @objc + @discardableResult + public func flush() -> ObjCAmplitude { + amplitude.flush() + return self + } + + @objc(setUserId:) + @discardableResult + public func setUserId(userId: String?) -> ObjCAmplitude { + amplitude.setUserId(userId: userId) + return self + } + + @objc(setDeviceId:) + @discardableResult + public func setDeviceId(deviceId: String?) -> ObjCAmplitude { + amplitude.setDeviceId(deviceId: deviceId) + return self + } + + @objc + public func getUserId() -> String? { + amplitude.getUserId() + } + + @objc + public func getDeviceId() -> String? { + amplitude.getDeviceId() + } + + @objc + public func getSessionId() -> Int64 { + amplitude.getSessionId() + } + + @objc(setSessionIdWithTimestamp:) + @discardableResult + public func setSessionId(timestamp: Int64) -> ObjCAmplitude { + amplitude.setSessionId(timestamp: timestamp) + return self + } + + @objc(setSessionIdWithDate:) + @discardableResult + public func setSessionId(date: Date) -> ObjCAmplitude { + amplitude.setSessionId(date: date) + return self + } + + @objc + @discardableResult + public func reset() -> ObjCAmplitude { + amplitude.reset() + return self + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCAutocaptureOptions.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCAutocaptureOptions.swift new file mode 100644 index 000000000..a601fe346 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCAutocaptureOptions.swift @@ -0,0 +1,92 @@ +import Foundation + +@objc(AMPAutocaptureOptions) +public final class ObjCAutocaptureOptions: NSObject { + internal var _options: AutocaptureOptions + + public override init() { + _options = AutocaptureOptions() + super.init() + } + + @objc + public convenience init(optionsToUnion: [ObjCAutocaptureOptions]) { + self.init() + for option in optionsToUnion { + formUnion(option) + } + } + + internal convenience init(options: AutocaptureOptions) { + self.init() + _options = options + } + + internal var options: AutocaptureOptions { + get { + return _options + } + set { + _options = newValue + } + } + + @objc + public static let sessions = ObjCAutocaptureOptions(options: .sessions) + + @objc + public static let appLifecycles = ObjCAutocaptureOptions(options: .appLifecycles) + + @objc + public static let screenViews = ObjCAutocaptureOptions(options: .screenViews) + + @objc + public static let elementInteractions = ObjCAutocaptureOptions(options: .elementInteractions) + + // MARK: NSObject + + public override var hash: Int { + return _options.rawValue + } + + public override func isEqual(_ object: Any?) -> Bool { + guard let that = object as? ObjCAutocaptureOptions else { + return false + } + return _options == that._options + } + + // MARK: OptionSet-like behavior + + @objc + public func formUnion(_ other: ObjCAutocaptureOptions) { + _options.formUnion(other._options) + } + + @objc + public func formIntersection(_ other: ObjCAutocaptureOptions) { + _options.formIntersection(other._options) + } + + @objc + public func formSymmetricDifference(_ other: ObjCAutocaptureOptions) { + _options.formSymmetricDifference(other._options) + } + + // MARK: Convenience methods for Objective-C + + @objc + public func contains(_ option: ObjCAutocaptureOptions) -> Bool { + return _options.contains(option._options) + } + + @objc + public func union(_ option: ObjCAutocaptureOptions) -> ObjCAutocaptureOptions { + return ObjCAutocaptureOptions(options: _options.union(option._options)) + } + + @objc + public func intersect(_ option: ObjCAutocaptureOptions) -> ObjCAutocaptureOptions { + return ObjCAutocaptureOptions(options: _options.intersection(option._options)) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCBaseEvent.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCBaseEvent.swift new file mode 100644 index 000000000..95042be7c --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCBaseEvent.swift @@ -0,0 +1,99 @@ +import Foundation + +@objc(AMPBaseEvent) +public class ObjCBaseEvent: ObjCEventOptions { + internal let event: BaseEvent + + internal override var options: EventOptions { + event + } + + @objc(initWithEventType:) + public static func initWithEventType(eventType: String) -> ObjCBaseEvent { + ObjCBaseEvent(eventType: eventType) + } + + @objc(initWithEventType:eventProperties:) + public static func initWithEventType(eventType: String, eventProperties: [String: Any]?) -> ObjCBaseEvent { + ObjCBaseEvent(eventType: eventType, eventProperties: eventProperties) + } + + @objc(initWithEventType:) + public convenience init(eventType: String) { + self.init(event: BaseEvent(eventType: eventType)) + } + + @objc(initWithEventType:eventProperties:) + public convenience init(eventType: String, eventProperties: [String: Any]?) { + self.init(event: BaseEvent(eventType: eventType, eventProperties: eventProperties)) + } + + internal init(event: BaseEvent) { + self.event = event + } + + @objc + public var eventType: String { + event.eventType + } + + @objc + public var eventProperties: ObjCProperties { + ObjCProperties(getter: { key in + guard let eventProperties = self.event.eventProperties else { return nil } + return eventProperties[key] ?? nil + }, setter: { (key, value) in + if self.event.eventProperties == nil { + self.event.eventProperties = [:] + } + self.event.eventProperties![key] = value + }, remover: { key in + self.event.eventProperties?.removeValue(forKey: key) + }) + } + + @objc + public var userProperties: ObjCProperties { + ObjCProperties(getter: { key in + guard let userProperties = self.event.userProperties else { return nil } + return userProperties[key] ?? nil + }, setter: { (key, value) in + if self.event.userProperties == nil { + self.event.userProperties = [:] + } + self.event.userProperties![key] = value + }, remover: { key in + self.event.userProperties?.removeValue(forKey: key) + }) + } + + @objc + public var groups: ObjCProperties { + ObjCProperties(getter: { key in + guard let groups = self.event.groups else { return nil } + return groups[key] ?? nil + }, setter: { (key, value) in + if self.event.groups == nil { + self.event.groups = [:] + } + self.event.groups![key] = value + }, remover: { key in + self.event.groups?.removeValue(forKey: key) + }) + } + + @objc + public var groupProperties: ObjCProperties { + ObjCProperties(getter: { key in + guard let groupProperties = self.event.groupProperties else { return nil } + return groupProperties[key] ?? nil + }, setter: { (key, value) in + if self.event.groupProperties == nil { + self.event.groupProperties = [:] + } + self.event.groupProperties![key] = value + }, remover: { key in + self.event.groupProperties?.removeValue(forKey: key) + }) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCConfiguration.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCConfiguration.swift new file mode 100644 index 000000000..3a7fd0fd7 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCConfiguration.swift @@ -0,0 +1,297 @@ +import Foundation + +@objc(AMPConfiguration) +public class ObjCConfiguration: NSObject { + internal let configuration: Configuration + + @objc(initWithApiKey:) + public static func initWithApiKey(apiKey: String) -> ObjCConfiguration { + ObjCConfiguration(apiKey: apiKey) + } + + @objc(initWithApiKey:instanceName:) + public static func initWithApiKey(apiKey: String, instanceName: String) -> ObjCConfiguration { + ObjCConfiguration(apiKey: apiKey, instanceName: instanceName) + } + + @objc(initWithApiKey:) + public convenience init(apiKey: String) { + self.init(configuration: Configuration(apiKey: apiKey)) + } + + @objc(initWithApiKey:instanceName:) + public convenience init(apiKey: String, instanceName: String) { + self.init(configuration: Configuration(apiKey: apiKey, instanceName: instanceName)) + } + + internal init(configuration: Configuration) { + self.configuration = configuration + } + + @objc + public var apiKey: String { + configuration.apiKey + } + + @objc + public var flushQueueSize: Int { + get { + configuration.flushQueueSize + } + set(value) { + configuration.flushQueueSize = value + } + } + + @objc + public var flushIntervalMillis: Int { + get { + configuration.flushIntervalMillis + } + set(value) { + configuration.flushIntervalMillis = value + } + } + + @objc + public var instanceName: String { + configuration.instanceName + } + + @objc + public var optOut: Bool { + get { + configuration.optOut + } + set(value) { + configuration.optOut = value + } + } + + @objc + public var logLevel: LogLevelEnum { + get { + configuration.logLevel + } + set(value) { + configuration.logLevel = value + configuration.loggerProvider.logLevel = value.rawValue + } + } + + @objc + public var loggerProvider: ObjCLoggerProvider? { + get { + { (logLevel, message) in + switch logLevel { + case LogLevelEnum.ERROR.rawValue: + self.configuration.loggerProvider.error(message: message) + case LogLevelEnum.WARN.rawValue: + self.configuration.loggerProvider.warn(message: message) + case LogLevelEnum.LOG.rawValue: + self.configuration.loggerProvider.log(message: message) + case LogLevelEnum.DEBUG.rawValue: + self.configuration.loggerProvider.debug(message: message) + default: + break + } + } + } + set(value) { + if let value = value { + configuration.loggerProvider = ObjCLoggerProviderWrapper(logLevel: configuration.logLevel, logProvider: value) + } + } + } + + @objc + public var minIdLength: Int { + get { + configuration.minIdLength ?? -1 + } + set(value) { + configuration.minIdLength = value + } + } + + @objc + public var callback: ObjCEventCallback? { + get { + guard let callback = configuration.callback else { return nil } + return { (event, code, message) in callback(event.event, code, message) } + } + set(value) { + if let value = value { + configuration.callback = { (event, code, message) in + value(ObjCBaseEvent(event: event), code, message) + } + } else { + configuration.callback = nil + } + } + } + + @objc + public var partnerId: String? { + get { + configuration.partnerId + } + set(value) { + configuration.partnerId = value + } + } + + @objc + public var flushMaxRetries: Int { + get { + configuration.flushMaxRetries + } + set(value) { + configuration.flushMaxRetries = value + } + } + + @objc + public var useBatch: Bool { + get { + configuration.useBatch + } + set(value) { + configuration.useBatch = value + } + } + + @objc + public var serverZone: ServerZone { + get { + configuration.serverZone + } + set(value) { + configuration.serverZone = value + } + } + + @objc + public var serverUrl: String? { + get { + configuration.serverUrl + } + set(value) { + configuration.serverUrl = value + } + } + + @objc + public var plan: ObjCPlan? { + get { + guard let plan = configuration.plan else { return nil } + return ObjCPlan(plan) + } + set(value) { + configuration.plan = value?.plan + } + } + + @objc + public var ingestionMetadata: ObjCIngestionMetadata? { + get { + guard let ingestionMetadata = configuration.ingestionMetadata else { return nil } + return ObjCIngestionMetadata(ingestionMetadata) + } + set(value) { + configuration.ingestionMetadata = value?.ingestionMetadata + } + } + + @objc + public var enableCoppaControl: Bool { + get { + configuration.enableCoppaControl + } + set(value) { + configuration.enableCoppaControl = value + } + } + + @objc + public var trackingOptions: ObjCTrackingOptions { + get { + ObjCTrackingOptions(configuration.trackingOptions) + } + set(value) { + configuration.trackingOptions = value.options + } + } + + @objc + public var flushEventsOnClose: Bool { + get { + configuration.flushEventsOnClose + } + set(value) { + configuration.flushEventsOnClose = value + } + } + + @objc + public var minTimeBetweenSessionsMillis: Int { + get { + configuration.minTimeBetweenSessionsMillis + } + set(value) { + configuration.minTimeBetweenSessionsMillis = value + } + } + + @objc + @available(*, deprecated, renamed: "autocapture", message: "Please use `autocapture` instead.") + /// The SDK no longer tracks changes to the defaultTracking options after initialization. + public var defaultTracking: ObjCDefaultTrackingOptions { + get { + ObjCDefaultTrackingOptions(configuration.defaultTracking) + } + set(value) { + configuration.defaultTracking = value.options + } + } + + @objc + public var autocapture: ObjCAutocaptureOptions { + get { + ObjCAutocaptureOptions(options: configuration.autocapture) + } + set(value) { + configuration.autocapture = value.options + } + } + + @objc + public var identifyBatchIntervalMillis: Int { + get { + configuration.identifyBatchIntervalMillis + } + set(value) { + configuration.identifyBatchIntervalMillis = value + } + } + + @objc + public var migrateLegacyData: Bool { + get { + configuration.migrateLegacyData + } + set(value) { + configuration.migrateLegacyData = value + } + } + + @objc + public var offline: NSNumber? { + get { + return configuration.offline as NSNumber? + } + set(value) { + configuration.offline = value?.boolValue + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCDeepLinkOpenedEvent.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCDeepLinkOpenedEvent.swift new file mode 100644 index 000000000..c16d9f848 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCDeepLinkOpenedEvent.swift @@ -0,0 +1,38 @@ +import Foundation + +@objc(AMPDeepLinkOpenedEvent) +public class ObjCDeepLinkOpenedEvent: ObjCBaseEvent { + @objc(initWithActivity:) + public static func initWithActivity(activity: NSUserActivity) -> ObjCDeepLinkOpenedEvent { + ObjCDeepLinkOpenedEvent(activity: activity) + } + + @objc(initWithUrl:) + public static func initWithUrl(url: String?) -> ObjCDeepLinkOpenedEvent { + ObjCDeepLinkOpenedEvent(url: url) + } + + @objc(initWithUrl:referrer:) + public static func initWithUrl(url: String?, referrer: String?) -> ObjCDeepLinkOpenedEvent { + ObjCDeepLinkOpenedEvent(url: url, referrer: referrer) + } + + @objc(initWithActivity:) + public convenience init(activity: NSUserActivity) { + self.init(event: DeepLinkOpenedEvent(activity: activity)) + } + + @objc(initWihUrl:) + public convenience init(url: String?) { + self.init(url: url, referrer: nil) + } + + @objc(initWihUrl:referrer:) + public convenience init(url: String?, referrer: String?) { + self.init(event: DeepLinkOpenedEvent(url: url, referrer: referrer)) + } + + internal init(event: DeepLinkOpenedEvent) { + super.init(event: event) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCDefaultTrackingOptions.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCDefaultTrackingOptions.swift new file mode 100644 index 000000000..e3f073469 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCDefaultTrackingOptions.swift @@ -0,0 +1,56 @@ +import Foundation + +@objc(AMPDefaultTrackingOptions) +@available(*, deprecated, renamed: "AMPAutocaptureOptions", message: "Please use `AMPAutocaptureOptions` instead") +public class ObjCDefaultTrackingOptions: NSObject { + internal let options: DefaultTrackingOptions + + @objc + convenience public override init() { + self.init(DefaultTrackingOptions()) + } + + internal init(_ options: DefaultTrackingOptions) { + self.options = options + } + + @objc + public static var ALL: ObjCDefaultTrackingOptions { + ObjCDefaultTrackingOptions(DefaultTrackingOptions.ALL) + } + + @objc + public static var NONE: ObjCDefaultTrackingOptions { + ObjCDefaultTrackingOptions(DefaultTrackingOptions.NONE) + } + + @objc + public var sessions: Bool { + get { + options.sessions + } + set(value) { + options.sessions = value + } + } + + @objc + public var appLifecycles: Bool { + get { + options.appLifecycles + } + set(value) { + options.appLifecycles = value + } + } + + @objc + public var screenViews: Bool { + get { + options.screenViews + } + set(value) { + options.screenViews = value + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCEventOptions.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCEventOptions.swift new file mode 100644 index 000000000..028a7784b --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCEventOptions.swift @@ -0,0 +1,401 @@ +import Foundation + +public typealias ObjCEventCallback = (ObjCBaseEvent, Int, String) -> Void + +@objc(AMPEventOptions) +public class ObjCEventOptions: NSObject { + internal let _options = EventOptions() + + internal var options: EventOptions { + _options + } + + @objc + public var userId: String? { + get { + options.userId + } + set(value) { + options.userId = value + } + } + + @objc + public var deviceId: String? { + get { + options.deviceId + } + set(value) { + options.deviceId = value + } + } + + @objc + public var timestamp: Int64 { + get { + options.timestamp ?? -1 + } + set(value) { + options.timestamp = value + } + } + + @objc + public var eventId: Int64 { + get { + options.eventId ?? -1 + } + set(value) { + options.eventId = value + } + } + + @objc + public var sessionId: Int64 { + get { + options.sessionId ?? -1 + } + set(value) { + options.sessionId = value + } + } + + @objc + public var insertId: String? { + get { + options.insertId + } + set(value) { + options.insertId = value + } + } + + @objc + public var locationLat: Double { + get { + options.locationLat ?? Double.nan + } + set(value) { + options.locationLat = value.isNaN ? nil : value + } + } + + @objc + public var locationLng: Double { + get { + options.locationLng ?? Double.nan + } + set(value) { + options.locationLng = value.isNaN ? nil : value + } + } + + @objc + public var appVersion: String? { + get { + options.appVersion + } + set(value) { + options.appVersion = value + } + } + + @objc + public var versionName: String? { + get { + options.versionName + } + set(value) { + options.versionName = value + } + } + + @objc + public var platform: String? { + get { + options.platform + } + set(value) { + options.platform = value + } + } + + @objc + public var osName: String? { + get { + options.osName + } + set(value) { + options.osName = value + } + } + + @objc + public var osVersion: String? { + get { + options.osVersion + } + set(value) { + options.osVersion = value + } + } + + @objc + public var deviceBrand: String? { + get { + options.deviceBrand + } + set(value) { + options.deviceBrand = value + } + } + + @objc + public var deviceManufacturer: String? { + get { + options.deviceManufacturer + } + set(value) { + options.deviceManufacturer = value + } + } + + @objc + public var deviceModel: String? { + get { + options.deviceModel + } + set(value) { + options.deviceModel = value + } + } + + @objc + public var carrier: String? { + get { + options.carrier + } + set(value) { + options.carrier = value + } + } + + @objc + public var country: String? { + get { + options.country + } + set(value) { + options.country = value + } + } + + @objc + public var region: String? { + get { + options.region + } + set(value) { + options.region = value + } + } + + @objc + public var city: String? { + get { + options.city + } + set(value) { + options.city = value + } + } + + @objc + public var dma: String? { + get { + options.dma + } + set(value) { + options.dma = value + } + } + + @objc + public var idfa: String? { + get { + options.idfa + } + set(value) { + options.idfa = value + } + } + + @objc + public var idfv: String? { + get { + options.idfv + } + set(value) { + options.idfv = value + } + } + + @objc + public var adid: String? { + get { + options.adid + } + set(value) { + options.adid = value + } + } + + @objc + public var language: String? { + get { + options.language + } + set(value) { + options.language = value + } + } + + @objc + public var library: String? { + get { + options.library + } + set(value) { + options.library = value + } + } + + @objc + public var ip: String? { + get { + options.ip + } + set(value) { + options.ip = value + } + } + + @objc + public var plan: ObjCPlan? { + get { + guard let plan = options.plan else { return nil } + return ObjCPlan(plan) + } + set(value) { + options.plan = value?.plan + } + } + + @objc + public var ingestionMetadata: ObjCIngestionMetadata? { + get { + guard let ingestionMetadata = options.ingestionMetadata else { return nil } + return ObjCIngestionMetadata(ingestionMetadata) + } + set(value) { + options.ingestionMetadata = value?.ingestionMetadata + } + } + + @objc + public var revenue: Double { + get { + options.revenue ?? Double.nan + } + set(value) { + options.revenue = value.isNaN ? nil : value + } + } + + @objc + public var price: Double { + get { + options.price ?? Double.nan + } + set(value) { + options.price = value.isNaN ? nil : value + } + } + + @objc + public var quantity: Int { + get { + options.quantity ?? -1 + } + set(value) { + options.quantity = value + } + } + + @objc + public var productId: String? { + get { + options.productId + } + set(value) { + options.productId = value + } + } + + @objc + public var revenueType: String? { + get { + options.revenueType + } + set(value) { + options.revenueType = value + } + } + + @objc + public var extra: ObjCProperties { + ObjCProperties(getter: { key in + guard let extra = self.options.extra else { return nil } + return extra[key] + }, setter: { (key, value) in + if self.options.extra == nil { + self.options.extra = [:] + } + self.options.extra![key] = value + }, remover: { key in + self.options.extra?.removeValue(forKey: key) + }) + } + + @objc + public var callback: ObjCEventCallback? { + get { + guard let callback = options.callback else { return nil } + return { (event, code, message) in callback(event.event, code, message) } + } + set(value) { + if let value = value { + options.callback = { (event, code, message) in + value(ObjCBaseEvent(event: event), code, message) + } + } else { + options.callback = nil + } + } + } + + @objc + public var partnerId: String? { + get { + options.partnerId + } + set(value) { + options.partnerId = value + } + } + + @objc(mergeEventOptions:) + public func mergeEventOptions(other: ObjCEventOptions) { + options.mergeEventOptions(eventOptions: other.options) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCIdentify.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCIdentify.swift new file mode 100644 index 000000000..80e91f241 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCIdentify.swift @@ -0,0 +1,97 @@ +import Foundation + +@objc(AMPIdentify) +public class ObjCIdentify: NSObject { + internal let identify = Identify() + + @objc(set:value:) + @discardableResult + public func set(property: String, value: Any?) -> ObjCIdentify { + identify.set(property: property, value: value) + return self + } + + @objc(setOnce:value:) + @discardableResult + public func setOnce(property: String, value: Any?) -> ObjCIdentify { + identify.setOnce(property: property, value: value) + return self + } + + @objc(prepend:value:) + @discardableResult + public func prepend(property: String, value: Any?) -> ObjCIdentify { + identify.prepend(property: property, value: value) + return self + } + + @objc(append:value:) + @discardableResult + public func append(property: String, value: Any?) -> ObjCIdentify { + identify.append(property: property, value: value) + return self + } + + @objc(postInsert:value:) + @discardableResult + public func postInsert(property: String, value: Any?) -> ObjCIdentify { + identify.postInsert(property: property, value: value) + return self + } + + @objc(preInsert:value:) + @discardableResult + public func preInsert(property: String, value: Any?) -> ObjCIdentify { + identify.preInsert(property: property, value: value) + return self + } + + @objc(remove:value:) + @discardableResult + public func remove(property: String, value: Any?) -> ObjCIdentify { + identify.remove(property: property, value: value) + return self + } + + @objc(add:valueInt:) + @discardableResult + public func add(property: String, value: Int) -> ObjCIdentify { + identify.add(property: property, value: value) + return self + } + + @objc(add:valueInt64:) + @discardableResult + public func add(property: String, value: Int64) -> ObjCIdentify { + identify.add(property: property, value: value) + return self + } + + @objc(add:valueDouble:) + @discardableResult + public func add(property: String, value: Double) -> ObjCIdentify { + identify.add(property: property, value: value) + return self + } + + @objc(add:valueFloat:) + @discardableResult + public func add(property: String, value: Float) -> ObjCIdentify { + identify.add(property: property, value: value) + return self + } + + @objc(unset:) + @discardableResult + public func unset(property: String) -> ObjCIdentify { + identify.unset(property: property) + return self + } + + @objc + @discardableResult + public func clearAll() -> ObjCIdentify { + identify.clearAll() + return self + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCIngestionMetadata.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCIngestionMetadata.swift new file mode 100644 index 000000000..b02fcf34c --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCIngestionMetadata.swift @@ -0,0 +1,35 @@ +import Foundation + +@objc(AMPIngestionMetadata) +public class ObjCIngestionMetadata: NSObject { + internal var ingestionMetadata: IngestionMetadata + + @objc + convenience public override init() { + self.init(IngestionMetadata()) + } + + internal init(_ ingestionMetadata: IngestionMetadata) { + self.ingestionMetadata = ingestionMetadata + } + + @objc + public var sourceName: String? { + get { + ingestionMetadata.sourceName + } + set(value) { + ingestionMetadata.sourceName = value + } + } + + @objc + public var sourceVersion: String? { + get { + ingestionMetadata.sourceVersion + } + set(value) { + ingestionMetadata.sourceVersion = value + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCLoggerProvider.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCLoggerProvider.swift new file mode 100644 index 000000000..dd916fdcd --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCLoggerProvider.swift @@ -0,0 +1,42 @@ +import Foundation + +public typealias ObjCLoggerProvider = (Int, String) -> Void + +class ObjCLoggerProviderWrapper: Logger { + public typealias LogLevel = LogLevelEnum + public var logLevel: Int + + private let logProvider: ObjCLoggerProvider + + init( + logLevel: LogLevelEnum, + logProvider: @escaping ObjCLoggerProvider + ) { + self.logLevel = logLevel.rawValue + self.logProvider = logProvider + } + + func error(message: String) { + if logLevel >= LogLevelEnum.ERROR.rawValue { + logProvider(logLevel, message) + } + } + + func warn(message: String) { + if logLevel >= LogLevelEnum.WARN.rawValue { + logProvider(logLevel, message) + } + } + + func log(message: String) { + if logLevel >= LogLevelEnum.LOG.rawValue { + logProvider(logLevel, message) + } + } + + func debug(message: String) { + if logLevel >= LogLevelEnum.DEBUG.rawValue { + logProvider(logLevel, message) + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCNetworkConnectivityCheckerPlugin.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCNetworkConnectivityCheckerPlugin.swift new file mode 100644 index 000000000..430d9b808 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCNetworkConnectivityCheckerPlugin.swift @@ -0,0 +1,6 @@ +import Foundation + +@objc(AMPNetworkConnectivityCheckerPlugin) +public class ObjCNetworkConnectivityCheckerPlugin: NSObject { + @objc public static let Disabled: NSNumber? = nil +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCPlan.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCPlan.swift new file mode 100644 index 000000000..de7effc51 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCPlan.swift @@ -0,0 +1,55 @@ +import Foundation + +@objc(AMPPlan) +public class ObjCPlan: NSObject { + internal var plan: Plan + + @objc + convenience public override init() { + self.init(Plan()) + } + + internal init(_ plan: Plan) { + self.plan = plan + } + + @objc + public var branch: String? { + get { + plan.branch + } + set(value) { + plan.branch = value + } + } + + @objc + public var source: String? { + get { + plan.source + } + set(value) { + plan.source = value + } + } + + @objc + public var version: String? { + get { + plan.version + } + set(value) { + plan.version = value + } + } + + @objc + public var versionId: String? { + get { + plan.versionId + } + set(value) { + plan.versionId = value + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCPlugin.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCPlugin.swift new file mode 100644 index 000000000..1d0d91652 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCPlugin.swift @@ -0,0 +1,131 @@ +import Foundation + +@objc(AMPPlugin) +public class ObjCPlugin: NSObject { + internal let type: PluginType + internal let setup: ((ObjCAmplitude) -> Void)? + internal let execute: (ObjCBaseEvent) -> ObjCBaseEvent? + internal let flush: (() -> Void)? + + @objc(initWithType:setup:execute:) + public static func initWithType( + type: PluginType, + setup: @escaping (ObjCAmplitude) -> Void, + execute: @escaping (ObjCBaseEvent) -> ObjCBaseEvent? + ) -> ObjCPlugin { + ObjCPlugin(type: type, setup: setup, execute: execute) + } + + @objc(initWithType:execute:) + public static func initWithType( + type: PluginType, + execute: @escaping (ObjCBaseEvent) -> ObjCBaseEvent? + ) -> ObjCPlugin { + ObjCPlugin(type: type, execute: execute) + } + + @objc(initWithType:setup:execute:flush:) + public static func initWithType( + type: PluginType, + setup: @escaping (ObjCAmplitude) -> Void, + execute: @escaping (ObjCBaseEvent) -> ObjCBaseEvent?, + flush: @escaping () -> Void + ) -> ObjCPlugin { + ObjCPlugin(type: type, setup: setup, execute: execute, flush: flush) + } + + @objc(initWithType:execute:flush:) + public static func initWithType( + type: PluginType, + execute: @escaping (ObjCBaseEvent) -> ObjCBaseEvent?, + flush: @escaping () -> Void + ) -> ObjCPlugin { + ObjCPlugin(type: type, execute: execute, flush: flush) + } + + @objc(initWithType:setup:execute:) + public init( + type: PluginType, + setup: @escaping (ObjCAmplitude) -> Void, + execute: @escaping (ObjCBaseEvent) -> ObjCBaseEvent? + ) { + self.type = type + self.setup = setup + self.execute = execute + self.flush = nil + } + + @objc(initWithType:execute:) + public init(type: PluginType, execute: @escaping (ObjCBaseEvent) -> ObjCBaseEvent?) { + self.type = type + self.setup = nil + self.execute = execute + self.flush = nil + } + + @objc(initWithType:setup:execute:flush:) + public init( + type: PluginType, + setup: @escaping (ObjCAmplitude) -> Void, + execute: @escaping (ObjCBaseEvent) -> ObjCBaseEvent?, + flush: @escaping () -> Void + ) { + self.type = type + self.setup = setup + self.execute = execute + self.flush = flush + } + + @objc(initWithType:execute:flush:) + public init( + type: PluginType, + execute: @escaping (ObjCBaseEvent) -> ObjCBaseEvent?, + flush: @escaping() -> Void) + { + self.type = type + self.setup = nil + self.execute = execute + self.flush = flush + } +} + +class ObjCPluginWrapper: Plugin, EventPlugin { + weak var amplitude: ObjCAmplitude? + let type: PluginType + let wrapped: ObjCPlugin + + init(amplitude: ObjCAmplitude, wrapped: ObjCPlugin) { + self.amplitude = amplitude + self.type = wrapped.type + self.wrapped = wrapped + } + + func setup(amplitude: Amplitude) { + guard let amplitude = self.amplitude else { return } + wrapped.setup?(amplitude) + } + + func execute(event: BaseEvent) -> BaseEvent? { + wrapped.execute(ObjCBaseEvent(event: event))?.event + } + + func track(event: BaseEvent) -> BaseEvent? { + execute(event: event) + } + + func identify(event: IdentifyEvent) -> IdentifyEvent? { + execute(event: event) as? IdentifyEvent + } + + func groupIdentify(event: GroupIdentifyEvent) -> GroupIdentifyEvent? { + execute(event: event) as? GroupIdentifyEvent + } + + func revenue(event: RevenueEvent) -> RevenueEvent? { + execute(event: event) as? RevenueEvent + } + + func flush() { + wrapped.flush?() + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCProperties.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCProperties.swift new file mode 100644 index 000000000..779910d5f --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCProperties.swift @@ -0,0 +1,37 @@ +import Foundation + +@objc(AMPProperties) +public class ObjCProperties: NSObject { + internal let getter: (String) -> Any? + internal let setter: (String, Any) -> Void + internal let remover: (String) -> Void + + internal init( + getter: @escaping (String) -> Any?, + setter: @escaping (String, Any) -> Void, + remover: @escaping (String) -> Void + ) { + self.getter = getter + self.setter = setter + self.remover = remover + } + + @objc(get:) + public func get(key: String) -> Any? { + getter(key) + } + + @objc(set:value:) + @discardableResult + public func set(key: String, value: Any) -> ObjCProperties { + setter(key, value) + return self + } + + @objc(remove:) + @discardableResult + public func remove(key: String) -> ObjCProperties { + remover(key) + return self + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCRevenue.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCRevenue.swift new file mode 100644 index 000000000..01e913302 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCRevenue.swift @@ -0,0 +1,98 @@ +import Foundation + +@objc(AMPRevenue) +public class ObjCRevenue: NSObject { + internal let instance = Revenue() + + @objc + public var productId: String? { + get { + instance.productId + } + set(value) { + instance.productId = value + } + } + + @objc + public var quantity: Int { + get { + instance.quantity + } + set(value) { + instance.quantity = value + } + } + + @objc + public var price: Double { + get { + instance.price ?? Double.nan + } + set(value) { + instance.price = value.isNaN ? nil : value + } + } + + @objc + public var revenue: Double { + get { + instance.revenue ?? Double.nan + } + set(value) { + instance.revenue = value.isNaN ? nil : value + } + } + + @objc + public var revenueType: String? { + get { + instance.revenueType + } + set(value) { + instance.revenueType = value + } + } + + @objc + public var receipt: String? { + get { + instance.receipt + } + set(value) { + instance.receipt = value + } + } + + @objc + public var receiptSig: String? { + get { + instance.receiptSig + } + set(value) { + instance.receiptSig = value + } + } + + @objc + public var properties: ObjCProperties { + ObjCProperties(getter: { key in + guard let properties = self.instance.properties else { return nil } + return properties[key] ?? nil + }, setter: { (key, value) in + if self.instance.properties == nil { + self.instance.properties = [:] + } + self.instance.properties![key] = value + }, remover: { key in + self.instance.properties?.removeValue(forKey: key) + }) + } + + @objc(setReceipt:receiptSignature:) + @discardableResult + public func setReceipt(receipt: String, receiptSignature: String) -> ObjCRevenue { + instance.setReceipt(receipt: receipt, receiptSignature: receiptSignature) + return self + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCScreenViewedEvent.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCScreenViewedEvent.swift new file mode 100644 index 000000000..b3bd5c962 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCScreenViewedEvent.swift @@ -0,0 +1,14 @@ +import Foundation + +@objc(AMPScreenViewedEvent) +public class ObjCScreenViewedEvent: ObjCBaseEvent { + @objc(initWithScreenName:) + public static func initWithScreenName(screenName: String) -> ObjCScreenViewedEvent { + ObjCScreenViewedEvent(screenName: screenName) + } + + @objc(initWithScreenName:) + public convenience init(screenName: String) { + self.init(event: ScreenViewedEvent(screenName: screenName)) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCStorage.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCStorage.swift new file mode 100644 index 000000000..090e3f1b3 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCStorage.swift @@ -0,0 +1,29 @@ +import Foundation + +@objc(AMPStorage) +public class ObjCStorage: NSObject { + private weak var amplitude: Amplitude? + + internal init(amplitude: Amplitude) { + self.amplitude = amplitude + } + + @objc + public func getEventsStrings() -> [String] { + guard let storage = amplitude?.storage else { return [] } + return getEventsStrings(storage: storage) + } + + @objc + public func getInterceptedIdentifiesStrings() -> [String] { + guard let storage = amplitude?.identifyStorage else { return [] } + return getEventsStrings(storage: storage) + } + + private func getEventsStrings(storage: Storage) -> [String] { + guard let eventURLs: [URL] = storage.read(key: StorageKey.EVENTS) else { return [] } + return eventURLs.map { eventURL in + storage.getEventsString(eventBlock: eventURL) + }.compactMap { $0 } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCTrackingOptions.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCTrackingOptions.swift new file mode 100644 index 000000000..76eabf6c4 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/ObjC/ObjCTrackingOptions.swift @@ -0,0 +1,183 @@ +import Foundation + +@objc(AMPTrackingOptions) +public class ObjCTrackingOptions: NSObject { + internal let options: TrackingOptions + + @objc + convenience public override init() { + self.init(TrackingOptions()) + } + + internal init(_ options: TrackingOptions) { + self.options = options + } + + @objc + public func shouldTrackVersionName() -> Bool { + options.shouldTrackVersionName() + } + + @objc + @discardableResult + public func disableTrackVersionName() -> ObjCTrackingOptions { + options.disableTrackVersionName() + return self + } + + @objc + public func shouldTrackOsName() -> Bool { + options.shouldTrackOsName() + } + + @objc + @discardableResult + public func disableTrackOsName() -> ObjCTrackingOptions { + options.disableTrackOsName() + return self + } + + @objc + public func shouldTrackOsVersion() -> Bool { + options.shouldTrackOsVersion() + } + + @objc + @discardableResult + public func disableTrackOsVersion() -> ObjCTrackingOptions { + options.disableTrackOsVersion() + return self + } + + @objc + public func shouldTrackDeviceManufacturer() -> Bool { + options.shouldTrackDeviceManufacturer() + } + + @objc + @discardableResult + public func disableTrackDeviceManufacturer() -> ObjCTrackingOptions { + options.disableTrackDeviceManufacturer() + return self + } + + @objc + public func shouldTrackDeviceModel() -> Bool { + options.shouldTrackDeviceModel() + } + + @objc + @discardableResult + public func disableTrackDeviceModel() -> ObjCTrackingOptions { + options.disableTrackDeviceModel() + return self + } + + @objc + public func shouldTrackCarrier() -> Bool { + options.shouldTrackCarrier() + } + + @objc + @discardableResult + public func disableTrackCarrier() -> ObjCTrackingOptions { + options.disableTrackCarrier() + return self + } + + @objc + public func shouldTrackIpAddress() -> Bool { + options.shouldTrackIpAddress() + } + + @objc + @discardableResult + public func disableTrackIpAddress() -> ObjCTrackingOptions { + options.disableTrackIpAddress() + return self + } + + @objc + public func shouldTrackCountry() -> Bool { + options.shouldTrackCountry() + } + + @objc + @discardableResult + public func disableTrackCountry() -> ObjCTrackingOptions { + options.disableTrackCountry() + return self + } + + @objc + public func shouldTrackCity() -> Bool { + options.shouldTrackCity() + } + + @objc + @discardableResult + public func disableTrackCity() -> ObjCTrackingOptions { + options.disableTrackCity() + return self + } + + @objc + public func shouldTrackDMA() -> Bool { + options.shouldTrackDMA() + } + + @objc + @discardableResult + public func disableTrackDMA() -> ObjCTrackingOptions { + options.disableTrackDMA() + return self + } + + @objc + public func shouldTrackIDFV() -> Bool { + options.shouldTrackIDFV() + } + + @objc + @discardableResult + public func disableTrackIDFV() -> ObjCTrackingOptions { + options.disableTrackIDFV() + return self + } + + @objc + public func shouldTrackLanguage() -> Bool { + options.shouldTrackLanguage() + } + + @objc + @discardableResult + public func disableTrackLanguage() -> ObjCTrackingOptions { + options.disableTrackLanguage() + return self + } + + @objc + public func shouldTrackRegion() -> Bool { + options.shouldTrackRegion() + } + + @objc + @discardableResult + public func disableTrackRegion() -> ObjCTrackingOptions { + options.disableTrackRegion() + return self + } + + @objc + public func shouldTrackPlatform() -> Bool { + options.shouldTrackPlatform() + } + + @objc + @discardableResult + public func disableTrackPlatform() -> ObjCTrackingOptions { + options.disableTrackPlatform() + return self + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/AmplitudeDestinationPlugin.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/AmplitudeDestinationPlugin.swift new file mode 100644 index 000000000..e4a26c12a --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/AmplitudeDestinationPlugin.swift @@ -0,0 +1,62 @@ +// +// AmplitudeDestinationPlugin.swift +// +// +// Created by Marvin Liu on 10/27/22. +// + +public class AmplitudeDestinationPlugin: DestinationPlugin { + private var pipeline: EventPipeline? + private var identifyInterceptor: IdentifyInterceptor? + + internal func enqueue(event: BaseEvent?) { + if let event { + if event.isValid() { + let e = identifyInterceptor?.intercept(event: event) + if let e { + pipeline?.put(event: e) + } + } else { + logger?.error(message: "Event is invalid for missing information like userId and deviceId") + } + } + } + + public override func track(event: BaseEvent) -> BaseEvent? { + enqueue(event: event) + return event + } + + public override func identify(event: IdentifyEvent) -> IdentifyEvent? { + enqueue(event: event) + return event + } + + public override func groupIdentify(event: GroupIdentifyEvent) -> GroupIdentifyEvent? { + enqueue(event: event) + return event + } + + public override func revenue(event: RevenueEvent) -> RevenueEvent? { + enqueue(event: event) + return event + } + + public override func flush() { + identifyInterceptor?.transferInterceptedIdentifyEvent() + pipeline?.flush() + } + + public override func setup(amplitude: Amplitude) { + super.setup(amplitude: amplitude) + pipeline = EventPipeline(amplitude: amplitude) + identifyInterceptor = IdentifyInterceptor( + configuration: amplitude.configuration, + pipeline: pipeline!, + queue: amplitude.trackingQueue + ) + pipeline?.start() + + add(plugin: IdentityEventSender()) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/AnalyticsConnectorIdentityPlugin.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/AnalyticsConnectorIdentityPlugin.swift new file mode 100644 index 000000000..fafa71850 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/AnalyticsConnectorIdentityPlugin.swift @@ -0,0 +1,23 @@ +import Foundation +import AnalyticsConnector + +class AnalyticsConnectorIdentityPlugin: ObservePlugin { + private var connector: AnalyticsConnector? + + override func setup(amplitude: Amplitude) { + super.setup(amplitude: amplitude) + connector = AnalyticsConnector.getInstance(amplitude.configuration.instanceName) + connector?.identityStore.editIdentity() + .setUserId(amplitude.getUserId()) + .setDeviceId(amplitude.getDeviceId()) + .commit() + } + + override func onUserIdChanged(_ userId: String?) { + connector?.identityStore.editIdentity().setUserId(userId).commit() + } + + override func onDeviceIdChanged(_ deviceId: String?) { + connector?.identityStore.editIdentity().setDeviceId(deviceId).commit() + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/AnalyticsConnectorPlugin.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/AnalyticsConnectorPlugin.swift new file mode 100644 index 000000000..bf4ca5dcb --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/AnalyticsConnectorPlugin.swift @@ -0,0 +1,35 @@ +import Foundation +import AnalyticsConnector + +class AnalyticsConnectorPlugin: BeforePlugin { + private static let EXPOSURE_EVENT = "$exposure" + private var connector: AnalyticsConnector? + + override func setup(amplitude: Amplitude) { + super.setup(amplitude: amplitude) + connector = AnalyticsConnector.getInstance(amplitude.configuration.instanceName) + let logger = amplitude.logger + connector!.eventBridge.setEventReceiver { [weak amplitude, logger] event in + if let amplitude = amplitude { + amplitude.track(event: BaseEvent( + eventType: event.eventType, + eventProperties: event.eventProperties as? [String: Any], + userProperties: event.userProperties as? [String: Any] + )) + } else { + logger?.error(message: "Amplitude instance has been deallocated, please maintain a strong reference to track events from Experiment") + } + } + } + + override func execute(event: BaseEvent) -> BaseEvent? { + guard let userProperties = event.userProperties else { + return event + } + if userProperties.count == 0 || event.eventType == AnalyticsConnectorPlugin.EXPOSURE_EVENT { + return event + } + connector?.identityStore.editIdentity().updateUserProperties(userProperties as NSDictionary).commit() + return event + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/BasePlugins.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/BasePlugins.swift new file mode 100644 index 000000000..e1b63376b --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/BasePlugins.swift @@ -0,0 +1,39 @@ +import Foundation + +open class BasePlugin { + weak public private(set) var amplitude: Amplitude? + + public init() { + } + + open func setup(amplitude: Amplitude) { + self.amplitude = amplitude + } + + open func execute(event: BaseEvent) -> BaseEvent? { + return event + } + + public func teardown(){ + // Clean up any resources from setup if necessary + } +} + +open class BeforePlugin: BasePlugin, Plugin { + public let type: PluginType = .before +} + +open class EnrichmentPlugin: BasePlugin, Plugin { + public let type: PluginType = .enrichment +} + +open class UtilityPlugin: BasePlugin, Plugin { + public let type: PluginType = .utility +} + +open class ObservePlugin: BasePlugin, Plugin { + public let type: PluginType = .observe + + open func onUserIdChanged(_ userId: String?) {} + open func onDeviceIdChanged(_ deviceId: String?) {} +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/ContextPlugin.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/ContextPlugin.swift new file mode 100644 index 000000000..62d37b7f7 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/ContextPlugin.swift @@ -0,0 +1,182 @@ +// +// ContextPlugin.swift +// +// +// Created by Marvin Liu on 10/28/22. +// + +#if os(iOS) +import CoreTelephony +#endif +import Foundation + +class ContextPlugin: BeforePlugin { + internal static var device = VendorSystem.current + + override func setup(amplitude: Amplitude) { + super.setup(amplitude: amplitude) + initializeDeviceId() + } + + override func execute(event: BaseEvent) -> BaseEvent? { + let context = staticContext + + // merge context data + mergeContext(event: event, context: context) + + return event + } + + internal var staticContext = staticContextData() + + internal static func staticContextData() -> [String: Any] { + var staticContext = [String: Any]() + // library + staticContext["library"] = "\(Constants.SDK_LIBRARY)/\(Constants.SDK_VERSION)" + + // app info + let info = Bundle.main.infoDictionary + let localizedInfo = Bundle.main.localizedInfoDictionary + var app = [String: Any]() + if let info = info { + app.merge(info) { (_, new) in new } + } + + if let localizedInfo = localizedInfo { + app.merge(localizedInfo) { (_, new) in new } + } + + if app.count != 0 { + staticContext["version_name"] = app["CFBundleShortVersionString"] ?? "" + } + + // platform/device info + let device = self.device + staticContext["device_manufacturer"] = device.manufacturer + staticContext["device_model"] = device.model + staticContext["idfv"] = device.identifierForVendor + staticContext["os_name"] = device.os_name + staticContext["os_version"] = device.os_version + staticContext["platform"] = device.platform + if Locale.preferredLanguages.count > 0 { + staticContext["language"] = Locale.preferredLanguages[0] + } + + var carrier = "Unknown" + #if os(iOS) && !targetEnvironment(simulator) + let networkInfo = CTTelephonyNetworkInfo() + if let providers = networkInfo.serviceSubscriberCellularProviders { + for (_, provider) in providers where provider.mobileNetworkCode != nil { + carrier = provider.carrierName ?? carrier + // As long as we get one carrier information, we break. + break + } + } + #endif + staticContext["carrier"] = carrier + + if Locale.preferredLanguages.count > 0 { + staticContext["country"] = Locale.current.regionCode + } + + return staticContext + } + + internal func mergeContext(event: BaseEvent, context: [String: Any]) { + if event.insertId == nil { + event.insertId = NSUUID().uuidString + } + if event.library == nil { + event.library = context["library"] as? String + } + if event.userId == nil { + event.userId = self.amplitude?.state.userId + } + if event.deviceId == nil { + event.deviceId = self.amplitude?.state.deviceId + } + if event.partnerId == nil { + if let pId = self.amplitude?.configuration.partnerId { + event.partnerId = pId + } + } + let configuration = self.amplitude?.configuration + let trackingOptions = configuration?.trackingOptions + + if configuration?.enableCoppaControl ?? false { + trackingOptions?.mergeIn(other: TrackingOptions.forCoppaControl()) + } + + if trackingOptions?.shouldTrackVersionName() ?? false { + event.versionName = context["version_name"] as? String + } + if trackingOptions?.shouldTrackOsName() ?? false { + event.osName = context["os_name"] as? String + } + if trackingOptions?.shouldTrackOsVersion() ?? false { + event.osVersion = context["os_version"] as? String + } + if trackingOptions?.shouldTrackDeviceManufacturer() ?? false { + event.deviceManufacturer = context["device_manufacturer"] as? String + } + if trackingOptions?.shouldTrackDeviceModel() ?? false { + event.deviceModel = context["device_model"] as? String + } + if trackingOptions?.shouldTrackCarrier() ?? false { + event.carrier = context["carrier"] as? String + } + if trackingOptions?.shouldTrackIpAddress() ?? false { + if event.ip == nil { + // get the ip in server side if there is no event level ip + event.ip = "$remote" + } + } + if trackingOptions?.shouldTrackCountry() ?? false && event.ip != "$remote" { + event.country = context["country"] as? String + } + if trackingOptions?.shouldTrackIDFV() ?? false { + event.idfv = context["idfv"] as? String + } + if trackingOptions?.shouldTrackLanguage() ?? false { + event.language = context["language"] as? String + } + if trackingOptions?.shouldTrackPlatform() ?? false { + event.platform = context["platform"] as? String + } + if event.plan == nil { + if let plan = self.amplitude?.configuration.plan { + event.plan = plan + } + } + if event.ingestionMetadata == nil { + if let ingestionMetadata = self.amplitude?.configuration.ingestionMetadata { + event.ingestionMetadata = ingestionMetadata + } + } + } + + func initializeDeviceId(forceReset: Bool = false) { + var deviceId = forceReset ? nil : amplitude?.state.deviceId + if isValidDeviceId(deviceId) { + return + } + if deviceId == nil, amplitude?.configuration.trackingOptions.shouldTrackIDFV() ?? false { + if let idfv = staticContext["idfv"] as? String, idfv != "00000000-0000-0000-0000-000000000000" { + deviceId = idfv + } + } + if deviceId == nil { + deviceId = NSUUID().uuidString + } + amplitude?.setDeviceId(deviceId: deviceId) + } + + func isValidDeviceId(_ deviceId: String?) -> Bool { + if deviceId == nil || deviceId == "e3f5536a141811db40efd6400f1d0a4e" + || deviceId == "04bab7ee75b9a58d39b8dc54e8851084" + { + return false + } + return true + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/DestinationPlugin.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/DestinationPlugin.swift new file mode 100644 index 000000000..4a1d1acd0 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/DestinationPlugin.swift @@ -0,0 +1,76 @@ +// +// DestinationPlugin.swift +// +// +// Created by Hao Yu on 11/15/22. +// + +open class DestinationPlugin: BasePlugin, EventPlugin { + public let type: PluginType = .destination + private let timeline = Timeline() + + open func track(event: BaseEvent) -> BaseEvent? { + return event + } + + open func identify(event: IdentifyEvent) -> IdentifyEvent? { + return event + } + + open func groupIdentify(event: GroupIdentifyEvent) -> GroupIdentifyEvent? { + return event + } + + open func revenue(event: RevenueEvent) -> RevenueEvent? { + return event + } + + open func flush() { + } + + open override func execute(event: BaseEvent) -> BaseEvent? { + // Skip this destination if it is disabled via settings + if !enabled { + return nil + } + let beforeResult = timeline.applyPlugin(pluginType: .before, event: event) + let enrichmentResult = timeline.applyPlugin(pluginType: .enrichment, event: beforeResult) + var destinationResult: BaseEvent? + switch enrichmentResult { + case let e as IdentifyEvent: + destinationResult = identify(event: e) + case let e as GroupIdentifyEvent: + destinationResult = track(event: e) + case let e as RevenueEvent: + destinationResult = revenue(event: e) + default: + destinationResult = track(event: event) + } + return destinationResult + } +} + +extension DestinationPlugin { + var enabled: Bool { + return true + } + + var logger: (any Logger)? { + return self.amplitude?.logger + } + + @discardableResult + func add(plugin: Plugin) -> Plugin { + plugin.setup(amplitude: amplitude!) + timeline.add(plugin: plugin) + return plugin + } + + func remove(plugin: Plugin) { + timeline.remove(plugin: plugin) + } + + public func apply(closure: (Plugin) -> Void) { + timeline.apply(closure) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/IdentityEventSender.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/IdentityEventSender.swift new file mode 100644 index 000000000..8aa15ab85 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/IdentityEventSender.swift @@ -0,0 +1,9 @@ +// +// IdentityEventSender.swift +// +// +// Created by Hao Yu on 11/15/22. +// + +internal class IdentityEventSender: BeforePlugin { +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/Mac/MacOSLifecycleMonitor.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/Mac/MacOSLifecycleMonitor.swift new file mode 100644 index 000000000..47bd55974 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/Mac/MacOSLifecycleMonitor.swift @@ -0,0 +1,61 @@ +// +// MacOSLifecycleMonitor.swift +// +// +// Created by Hao Yu on 11/17/22. +// + +#if os(macOS) + import Cocoa + + class MacOSLifecycleMonitor: UtilityPlugin { + private var application: NSApplication + private var appNotifications: [NSNotification.Name] = + [ + NSApplication.didBecomeActiveNotification, + NSApplication.willResignActiveNotification, + ] + + override init() { + self.application = NSApplication.shared + super.init() + setupListeners() + } + + @objc + func notificationResponse(notification: NSNotification) { + switch notification.name { + case NSApplication.didBecomeActiveNotification: + self.applicationDidBecomeActive(notification: notification) + case NSApplication.willResignActiveNotification: + self.applicationWillResignActive(notification: notification) + default: + break + } + } + + func setupListeners() { + // Configure the current life cycle events + let notificationCenter = NotificationCenter.default + for notification in appNotifications { + notificationCenter.addObserver( + self, + selector: #selector(notificationResponse(notification:)), + name: notification, + object: application + ) + } + } + + func applicationDidBecomeActive(notification: NSNotification) { + let timestamp = Int64(NSDate().timeIntervalSince1970 * 1000) + self.amplitude?.onEnterForeground(timestamp: timestamp) + } + + func applicationWillResignActive(notification: NSNotification) { + let timestamp = Int64(NSDate().timeIntervalSince1970 * 1000) + self.amplitude?.onExitForeground(timestamp: timestamp) + } + } + +#endif diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/NetworkConnectivityCheckerPlugin.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/NetworkConnectivityCheckerPlugin.swift new file mode 100644 index 000000000..4e6cd3ea3 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/NetworkConnectivityCheckerPlugin.swift @@ -0,0 +1,79 @@ +// +// NetworkConnectivityCheckerPlugin.swift +// Amplitude-Swift +// +// Created by Xinyi.Ye on 1/26/24. +// + +import Foundation +import Network +import Combine + +// Define a custom struct to represent network path status +public struct NetworkPath { + public var status: NWPath.Status + + public init(status: NWPath.Status) { + self.status = status + } +} + +// Protocol for creating network paths +protocol PathCreationProtocol { + var networkPathPublisher: AnyPublisher? { get } + func start(queue: DispatchQueue) +} + +// Implementation of PathCreationProtocol using NWPathMonitor +final class PathCreation: PathCreationProtocol { + public var networkPathPublisher: AnyPublisher? + private let subject = PassthroughSubject() + private let monitor = NWPathMonitor() + + func start(queue: DispatchQueue) { + monitor.pathUpdateHandler = subject.send + networkPathPublisher = subject + .map { NetworkPath(status: $0.status) } + .eraseToAnyPublisher() + monitor.start(queue: queue) + } +} + +open class NetworkConnectivityCheckerPlugin: BeforePlugin { + public static let Disabled: Bool? = nil + var pathCreation: PathCreationProtocol + private var pathUpdateCancellable: AnyCancellable? + + init(pathCreation: PathCreationProtocol = PathCreation()) { + self.pathCreation = pathCreation + super.init() + } + + open override func setup(amplitude: Amplitude) { + super.setup(amplitude: amplitude) + amplitude.logger?.debug(message: "Installing NetworkConnectivityCheckerPlugin, offline feature should be supported.") + + pathCreation.start(queue: amplitude.trackingQueue) + let logger = amplitude.logger + pathUpdateCancellable = pathCreation.networkPathPublisher? + .sink(receiveValue: { [weak amplitude, logger] networkPath in + guard let amplitude = amplitude else { + logger?.debug(message: "Received network connectivity updated when amplitude instance has been deallocated") + return + } + let isOffline = !(networkPath.status == .satisfied) + if amplitude.configuration.offline == isOffline { + return + } + amplitude.logger?.debug(message: "Network connectivity changed to \(isOffline ? "offline" : "online").") + amplitude.configuration.offline = isOffline + if !isOffline { + amplitude.flush() + } + }) + } + + open override func teardown() { + pathUpdateCancellable?.cancel() + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/Vendors/AppUtil.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/Vendors/AppUtil.swift new file mode 100644 index 000000000..d37cd9065 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/Vendors/AppUtil.swift @@ -0,0 +1,293 @@ +// +// AppleUtils.swift +// +// Created by Hao Yu on 11/16/22. +// + +import Foundation + +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) + import SystemConfiguration + import UIKit + #if !os(tvOS) + import WebKit + #endif + + internal class IOSVendorSystem: VendorSystem { + private let device = UIDevice.current + override var manufacturer: String { + return "Apple" + } + + override var model: String { + return deviceModel() + } + + override var identifierForVendor: String? { + return device.identifierForVendor?.uuidString + } + + override var os_name: String { + return device.systemName.lowercased() + } + + override var os_version: String { + return device.systemVersion + } + + override var platform: String { + #if os(tvOS) + return "tvOS" + #elseif targetEnvironment(macCatalyst) + return "macOS" + #else + return "iOS" + #endif + } + + private func getPlatformString() -> String { + var name: [Int32] = [CTL_HW, HW_MACHINE] + var size: Int = 2 + sysctl(&name, 2, nil, &size, nil, 0) + var hw_machine = [CChar](repeating: 0, count: Int(size)) + sysctl(&name, 2, &hw_machine, &size, nil, 0) + let platform = String(cString: hw_machine) + return platform + } + + private func deviceModel() -> String { + let platform = getPlatformString() + return getDeviceModel(platform: platform) + } + + override var requiredPlugin: Plugin { + return IOSLifecycleMonitor() + } + + override func beginBackgroundTask() -> BackgroundTaskCompletionCallback? { + if isRunningInAppExtension { + let semaphore = DispatchSemaphore(value: 0) + ProcessInfo.processInfo.performExpiringActivity(withReason: "Amplitude") { expired in + guard !expired else { + // If we've expired, just let the system terminate the process + return + } + semaphore.wait() + } + return { + semaphore.signal() + } + } else { + var id: UIBackgroundTaskIdentifier = .invalid + let callback = { () in + UIApplication.shared.endBackgroundTask(id) + id = .invalid + } + id = UIApplication.shared.beginBackgroundTask(withName: "amplitude", expirationHandler: callback) + return callback + } + } + + private var isRunningInAppExtension: Bool { + return Bundle.main.bundlePath.hasSuffix(".appex") + } + } +#endif + +#if os(macOS) + + import Cocoa + import WebKit + + internal class MacOSVendorSystem: VendorSystem { + private let device = ProcessInfo.processInfo + + override var manufacturer: String { + return "Apple" + } + + override var model: String { + return deviceModel() + } + + override var identifierForVendor: String? { + // apple suggested to use this for receipt validation + // in MAS, works for this too. + return macAddress(bsd: "en0") + } + + override var os_name: String { + return "macos" + } + + override var os_version: String { + return String( + format: "%ld.%ld.%ld", + device.operatingSystemVersion.majorVersion, + device.operatingSystemVersion.minorVersion, + device.operatingSystemVersion.patchVersion + ) + } + + override var requiredPlugin: Plugin { + return MacOSLifecycleMonitor() + } + + override var platform: String { + return "macOS" + } + + private func getPlatformString() -> String { + var systemInfo = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + let identifier = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + return identifier + } + + private func deviceModel() -> String { + let platform = getPlatformString() + return getDeviceModel(platform: platform) + } + + private func macAddress(bsd: String) -> String? { + let MAC_ADDRESS_LENGTH = 6 + let separator = ":" + + var length: size_t = 0 + var buffer: [CChar] + + let bsdIndex = Int32(if_nametoindex(bsd)) + if bsdIndex == 0 { + return nil + } + let bsdData = Data(bsd.utf8) + var managementInfoBase = [CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, bsdIndex] + + if sysctl(&managementInfoBase, 6, nil, &length, nil, 0) < 0 { + return nil + } + + buffer = [CChar]( + unsafeUninitializedCapacity: length, + initializingWith: { buffer, initializedCount in + for x in 0...stride + 1 + let rangeOfToken = infoData[indexAfterMsghdr...].range(of: bsdData)! + let lower = rangeOfToken.upperBound + let upper = lower + MAC_ADDRESS_LENGTH + let macAddressData = infoData[lower.. String { + var name: [Int32] = [CTL_HW, HW_MACHINE] + var size: Int = 2 + sysctl(&name, 2, nil, &size, nil, 0) + var hw_machine = [CChar](repeating: 0, count: Int(size)) + sysctl(&name, 2, &hw_machine, &size, nil, 0) + let platform = String(cString: hw_machine) + return platform + } + + private func deviceModel() -> String { + let platform = getPlatformString() + return getDeviceModel(platform: platform) + } + + override var requiredPlugin: Plugin { + return WatchOSLifecycleMonitor() + } + + // Per https://developer.apple.com/documentation/technotes/tn3135-low-level-networking-on-watchos, + // NWPathMonitor is not supported on most WatchOS apps when running on a real device. + override var networkConnectivityCheckingEnabled: Bool { + return false + } + } +#endif + +private func getDeviceModel(platform: String) -> String { + // use server device mapping except for the following exceptions + + if platform == "i386" || platform == "x86_64" { + return "Simulator" + } + + if platform.hasPrefix("MacBookAir") { + return "MacBook Air" + } + + if platform.hasPrefix("MacBookPro") { + return "MacBook Pro" + } + + if platform.hasPrefix("MacBook") { + return "MacBook" + } + + if platform.hasPrefix("MacPro") { + return "Mac Pro" + } + + if platform.hasPrefix("Macmini") { + return "Mac Mini" + } + + if platform.hasPrefix("iMac") { + return "iMac" + } + + if platform.hasPrefix("Xserve") { + return "Xserve" + } + + return platform +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/Vendors/VendorSystem.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/Vendors/VendorSystem.swift new file mode 100644 index 000000000..758995c30 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/Vendors/VendorSystem.swift @@ -0,0 +1,58 @@ +// +// VendorSystem.swift +// +// +// Created by Hao Yu on 11/11/22. +// + +internal typealias BackgroundTaskCompletionCallback = () -> Void + +internal class VendorSystem { + var manufacturer: String { + return "unknown" + } + + var model: String { + return "unknown" + } + + var identifierForVendor: String? { + return nil + } + + var os_name: String { + return "unknown" + } + + var os_version: String { + return "" + } + + var platform: String { + return "unknown" + } + + static var current: VendorSystem = { + #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) + return IOSVendorSystem() + #elseif os(macOS) + return MacOSVendorSystem() + #elseif os(watchOS) + return WatchOSVendorSystem() + #else + return VendorSystem() + #endif + }() + + var requiredPlugin: Plugin? { + return nil + } + + var networkConnectivityCheckingEnabled: Bool { + return true + } + + func beginBackgroundTask() -> BackgroundTaskCompletionCallback? { + return nil + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/iOS/IOSLifecycleMonitor.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/iOS/IOSLifecycleMonitor.swift new file mode 100644 index 000000000..511c0c062 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/iOS/IOSLifecycleMonitor.swift @@ -0,0 +1,140 @@ +// +// IOSLifecycleMonitor.swift +// +// +// Created by Hao Yu on 11/15/22. +// + +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) + +import Foundation +import SwiftUI + +class IOSLifecycleMonitor: UtilityPlugin { + private var application: UIApplication? + private var appNotifications: [NSNotification.Name] = [ + UIApplication.didEnterBackgroundNotification, + UIApplication.willEnterForegroundNotification, + UIApplication.didFinishLaunchingNotification, + UIApplication.didBecomeActiveNotification, + ] + private var utils: DefaultEventUtils? + private var sendApplicationOpenedOnDidBecomeActive = false + + override init() { + // TODO: Check if lifecycle plugin works for app extension + // App extensions can't use UIApplication.shared, so + // funnel it through something to check; Could be nil. + application = UIApplication.value(forKeyPath: "sharedApplication") as? UIApplication + super.init() + setupListeners() + } + + public override func setup(amplitude: Amplitude) { + super.setup(amplitude: amplitude) + utils = DefaultEventUtils(amplitude: amplitude) + if amplitude.configuration.autocapture.contains(.screenViews) { + UIKitScreenViews.register(amplitude) + } + if amplitude.configuration.autocapture.contains(.elementInteractions) { + UIKitElementInteractions.register(amplitude) + } + } + + @objc + func notificationResponse(notification: Notification) { + switch notification.name { + case UIApplication.didEnterBackgroundNotification: + didEnterBackground(notification: notification) + case UIApplication.willEnterForegroundNotification: + applicationWillEnterForeground(notification: notification) + case UIApplication.didFinishLaunchingNotification: + applicationDidFinishLaunchingNotification(notification: notification) + case UIApplication.didBecomeActiveNotification: + applicationDidBecomeActive(notification: notification) + default: + break + } + } + + func setupListeners() { + // Configure the current life cycle events + let notificationCenter = NotificationCenter.default + for notification in appNotifications { + notificationCenter.addObserver( + self, + selector: #selector(notificationResponse(notification:)), + name: notification, + object: application + ) + } + + } + + func applicationWillEnterForeground(notification: Notification) { + let timestamp = Int64(NSDate().timeIntervalSince1970 * 1000) + + let fromBackground: Bool + if let sharedApplication = application { + switch sharedApplication.applicationState { + case .active, .inactive: + fromBackground = false + case .background: + fromBackground = true + @unknown default: + fromBackground = false + } + } else { + fromBackground = false + } + + amplitude?.onEnterForeground(timestamp: timestamp) + sendApplicationOpened(fromBackground: fromBackground) + } + + func didEnterBackground(notification: Notification) { + let timestamp = Int64(NSDate().timeIntervalSince1970 * 1000) + self.amplitude?.onExitForeground(timestamp: timestamp) + if amplitude?.configuration.autocapture.contains(.appLifecycles) ?? false { + self.amplitude?.track(eventType: Constants.AMP_APPLICATION_BACKGROUNDED_EVENT) + } + } + + func applicationDidFinishLaunchingNotification(notification: Notification) { + utils?.trackAppUpdatedInstalledEvent() + + // Pre SceneDelegate apps wil not fire a willEnterForeground notification on app launch. + // Instead, use the initial applicationDidBecomeActive + let usesSceneDelegate = application?.delegate?.responds(to: #selector(UIApplicationDelegate.application(_:configurationForConnecting:options:))) ?? false + if !usesSceneDelegate { + sendApplicationOpenedOnDidBecomeActive = true + } + } + + func applicationDidBecomeActive(notification: Notification) { + guard sendApplicationOpenedOnDidBecomeActive else { + return + } + sendApplicationOpenedOnDidBecomeActive = false + + let timestamp = Int64(NSDate().timeIntervalSince1970 * 1000) + amplitude?.onEnterForeground(timestamp: timestamp) + sendApplicationOpened(fromBackground: false) + } + + private func sendApplicationOpened(fromBackground: Bool) { + guard amplitude?.configuration.autocapture.contains(.appLifecycles) ?? false else { + return + } + let info = Bundle.main.infoDictionary + let currentBuild = info?["CFBundleVersion"] as? String + let currentVersion = info?["CFBundleShortVersionString"] as? String + self.amplitude?.track(eventType: Constants.AMP_APPLICATION_OPENED_EVENT, eventProperties: [ + Constants.AMP_APP_BUILD_PROPERTY: currentBuild ?? "", + Constants.AMP_APP_VERSION_PROPERTY: currentVersion ?? "", + Constants.AMP_APP_FROM_BACKGROUND_PROPERTY: fromBackground, + ]) + } +} + +#endif diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/iOS/UIKitElementInteractions.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/iOS/UIKitElementInteractions.swift new file mode 100644 index 000000000..38e6280d9 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/iOS/UIKitElementInteractions.swift @@ -0,0 +1,263 @@ +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +import UIKit + +class UIKitElementInteractions { + struct EventData { + enum Source { + case actionMethod + + case gestureRecognizer + } + + let screenName: String? + + let accessibilityLabel: String? + + let accessibilityIdentifier: String? + + let targetViewClass: String + + let targetText: String? + + let hierarchy: String + + fileprivate func elementInteractionEvent(for action: String, from source: Source? = nil, withName sourceName: String? = nil) -> ElementInteractionEvent { + return ElementInteractionEvent( + screenName: screenName, + accessibilityLabel: accessibilityLabel, + accessibilityIdentifier: accessibilityIdentifier, + action: action, + targetViewClass: targetViewClass, + targetText: targetText, + hierarchy: hierarchy, + actionMethod: source == .actionMethod ? sourceName : nil, + gestureRecognizer: source == .gestureRecognizer ? sourceName : nil + ) + } + } + + fileprivate static let amplitudeInstances = NSHashTable.weakObjects() + + private static let lock = NSLock() + + private static let addNotificationObservers: Void = { + NotificationCenter.default.addObserver(UIKitElementInteractions.self, selector: #selector(didEndEditing), name: UITextField.textDidEndEditingNotification, object: nil) + NotificationCenter.default.addObserver(UIKitElementInteractions.self, selector: #selector(didEndEditing), name: UITextView.textDidEndEditingNotification, object: nil) + }() + + private static let setupMethodSwizzling: Void = { + swizzleMethod(UIApplication.self, from: #selector(UIApplication.sendAction), to: #selector(UIApplication.amp_sendAction)) + swizzleMethod(UIGestureRecognizer.self, from: #selector(setter: UIGestureRecognizer.state), to: #selector(UIGestureRecognizer.amp_setState)) + }() + + static func register(_ amplitude: Amplitude) { + lock.withLock { + amplitudeInstances.add(amplitude) + } + setupMethodSwizzling + addNotificationObservers + } + + @objc static func didEndEditing(_ notification: NSNotification) { + guard let view = notification.object as? UIView else { return } + // Text fields in SwiftUI are identifiable only after the text field is edited. + let elementInteractionEvent = view.eventData.elementInteractionEvent(for: "didEndEditing") + amplitudeInstances.allObjects.forEach { + $0.track(event: elementInteractionEvent) + } + } + + private static func swizzleMethod(_ cls: AnyClass?, from original: Selector, to swizzled: Selector) { + guard + let originalMethod = class_getInstanceMethod(cls, original), + let swizzledMethod = class_getInstanceMethod(cls, swizzled) + else { return } + + let originalImp = method_getImplementation(originalMethod) + let swizzledImp = method_getImplementation(swizzledMethod) + + class_replaceMethod(cls, + swizzled, + originalImp, + method_getTypeEncoding(originalMethod)) + class_replaceMethod(cls, + original, + swizzledImp, + method_getTypeEncoding(swizzledMethod)) + } +} + +extension UIApplication { + @objc func amp_sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool { + let sendActionResult = amp_sendAction(action, to: target, from: sender, for: event) + + // TODO: Reduce SwiftUI noise by finding the unique view that the action method is attached to. + // Currently, the action methods pointing to a SwiftUI target are blocked. + let targetClass = String(cString: object_getClassName(target)) + if targetClass.contains("SwiftUI") { + return sendActionResult + } + + guard sendActionResult, + let control = sender as? UIControl, + control.amp_shouldTrack(action, for: target), + let actionEvent = control.event(for: action, to: target)?.description + else { return sendActionResult } + + let elementInteractionEvent = control.eventData.elementInteractionEvent(for: actionEvent, from: .actionMethod, withName: NSStringFromSelector(action)) + + UIKitElementInteractions.amplitudeInstances.allObjects.forEach { + $0.track(event: elementInteractionEvent) + } + + return sendActionResult + } +} + +extension UIGestureRecognizer { + @objc func amp_setState(_ state: UIGestureRecognizer.State) { + amp_setState(state) + + guard state == .ended, let view else { return } + + // Block scroll and zoom events for `UIScrollView`. + if let scrollView = view as? UIScrollView, self === scrollView.panGestureRecognizer || self === scrollView.pinchGestureRecognizer { + return + } + + let gestureAction: String? = switch self { + case is UITapGestureRecognizer: "tap" + case is UISwipeGestureRecognizer: "swipe" + case is UIPanGestureRecognizer: "pan" + case is UILongPressGestureRecognizer: "longPress" +#if !os(tvOS) + case is UIPinchGestureRecognizer: "pinch" + case is UIRotationGestureRecognizer: "rotation" + case is UIScreenEdgePanGestureRecognizer: "screenEdgePan" +#endif + default: nil + } + + guard let gestureAction else { return } + + let elementInteractionEvent = view.eventData.elementInteractionEvent(for: gestureAction, from: .gestureRecognizer, withName: descriptiveTypeName) + + UIKitElementInteractions.amplitudeInstances.allObjects.forEach { + $0.track(event: elementInteractionEvent) + } + } +} + +extension UIView { + private static let viewHierarchyDelimiter = " → " + + var eventData: UIKitElementInteractions.EventData { + return UIKitElementInteractions.EventData( + screenName: owningViewController + .flatMap(UIViewController.amp_topViewController) + .flatMap(UIKitScreenViews.screenName), + accessibilityLabel: accessibilityLabel, + accessibilityIdentifier: accessibilityIdentifier, + targetViewClass: descriptiveTypeName, + targetText: amp_title, + hierarchy: sequence(first: self, next: \.superview) + .map { $0.descriptiveTypeName } + .joined(separator: UIView.viewHierarchyDelimiter)) + } +} + +extension UIControl { + func event(for action: Selector, to target: Any?) -> UIControl.Event? { + var events: [UIControl.Event] = [ + .touchDown, .touchDownRepeat, .touchDragInside, .touchDragOutside, + .touchDragEnter, .touchDragExit, .touchUpInside, .touchUpOutside, + .touchCancel, .valueChanged, .editingDidBegin, .editingChanged, + .editingDidEnd, .editingDidEndOnExit, .primaryActionTriggered + ] + if #available(iOS 14.0, tvOS 14.0, macCatalyst 14.0, *) { + events.append(.menuActionTriggered) + } + + return events.first { event in + self.actions(forTarget: target, forControlEvent: event)?.contains(action.description) ?? false + } + } +} + +extension UIControl.Event { + var description: String? { + if UIControl.Event.allTouchEvents.contains(self) { + return "touch" + } else if UIControl.Event.allEditingEvents.contains(self) { + return "edit" + } else if self == .valueChanged { + return "valueChange" + } else if self == .primaryActionTriggered { + return "primaryAction" + } else if #available(iOS 14.0, tvOS 14.0, macCatalyst 14.0, *), self == .menuActionTriggered { + return "menuAction" + } + return nil + } +} + +extension UIResponder { + var owningViewController: UIViewController? { + self as? UIViewController ?? next?.owningViewController + } +} + +extension NSObject { + var descriptiveTypeName: String { + String(describing: type(of: self)) + } +} + +protocol ActionTrackable { + var amp_title: String? { get } + func amp_shouldTrack(_ action: Selector, for target: Any?) -> Bool +} + +extension UIView: ActionTrackable { + @objc var amp_title: String? { nil } + @objc func amp_shouldTrack(_ action: Selector, for target: Any?) -> Bool { false } +} + +extension UIControl { + override func amp_shouldTrack(_ action: Selector, for target: Any?) -> Bool { + actions(forTarget: target, forControlEvent: .touchUpInside)?.contains(action.description) ?? false + } +} + +extension UIButton { + override var amp_title: String? { currentTitle ?? currentImage?.accessibilityIdentifier } +} + +extension UISegmentedControl { + override var amp_title: String? { titleForSegment(at: selectedSegmentIndex) } + override func amp_shouldTrack(_ action: Selector, for target: Any?) -> Bool { + actions(forTarget: target, forControlEvent: .valueChanged)?.contains(action.description) ?? false + } +} + +extension UIPageControl { + override func amp_shouldTrack(_ action: Selector, for target: Any?) -> Bool { + actions(forTarget: target, forControlEvent: .valueChanged)?.contains(action.description) ?? false + } +} + +#if !os(tvOS) +extension UIDatePicker { + override func amp_shouldTrack(_ action: Selector, for target: Any?) -> Bool { + actions(forTarget: target, forControlEvent: .valueChanged)?.contains(action.description) ?? false + } +} + +extension UISwitch { + override func amp_shouldTrack(_ action: Selector, for target: Any?) -> Bool { + actions(forTarget: target, forControlEvent: .valueChanged)?.contains(action.description) ?? false + } +} +#endif + +#endif diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/iOS/UIKitScreenViews.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/iOS/UIKitScreenViews.swift new file mode 100644 index 000000000..0a14fe365 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/iOS/UIKitScreenViews.swift @@ -0,0 +1,122 @@ +import Foundation +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +import UIKit + +class UIKitScreenViews { + private static let lock = NSLock() + fileprivate static var amplitudes = NSHashTable.weakObjects() + fileprivate static weak var lastTopViewController: UIViewController? + + static func register(_ amplitude: Amplitude) { + lock.withLock { + amplitudes.add(amplitude) + } + + swizzleViewDidAppear + } + + private static let swizzleViewDidAppear: Void = { + let controllerClass = UIViewController.self + let originalSelector = #selector(UIViewController.viewDidAppear(_:)) + let swizzledSelector = #selector(UIViewController.amp_viewDidAppear(_:)) + + guard let originalViewDidAppear = class_getInstanceMethod(controllerClass, originalSelector) else { return } + guard let swizzledViewDidAppear = class_getInstanceMethod(controllerClass, swizzledSelector) else { return } + + let methodAdded = class_addMethod( + controllerClass, + originalSelector, + method_getImplementation(swizzledViewDidAppear), + method_getTypeEncoding(swizzledViewDidAppear) + ) + + if methodAdded { + class_replaceMethod( + controllerClass, + swizzledSelector, + method_getImplementation(originalViewDidAppear), + method_getTypeEncoding(originalViewDidAppear) + ) + } else { + method_exchangeImplementations(originalViewDidAppear, swizzledViewDidAppear) + } + }() + + static func screenName(for viewController: UIViewController) -> String { + if let title = viewController.title, !title.isEmpty { + return title + } + let className = String(describing: type(of: viewController)) + .replacingOccurrences(of: "ViewController", with: "") + if !className.isEmpty { + return className + } + return "Unknown" + } +} + +extension UIViewController { + + @objc func amp_viewDidAppear(_ animated: Bool) { + amp_viewDidAppear(animated) + + // Only record screen events for view controllers owned by the app + let viewControllerBundlePath = Bundle(for: classForCoder).bundleURL.resolvingSymlinksInPath().path + let mainBundlePath = Bundle.main.bundleURL.resolvingSymlinksInPath().path + guard viewControllerBundlePath.hasPrefix(mainBundlePath) else { + return + } + + guard let rootViewController = viewIfLoaded?.window?.rootViewController else { + return + } + + guard let top = Self.amp_topViewController(rootViewController), + top !== UIKitScreenViews.lastTopViewController else { + return + } + + UIKitScreenViews.lastTopViewController = top + + let screenName = UIKitScreenViews.screenName(for: top) + + for amplitude in UIKitScreenViews.amplitudes.allObjects { + amplitude.track(event: ScreenViewedEvent(screenName: screenName)) + } + } + + static func amp_topViewController(_ rootViewController: UIViewController) -> UIViewController? { + if let nextController = amp_nextRootViewController(rootViewController) { + return amp_topViewController(nextController) + } + return rootViewController + } + + static func amp_nextRootViewController(_ rootViewController: UIViewController) -> UIViewController? { + if let presentedViewController = rootViewController.presentedViewController { + return presentedViewController + } + + if let navigationController = rootViewController as? UINavigationController { + if let visibleViewController = navigationController.visibleViewController { + return visibleViewController + } + } + + if let tabBarController = rootViewController as? UITabBarController { + if let selectedViewController = tabBarController.selectedViewController { + return selectedViewController + } + } + + if rootViewController.children.count > 0 { + if let firstChildViewController = rootViewController.children.first { + return firstChildViewController + } + } + + return nil + } +} + +#endif diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/watchOS/WatchOSLifecycleMonitor.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/watchOS/WatchOSLifecycleMonitor.swift new file mode 100644 index 000000000..d87daa596 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Plugins/watchOS/WatchOSLifecycleMonitor.swift @@ -0,0 +1,62 @@ +// +// WatchOSLifecycleMonitor.swift +// +// +// Created by Hao Yu on 11/17/22. +// + +#if os(watchOS) + + import Foundation + import WatchKit + + class WatchOSLifecycleMonitor: UtilityPlugin { + var wasBackgrounded: Bool = false + + private var appNotifications: [NSNotification.Name] = [ + WKExtension.applicationWillEnterForegroundNotification, + WKExtension.applicationDidEnterBackgroundNotification, + ] + + override init() { + super.init() + setupListeners() + } + + @objc + func notificationResponse(notification: NSNotification) { + switch notification.name { + case WKExtension.applicationWillEnterForegroundNotification: + self.applicationWillEnterForeground(notification: notification) + case WKExtension.applicationDidEnterBackgroundNotification: + self.applicationDidEnterBackground(notification: notification) + default: + break + } + } + + func setupListeners() { + // Configure the current life cycle events + let notificationCenter = NotificationCenter.default + for notification in appNotifications { + notificationCenter.addObserver( + self, + selector: #selector(notificationResponse(notification:)), + name: notification, + object: nil + ) + } + } + + func applicationWillEnterForeground(notification: NSNotification) { + let timestamp = Int64(NSDate().timeIntervalSince1970 * 1000) + self.amplitude?.onEnterForeground(timestamp: timestamp) + } + + func applicationDidEnterBackground(notification: NSNotification) { + let timestamp = Int64(NSDate().timeIntervalSince1970 * 1000) + self.amplitude?.onExitForeground(timestamp: timestamp) + } + } + +#endif diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/PrivacyInfo.xcprivacy b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..aa43eb3bd --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/PrivacyInfo.xcprivacy @@ -0,0 +1,56 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCoarseLocation + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + + diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Sessions.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Sessions.swift new file mode 100644 index 000000000..a73ea5a64 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Sessions.swift @@ -0,0 +1,165 @@ +import Foundation + +public class Sessions { + private let configuration: Configuration + private let storage: Storage + private let logger: (any Logger)? + private var _sessionId: Int64 = -1 + private(set) var sessionId: Int64 { + get { _sessionId } + set { + _sessionId = newValue + do { + try storage.write(key: StorageKey.PREVIOUS_SESSION_ID, value: _sessionId) + } catch { + logger?.warn(message: "Can't write PREVIOUS_SESSION_ID to storage: \(error)") + } + } + } + + private var _lastEventId: Int64 = 0 + private(set) var lastEventId: Int64 { + get { _lastEventId } + set { + _lastEventId = newValue + do { + try storage.write(key: StorageKey.LAST_EVENT_ID, value: _lastEventId) + } catch { + logger?.warn(message: "Can't write LAST_EVENT_ID to storage: \(error)") + } + } + } + + private var _lastEventTime: Int64 = -1 + var lastEventTime: Int64 { + get { _lastEventTime } + set { + _lastEventTime = newValue + do { + try storage.write(key: StorageKey.LAST_EVENT_TIME, value: _lastEventTime) + } catch { + logger?.warn(message: "Can't write LAST_EVENT_TIME to storage: \(error)") + } + } + } + + init(amplitude: Amplitude) { + configuration = amplitude.configuration + storage = amplitude.storage + logger = amplitude.logger + self._sessionId = amplitude.storage.read(key: .PREVIOUS_SESSION_ID) ?? -1 + self._lastEventId = amplitude.storage.read(key: .LAST_EVENT_ID) ?? 0 + self._lastEventTime = amplitude.storage.read(key: .LAST_EVENT_TIME) ?? -1 + } + + func processEvent(event: BaseEvent, inForeground: Bool) -> [BaseEvent] { + event.timestamp = event.timestamp ?? Int64(NSDate().timeIntervalSince1970 * 1000) + let eventTimeStamp = event.timestamp! + var skipEvent: Bool = false + var sessionEvents: [BaseEvent]? + + if event.eventType == Constants.AMP_SESSION_START_EVENT { + if event.sessionId == nil { // dummy start_session event + skipEvent = true + sessionEvents = self.startNewSessionIfNeeded(timestamp: eventTimeStamp, inForeground: inForeground) + } else { + self.sessionId = event.sessionId! + self.lastEventTime = eventTimeStamp + } + } else if event.eventType == Constants.AMP_SESSION_END_EVENT { + // do nothing + } else { + sessionEvents = self.startNewSessionIfNeeded(timestamp: eventTimeStamp, inForeground: inForeground) + } + + if !skipEvent && event.sessionId == nil { + event.sessionId = self.sessionId + } + + var result: [BaseEvent] = [] + if let sessionEvents { + result.append(contentsOf: sessionEvents) + } + if !skipEvent { + result.append(event) + } + + return assignEventId(events: result) + } + + func assignEventId(events: [BaseEvent]) -> [BaseEvent] { + var newLastEventId = self.lastEventId + + events.forEach({ event in + if event.eventId == nil { + newLastEventId += 1 + event.eventId = newLastEventId + } + }) + + self.lastEventId = newLastEventId + + return events + } + + private func isWithinMinTimeBetweenSessions(timestamp: Int64) -> Bool { + let timeDelta = timestamp - self.lastEventTime + return timeDelta < configuration.minTimeBetweenSessionsMillis + } + + public func startNewSessionIfNeeded(timestamp: Int64, inForeground: Bool) -> [BaseEvent]? { + if self.sessionId >= 0 && (inForeground || isWithinMinTimeBetweenSessions(timestamp: timestamp)) { + // if with in the same session extend the session and update the session time + self.lastEventTime = timestamp + return nil + } + + return startNewSession(timestamp: timestamp) + } + + public func startNewSession(timestamp: Int64) -> [BaseEvent] { + var sessionEvents: [BaseEvent] = Array() + let trackingSessionEvents = configuration.autocapture.contains(.sessions) + + // end previous session + if trackingSessionEvents && self.sessionId >= 0 { + let sessionEndEvent = BaseEvent( + timestamp: self.lastEventTime > 0 ? self.lastEventTime : nil, + sessionId: self.sessionId, + eventType: Constants.AMP_SESSION_END_EVENT + ) + sessionEvents.append(sessionEndEvent) + } + + // start new session + self.sessionId = timestamp + self.lastEventTime = timestamp + if trackingSessionEvents { + let sessionStartEvent = BaseEvent( + timestamp: timestamp, + sessionId: timestamp, + eventType: Constants.AMP_SESSION_START_EVENT + ) + sessionEvents.append(sessionStartEvent) + } + + return sessionEvents + } + + public func endCurrentSession() -> [BaseEvent] { + var sessionEvents: [BaseEvent] = Array() + let trackingSessionEvents = configuration.autocapture.contains(.sessions) + + if trackingSessionEvents && self.sessionId >= 0 { + let sessionEndEvent = BaseEvent( + timestamp: self.lastEventTime > 0 ? self.lastEventTime : nil, + sessionId: self.sessionId, + eventType: Constants.AMP_SESSION_END_EVENT + ) + sessionEvents.append(sessionEndEvent) + } + + self.sessionId = -1 + return sessionEvents + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/State.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/State.swift new file mode 100644 index 000000000..4b4a542ce --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/State.swift @@ -0,0 +1,36 @@ +// +// State.swift +// +// +// Created by Marvin Liu on 12/10/22. +// + +import Foundation + +class State { + @Atomic var userId: String? { + didSet { + for plugin in plugins { + plugin.onUserIdChanged(userId) + } + } + } + + @Atomic var deviceId: String? { + didSet { + for plugin in plugins { + plugin.onDeviceIdChanged(deviceId) + } + } + } + + private var plugins: [ObservePlugin] = [] + + func add(plugin: ObservePlugin) { + plugins.append(plugin) + } + + func remove(plugin: ObservePlugin) { + plugins.removeAll(where: { $0 === plugin }) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Storages/InMemoryStorage.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Storages/InMemoryStorage.swift new file mode 100644 index 000000000..d854ef164 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Storages/InMemoryStorage.swift @@ -0,0 +1,49 @@ +// +// InMemoryStorage.swift +// +// +// Created by Marvin Liu on 10/28/22. +// + +import Foundation + +class InMemoryStorage: Storage { + typealias EventBlock = URL + + func write(key: StorageKey, value: Any?) { + + } + + func read(key: StorageKey) -> T? { + return nil + } + + func reset() { + + } + + func rollover() { + + } + + func getEventsString(eventBlock: EventBlock) -> String? { + return nil + } + + func remove(eventBlock: EventBlock) { + + } + + func splitBlock(eventBlock: EventBlock, events: [BaseEvent]) { + + } + + func getResponseHandler( + configuration: Configuration, + eventPipeline: EventPipeline, + eventBlock: EventBlock, + eventsString: String + ) -> ResponseHandler { + abort() + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Storages/PersistentStorage.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Storages/PersistentStorage.swift new file mode 100644 index 000000000..4f7cef447 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Storages/PersistentStorage.swift @@ -0,0 +1,530 @@ +// +// PersistentStorage.swift +// +// +// Created by Marvin Liu on 10/28/22. +// + +import Foundation + +class PersistentStorage: Storage { + typealias EventBlock = URL + + static internal func getEventStoragePrefix(_ apiKey: String, _ instanceName: String) -> String { + return "storage-\(apiKey)-\(instanceName)" + } + + static internal func getIdentifyStoragePrefix(_ apiKey: String, _ instanceName: String) -> String { + return "identify-\(apiKey)-\(instanceName)" + } + + let storagePrefix: String + let userDefaults: UserDefaults? + let fileManager: FileManager + private var outputStream: OutputFileStream? + // Store event.callback in memory as it cannot be ser/deser in files. + private var eventCallbackMap: [String: EventCallback] + private var appPath: String! + let syncQueue = DispatchQueueHolder.storageQueue + let storageVersionKey: String + let logger: (any Logger)? + let diagonostics: Diagnostics + + init(storagePrefix: String, logger: (any Logger)?, diagonostics: Diagnostics) { + self.storagePrefix = storagePrefix == PersistentStorage.DEFAULT_STORAGE_PREFIX || storagePrefix.starts(with: "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-") + ? storagePrefix + : "\(PersistentStorage.DEFAULT_STORAGE_PREFIX)-\(storagePrefix)" + self.userDefaults = UserDefaults(suiteName: "\(PersistentStorage.AMP_STORAGE_PREFIX).\(self.storagePrefix)") + self.fileManager = FileManager.default + self.eventCallbackMap = [String: EventCallback]() + self.storageVersionKey = "\(PersistentStorage.STORAGE_VERSION).\(self.storagePrefix)" + self.logger = logger + self.diagonostics = diagonostics + // Make sure Amplitude data is sandboxed per app + self.appPath = isStorageSandboxed() ? "" : "\(Bundle.main.bundleIdentifier!)/" + handleV1Files() + } + + func write(key: StorageKey, value: Any?) throws { + try syncQueue.sync { + switch key { + case .EVENTS: + if let event = value as? BaseEvent { + let eventStoreFile = getCurrentEventFile() + self.storeEvent(toFile: eventStoreFile, event: event) + if let eventCallback = event.callback, let eventInsertId = event.insertId { + eventCallbackMap[eventInsertId] = eventCallback + } + } + default: + if isBasicType(value: value) { + userDefaults?.set(value, forKey: key.rawValue) + } else { + throw Exception.unsupportedType + } + } + } + } + + func read(key: StorageKey) -> T? { + syncQueue.sync { + var result: T? + switch key { + case .EVENTS: + result = getEventFiles() as? T + default: + result = userDefaults?.object(forKey: key.rawValue) as? T + } + return result + } + } + + func getEventsString(eventBlock: EventBlock) -> String? { + var content: String? + do { + content = try String(contentsOf: eventBlock, encoding: .utf8) + if eventBlock.hasPrefix(PersistentStorage.STORAGE_V2_PREFIX) { + // v2 file + return readV2File(content: content!) + } else { + // handle v1 file + return readV1File(content: content!) + } + } catch { + diagonostics.addErrorLog(error.localizedDescription) + logger?.error(message: error.localizedDescription) + } + return content + } + + func remove(eventBlock: EventBlock) { + syncQueue.sync { + do { + try fileManager.removeItem(atPath: eventBlock.path) + } catch { + diagonostics.addErrorLog(error.localizedDescription) + logger?.error(message: error.localizedDescription) + } + } + } + + func splitBlock(eventBlock: EventBlock, events: [BaseEvent]) { + syncQueue.sync { + let total = events.count + let half = total / 2 + let leftSplit = Array(events[0.. ResponseHandler { + return PersistentStorageResponseHandler( + configuration: configuration, + storage: self, + eventPipeline: eventPipeline, + eventBlock: eventBlock, + eventsString: eventsString + ) + } + + func reset() { + syncQueue.sync { + let urls = getEventFiles(includeUnfinished: true) + let keys = userDefaults?.dictionaryRepresentation().keys + keys?.forEach { key in + userDefaults?.removeObject(forKey: key) + } + for url in urls { + try? fileManager.removeItem(atPath: url.path) + } + } + } + + func rollover() { + syncQueue.sync { + if getCurrentEventFileIndex() == nil { + return + } + let currentFile = getCurrentEventFile() + if fileManager.fileExists(atPath: currentFile.path) == false { + return + } + if let attributes = try? fileManager.attributesOfItem(atPath: currentFile.path), + let fileSize = attributes[FileAttributeKey.size] as? UInt64, + fileSize >= 0 + { + finish(file: currentFile) + } + } + } + + func getEventCallback(insertId: String) -> EventCallback? { + return eventCallbackMap[insertId] + } + + func removeEventCallback(insertId: String) { + eventCallbackMap.removeValue(forKey: insertId) + } + + func isBasicType(value: Any?) -> Bool { + var result = false + if value == nil { + result = true + } else { + switch value { + case is NSNull, is Int, is Float, is Double, is Decimal, is NSNumber, is Bool, is String, is NSString: + result = true + default: + break + } + } + return result + } + + internal func isStorageSandboxed() -> Bool { + return SandboxHelper().isSandboxEnabled() + } +} + +extension PersistentStorage { + static let DEFAULT_STORAGE_PREFIX = "amplitude-swift" + static let AMP_STORAGE_PREFIX = "com.amplitude.storage" + static let MAX_FILE_SIZE = 975000 // 975KB + static let TEMP_FILE_EXTENSION = "tmp" + static let DELMITER = "\u{0000}" + static let STORAGE_VERSION = "amplitude.events.storage.version" + static let STORAGE_V2_PREFIX = "v2-" + + enum Exception: Error { + case unsupportedType + } +} + +extension PersistentStorage { + internal var eventsFileKey: String { + return "\(storagePrefix).\(StorageKey.EVENTS.rawValue).index" + } + + private func getCurrentEventFile() -> URL { + let allOpenFiles = try? fileManager.contentsOfDirectory( + at: getEventsStorageDirectory(), + includingPropertiesForKeys: [], + options: .skipsHiddenFiles + ).filter { (file) -> Bool in + return file.pathExtension == PersistentStorage.TEMP_FILE_EXTENSION + } + if allOpenFiles != nil && allOpenFiles!.count > 0 { + return allOpenFiles![0] + } + var currentFileIndex = 0 + let index: Int = getCurrentEventFileIndex() ?? 0 + userDefaults?.set(index, forKey: eventsFileKey) + currentFileIndex = index + return getEventsFile(index: currentFileIndex) + } + + private func getCurrentEventFileIndex() -> Int? { + return userDefaults?.object(forKey: eventsFileKey) as? Int + } + + private func getEventsFile(index: Int) -> URL { + let dir = getEventsStorageDirectory() + let fileURL = dir.appendingPathComponent("\(PersistentStorage.STORAGE_V2_PREFIX)\(index)").appendingPathExtension( + PersistentStorage.TEMP_FILE_EXTENSION + ) + return fileURL + } + + internal func getEventFiles(includeUnfinished: Bool = false) -> [URL] { + var result = [URL]() + + let eventsStorageDirectory = getEventsStorageDirectory(createDirectory: false) + if !fileManager.fileExists(atPath: eventsStorageDirectory.path) { + return result + } + + // finish out any file in progress + let currentFile = getCurrentEventFile() + finish(file: currentFile) + + let allFiles = try? fileManager.contentsOfDirectory( + at: getEventsStorageDirectory(), + includingPropertiesForKeys: [], + options: .skipsHiddenFiles + ) + var files = allFiles + if includeUnfinished == false { + files = allFiles?.filter { (file) -> Bool in + return file.pathExtension != PersistentStorage.TEMP_FILE_EXTENSION + } + } + let sorted = files?.sorted { (left, right) -> Bool in + return left.lastPathComponent < right.lastPathComponent + } + if let s = sorted { + result = s + } + return result + } + + internal func getEventsStorageDirectory(createDirectory: Bool = true) -> URL { + // TODO: Update to use applicationSupportDirectory for all platforms (this will require a migration) + // let searchPathDirectory = FileManager.SearchPathDirectory.applicationSupportDirectory + // tvOS doesn't have access to document + // macOS /Documents dir might be synced with iCloud + #if os(tvOS) || os(macOS) + let searchPathDirectory = FileManager.SearchPathDirectory.cachesDirectory + #else + let searchPathDirectory = FileManager.SearchPathDirectory.documentDirectory + #endif + + let urls = fileManager.urls(for: searchPathDirectory, in: .userDomainMask) + let docUrl = urls[0] + let storageUrl = docUrl.appendingPathComponent("amplitude/\(appPath ?? "")\(eventsFileKey)/") + if createDirectory { + // try to create it, will fail if already exists. + // tvOS, watchOS regularly clear out data. + try? FileManager.default.createDirectory(at: storageUrl, withIntermediateDirectories: true, attributes: nil) + } + return storageUrl + } + + private func storeEvent(toFile file: URL, event: BaseEvent) { + var storeFile = file + + if fileManager.fileExists(atPath: storeFile.path) == false { + start(file: storeFile) + } else if outputStream == nil { + // this can happen if an instance was terminated before finishing a file. + open(file: storeFile) + } + + // Verify file size isn't too large + if let attributes = try? fileManager.attributesOfItem(atPath: storeFile.path), + let fileSize = attributes[FileAttributeKey.size] as? UInt64, + fileSize >= PersistentStorage.MAX_FILE_SIZE + { + finish(file: storeFile) + // Set the new file path + storeFile = getCurrentEventFile() + start(file: storeFile) + } + + let jsonString = event.toString().replacingOccurrences(of: PersistentStorage.DELMITER, with: "") + do { + if outputStream == nil { + logger?.error(message: "OutputStream is nil with file: \(storeFile)") + } + try outputStream?.write("\(jsonString)\(PersistentStorage.DELMITER)") + } catch { + diagonostics.addErrorLog(error.localizedDescription) + logger?.error(message: error.localizedDescription) + } + } + + private func storeEventsInNewFile(toFile file: URL, events: [BaseEvent]) { + let storeFile = file + + guard fileManager.fileExists(atPath: storeFile.path) != true else { + diagonostics.addErrorLog("Splited file duplicate for path: \(storeFile.path)") + return + } + + start(file: storeFile) + + let jsonString = events.map { $0.toString().replacingOccurrences(of: PersistentStorage.DELMITER, with: "") }.joined(separator: PersistentStorage.DELMITER) + do { + if outputStream == nil { + logger?.error(message: "OutputStream is nil with file: \(storeFile)") + } + try outputStream?.write("\(jsonString)\(PersistentStorage.DELMITER)") + } catch { + diagonostics.addErrorLog(error.localizedDescription) + logger?.error(message: error.localizedDescription) + } + finish(file: storeFile) + } + + private func start(file: URL) { + do { + outputStream = try OutputFileStream(fileURL: file) + try outputStream?.create() + } catch { + diagonostics.addErrorLog(error.localizedDescription) + logger?.error(message: error.localizedDescription) + } + } + + private func open(file: URL) { + if outputStream == nil { + // this can happen if an instance was terminated before finishing a file. + do { + outputStream = try OutputFileStream(fileURL: file) + if let outputStream = outputStream { + try outputStream.open() + } + } catch { + diagonostics.addErrorLog(error.localizedDescription) + logger?.error(message: error.localizedDescription) + } + } + } + + private func finish(file: URL) { + guard let outputStream = self.outputStream else { + return + } + + do { + try outputStream.close() + } catch { + diagonostics.addErrorLog(error.localizedDescription) + logger?.error(message: error.localizedDescription) + } + self.outputStream = nil + + rename(file) + let currentFileIndex: Int = (getCurrentEventFileIndex() ?? 0) + 1 + userDefaults?.set(currentFileIndex, forKey: eventsFileKey) + } + + private func rename(_ file: URL) { + let fileWithoutTemp = file.deletingPathExtension() + var updatedFile = fileWithoutTemp + if !fileManager.fileExists(atPath: file.path) { + logger?.debug(message: "Try to rename non exist file.") + return + } + if fileManager.fileExists(atPath: fileWithoutTemp.path) { + logger?.debug(message: "File already exists \(fileWithoutTemp.path), handle gracefully.") + let suffix = "-\(Date().timeIntervalSince1970)-\(Int.random(in: 0..<1000))" + updatedFile = fileWithoutTemp.appendFileNameSuffix(suffix: suffix) + } + do { + try fileManager.moveItem(at: file, to: updatedFile) + } catch { + diagonostics.addErrorLog(error.localizedDescription) + logger?.error(message: "Unable to rename file: \(file.path)") + } + } + + private func handleV1Files() { + syncQueue.sync { + let currentStorageVersion = (userDefaults?.object(forKey: self.storageVersionKey) as? Int) ?? 0 + if currentStorageVersion > 1 { + return + } + let allFiles = self.getEventFiles(includeUnfinished: true) + for file in allFiles { + do { + if file.hasPrefix(PersistentStorage.STORAGE_V2_PREFIX) { + logger?.debug(message: "file already migrated") + return + } + let content = try String(contentsOf: file, encoding: .utf8) + if content.hasSuffix(PersistentStorage.DELMITER) { + break // already handled and in v2 format + } + + let normalizedContent = "[\(content.trimmingCharacters(in: ["[", ",", "]"]))]" + let events = BaseEvent.fromArrayString(jsonString: normalizedContent) + if events != nil { + migrateFile(file: file, events: events!) + } + if file.pathExtension != "" { + finish(file: file) + } + migrateFileName(file) + } catch { + diagonostics.addErrorLog("Error migrating file: \(file.path) for \(error.localizedDescription)") + logger?.error(message: error.localizedDescription) + } + } + userDefaults?.setValue(2, forKey: self.storageVersionKey) + } + } + + private func migrateFile(file: URL, events: [BaseEvent]) { + guard fileManager.fileExists(atPath: file.path) == true else { + diagonostics.addErrorLog("File to migrate not exists any more : \(file.path)") + return + } + + do { + let jsonString = events.map { $0.toString().replacingOccurrences(of: PersistentStorage.DELMITER, with: "") }.joined(separator: PersistentStorage.DELMITER) + let finalString = "\(jsonString)\(PersistentStorage.DELMITER)" + try finalString.write(to: file, atomically: true, encoding: .utf8) + } catch { + diagonostics.addErrorLog(error.localizedDescription) + logger?.error(message: error.localizedDescription) + } + } + + private func migrateFileName(_ file: URL) { + let fileNameV2 = file.appendFileNamePrefix(prefix: PersistentStorage.STORAGE_V2_PREFIX).deletingPathExtension() + if fileManager.fileExists(atPath: fileNameV2.path) { + logger?.debug(message: "Migrate found an existing file.") + return + } + do { + try fileManager.moveItem(at: file, to: fileNameV2) + } catch { + diagonostics.addErrorLog(error.localizedDescription) + logger?.error(message: "Unable to migrate file: \(file.path)") + } + } + + private func readV2File(content: String) -> String { + var events: [BaseEvent] = [BaseEvent]() + content.components(separatedBy: PersistentStorage.DELMITER).forEach{ + let currentString = String($0) + if currentString.isEmpty { + return + } + if let event = BaseEvent.fromString(jsonString: String($0)) { + events.append(event) + } else { + diagonostics.addMalformedEvent(String($0)) + } + } + return eventsToJSONString(events: events) + } + + private func eventsToJSONString(events: [BaseEvent]) -> String { + var result = "" + do { + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + let json = try encoder.encode(events) + if let printed = String(data: json, encoding: .utf8) { + result = printed + } + } catch { + diagonostics.addErrorLog(error.localizedDescription) + } + return result + } + + private func readV1File(content: String) -> String { + var result = "" + let normalizedContent = "[\(content.trimmingCharacters(in: ["[", ",", "]"]))]" + let events = BaseEvent.fromArrayString(jsonString: normalizedContent) + if events != nil { + result = normalizedContent + } + return result + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Timeline.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Timeline.swift new file mode 100644 index 000000000..49c91126e --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Timeline.swift @@ -0,0 +1,69 @@ +// +// Timeline.swift +// +// +// Created by Marvin Liu on 10/27/22. +// + +import Foundation + +public class Timeline { + internal let plugins: [PluginType: Mediator] + + init() { + self.plugins = [ + PluginType.before: Mediator(), + PluginType.enrichment: Mediator(), + PluginType.destination: Mediator(), + PluginType.utility: Mediator(), + ] + } + + func processEvent(event: BaseEvent) { + let beforeResult = self.applyPlugin(pluginType: PluginType.before, event: event) + let enrichmentResult = self.applyPlugin(pluginType: PluginType.enrichment, event: beforeResult) + _ = self.applyPlugin(pluginType: PluginType.destination, event: enrichmentResult) + } + + internal func applyPlugin(pluginType: PluginType, event: BaseEvent?) -> BaseEvent? { + var result: BaseEvent? = event + if let mediator = plugins[pluginType] { + result = mediator.execute(event: event) + } + return result + } + + internal func add(plugin: Plugin) { + if let mediator = plugins[plugin.type] { + mediator.add(plugin: plugin) + } + } + + internal func remove(plugin: Plugin) { + // remove all plugins with this name in every category + for _plugin in plugins { + let list = _plugin.value + list.remove(plugin: plugin) + } + } + + internal func applyClosure(_ closure: (Plugin) -> Void) { + for plugin in plugins { + let mediator = plugin.value + mediator.applyClosure(closure) + } + } + + internal func apply(_ closure: (Plugin) -> Void) { + for type in PluginType.allCases { + if let mediator = plugins[type] { + mediator.plugins.forEach { (plugin) in + closure(plugin) + if let destPlugin = plugin as? DestinationPlugin { + destPlugin.apply(closure: closure) + } + } + } + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/TrackingOptions.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/TrackingOptions.swift new file mode 100644 index 000000000..7482664af --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/TrackingOptions.swift @@ -0,0 +1,187 @@ +// +// TrackingOptions.swift +// +// +// Created by Marvin Liu on 10/28/22. +// + +import Foundation + +public class TrackingOptions { + + public init() {} + + private static let COPPA_CONTROL_PROPERTIES = [ + Constants.AMP_TRACKING_OPTION_IDFA, + Constants.AMP_TRACKING_OPTION_IDFV, + Constants.AMP_TRACKING_OPTION_CITY, + Constants.AMP_TRACKING_OPTION_IP_ADDRESS, + ] + + var disabledFields: Set = [] + + public func shouldTrackVersionName() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_VERSION_NAME) + } + + @discardableResult + public func disableTrackVersionName() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_VERSION_NAME) + return self + } + + public func shouldTrackOsName() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_OS_NAME) + } + + @discardableResult + public func disableTrackOsName() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_OS_NAME) + return self + } + + public func shouldTrackOsVersion() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_OS_VERSION) + } + + @discardableResult + public func disableTrackOsVersion() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_OS_VERSION) + return self + } + + public func shouldTrackDeviceManufacturer() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_DEVICE_MANUFACTURER) + } + + @discardableResult + public func disableTrackDeviceManufacturer() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_DEVICE_MANUFACTURER) + return self + } + + public func shouldTrackDeviceModel() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_DEVICE_MODEL) + } + + @discardableResult + public func disableTrackDeviceModel() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_DEVICE_MODEL) + return self + } + + public func shouldTrackCarrier() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_CARRIER) + } + + @discardableResult + public func disableTrackCarrier() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_CARRIER) + return self + } + + public func shouldTrackIpAddress() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_IP_ADDRESS) + } + + @discardableResult + public func disableTrackIpAddress() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_IP_ADDRESS) + return self + } + + public func shouldTrackCountry() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_COUNTRY) + } + + @discardableResult + public func disableTrackCountry() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_COUNTRY) + return self + } + + public func shouldTrackCity() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_CITY) + } + + @discardableResult + public func disableTrackCity() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_CITY) + return self + } + + public func shouldTrackDMA() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_DMA) + } + + @discardableResult + public func disableTrackDMA() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_DMA) + return self + } + + public func shouldTrackIDFV() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_IDFV) + } + + @discardableResult + public func disableTrackIDFV() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_IDFV) + return self + } + + public func shouldTrackLanguage() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_LANGUAGE) + } + + @discardableResult + public func disableTrackLanguage() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_LANGUAGE) + return self + } + + public func shouldTrackRegion() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_REGION) + } + + @discardableResult + public func disableTrackRegion() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_REGION) + return self + } + + public func shouldTrackPlatform() -> Bool { + return shouldTrackField(field: Constants.AMP_TRACKING_OPTION_PLATFORM) + } + + @discardableResult + public func disableTrackPlatform() -> TrackingOptions { + disabledFields.insert(Constants.AMP_TRACKING_OPTION_PLATFORM) + return self + } + + static func forCoppaControl() -> TrackingOptions { + let trackingOptions = TrackingOptions() + for property in COPPA_CONTROL_PROPERTIES { + trackingOptions.disableTrackingField(field: property) + } + + return trackingOptions + } + + @discardableResult + func mergeIn(other: TrackingOptions) -> TrackingOptions { + for key in other.disabledFields { + disableTrackingField(field: key) + } + return self + } + + private func shouldTrackField(field: String) -> Bool { + return !disabledFields.contains(field) + } + + private func disableTrackingField(field: String) { + disabledFields.insert(field) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Types.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Types.swift new file mode 100644 index 000000000..388c785c7 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Types.swift @@ -0,0 +1,163 @@ +// +// Types.swift +// +// +// Created by Marvin Liu on 10/27/22. +// + +import Foundation + +public struct Plan: Codable { + public var branch: String? + public var source: String? + public var version: String? + public var versionId: String? + + public init(branch: String? = nil, source: String? = nil, version: String? = nil, versionId: String? = nil) { + self.branch = branch + self.source = source + self.version = version + self.versionId = versionId + } +} + +public struct IngestionMetadata: Codable { + public var sourceName: String? + public var sourceVersion: String? + + enum CodingKeys: String, CodingKey { + case sourceName = "source_name" + case sourceVersion = "source_version" + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + sourceName = try values.decodeIfPresent(String.self, forKey: .sourceName) + sourceVersion = try values.decodeIfPresent(String.self, forKey: .sourceVersion) + } + + public init(sourceName: String? = nil, sourceVersion: String? = nil) { + self.sourceName = sourceName + self.sourceVersion = sourceVersion + } +} + +public typealias EventCallback = (BaseEvent, Int, String) -> Void + +// Swift 5.7 supports any existential type. +// The type of EventBlock has to be determined pre-runtime. +// It cannot be dynamically associated with this protocol. +// https://github.com/apple/swift/issues/62219#issuecomment-1326531801 +public protocol Storage { + func write(key: StorageKey, value: Any?) throws + func read(key: StorageKey) -> T? + func getEventsString(eventBlock: URL) -> String? + func remove(eventBlock: URL) + func splitBlock(eventBlock: URL, events: [BaseEvent]) + func rollover() + func reset() + func getResponseHandler( + configuration: Configuration, + eventPipeline: EventPipeline, + eventBlock: URL, + eventsString: String + ) -> ResponseHandler +} + +public enum StorageKey: String, CaseIterable { + case LAST_EVENT_ID = "last_event_id" + case PREVIOUS_SESSION_ID = "previous_session_id" + case LAST_EVENT_TIME = "last_event_time" + case OPT_OUT = "opt_out" + case EVENTS = "events" + case USER_ID = "user_id" + case DEVICE_ID = "device_id" + case APP_BUILD = "app_build" + case APP_VERSION = "app_version" + // The version of PersistentStorage, used for data migrations + // Value should be a PersistentStorageVersion value + // Note the first version is 2, which corresponds to apiKey-instanceName based storage + case STORAGE_VERSION = "storage_version" +} + +public enum PersistentStorageVersion: Int, Comparable { + public static func < (lhs: PersistentStorageVersion, rhs: PersistentStorageVersion) -> Bool { + return lhs.rawValue < rhs.rawValue + } + + case NO_VERSION = -1 + // Note that versioning was added after these storage changes (0, 1) + case API_KEY = 0 + case INSTANCE_NAME = 1 + // This is the first version (2) we set a value in storageProvider.read(.StorageVersion) + case API_KEY_AND_INSTANCE_NAME = 2 +} + +public protocol Logger { + associatedtype LogLevel: RawRepresentable + var logLevel: Int { get set } + func error(message: String) + func warn(message: String) + func log(message: String) + func debug(message: String) +} + +@objc(AMPPluginType) +public enum PluginType: Int, CaseIterable { + case before + case enrichment + case destination + case utility + case observe +} + +public protocol Plugin: AnyObject { + var type: PluginType { get } + func setup(amplitude: Amplitude) + func execute(event: BaseEvent) -> BaseEvent? + func teardown() +} + +public protocol EventPlugin: Plugin { + func track(event: BaseEvent) -> BaseEvent? + func identify(event: IdentifyEvent) -> IdentifyEvent? + func groupIdentify(event: GroupIdentifyEvent) -> GroupIdentifyEvent? + func revenue(event: RevenueEvent) -> RevenueEvent? + func flush() +} + +extension Plugin { + // default behavior + public func execute(event: BaseEvent) -> BaseEvent? { + return event + } + + public func setup(amplitude: Amplitude) { + } + + public func teardown(){ + // Clean up any resources from setup if necessary + } +} + +public protocol ResponseHandler { + func handle(result: Result) + func handleSuccessResponse(code: Int) + func handleBadRequestResponse(data: [String: Any]) + func handlePayloadTooLargeResponse(data: [String: Any]) + func handleTooManyRequestsResponse(data: [String: Any]) + func handleTimeoutResponse(data: [String: Any]) + func handleFailedResponse(data: [String: Any]) +} + +extension ResponseHandler { + static func collectIndices(data: [String: [Int]]) -> Set { + var indices = Set() + for (_, elements) in data { + for el in elements { + indices.insert(el) + } + } + return indices + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/Atomic.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/Atomic.swift new file mode 100644 index 000000000..4e19ba1a8 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/Atomic.swift @@ -0,0 +1,35 @@ +// +// Atomic.swift +// +// +// Created by Marvin Liu on 11/29/22. +// + +import Foundation + +@propertyWrapper +public struct Atomic { + var value: T + private let lock = NSLock() + + public init(wrappedValue value: T) { + self.value = value + } + + public var wrappedValue: T { + get { return load() } + set { store(newValue: newValue) } + } + + func load() -> T { + lock.lock() + defer { lock.unlock() } + return value + } + + mutating func store(newValue: T) { + lock.lock() + defer { lock.unlock() } + value = newValue + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/CodableExtension.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/CodableExtension.swift new file mode 100644 index 000000000..5df58f48d --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/CodableExtension.swift @@ -0,0 +1,296 @@ +// +// CodableExtension.swift +// +// +// Created by Marvin Liu on 11/23/22. +// This file extends the current Codable to support custom JSON, like [String: Any]. + +import Foundation + +struct JSONCodingKeys: CodingKey { + var stringValue: String + var intValue: Int? + + // Non-failable variant + init(_ stringValue: String) { + self.stringValue = stringValue + } + + init?(stringValue: String) { + self.stringValue = stringValue + } + + init?(intValue: Int) { + self.init(stringValue: "\(intValue)") + self.intValue = intValue + } +} + +extension KeyedDecodingContainer { + func decode(_ type: [String: Any].Type, forKey key: K) throws -> [String: Any] { + let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key) + return try container.decode(type) + } + + func decode(_ type: [[String: Any]].Type, forKey key: K) throws -> [[String: Any]] { + var container = try self.nestedUnkeyedContainer(forKey: key) + if let decodedData = try container.decode([Any].self) as? [[String: Any]] { + return decodedData + } else { + return [] + } + } + + func decodeIfPresent(_ type: [String: Any].Type, forKey key: K) throws -> [String: Any]? { + guard contains(key) else { + return nil + } + guard try decodeNil(forKey: key) == false else { + return nil + } + return try decode(type, forKey: key) + } + + func decode(_ type: [Any].Type, forKey key: K) throws -> [Any] { + var container = try self.nestedUnkeyedContainer(forKey: key) + return try container.decode(type) + } + + func decodeIfPresent(_ type: [Any].Type, forKey key: K) throws -> [Any]? { + guard contains(key) else { + return nil + } + guard try decodeNil(forKey: key) == false else { + return nil + } + return try decode(type, forKey: key) + } + + func decode(_ type: [String: Any].Type) throws -> [String: Any] { + var dictionary = [String: Any]() + for key in allKeys { + if let boolValue = try? decode(Bool.self, forKey: key) { + dictionary[key.stringValue] = boolValue + } else if let stringValue = try? decode(String.self, forKey: key) { + dictionary[key.stringValue] = stringValue + } else if let intValue = try? decode(Int.self, forKey: key) { + dictionary[key.stringValue] = intValue + } else if let doubleValue = try? decode(Double.self, forKey: key) { + dictionary[key.stringValue] = doubleValue + } else if let nestedDictionary = try? decode(Dictionary.self, forKey: key) { + dictionary[key.stringValue] = nestedDictionary + } else if let nestedArray = try? decode(Array.self, forKey: key) { + dictionary[key.stringValue] = nestedArray + } + } + return dictionary + } +} + +extension UnkeyedDecodingContainer { + mutating func decode(_ type: [Any].Type) throws -> [Any] { + var array: [Any] = [] + while isAtEnd == false { + // See if the current value in the JSON array is `null` first + // and prevent infite recursion with nested arrays. + if try decodeNil() { + continue + } else if let value = try? decode(Bool.self) { + array.append(value) + } else if let value = try? decode(Double.self) { + array.append(value) + } else if let value = try? decode(String.self) { + array.append(value) + } else if let nestedDictionary = try? decode(Dictionary.self) { + array.append(nestedDictionary) + } else if let nestedArray = try? decode(Array.self) { + array.append(nestedArray) + } + } + return array + } + + mutating func decode(_ type: [String: Any].Type) throws -> [String: Any] { + let nestedContainer = try self.nestedContainer(keyedBy: JSONCodingKeys.self) + return try nestedContainer.decode(type) + } +} + +extension UnkeyedEncodingContainer { + + mutating func encodeAny(_ value: Any?) throws { + guard let value = value else { + return + } + + // NSNumber bridges into many different types - try to extract the original. + // Note that for swift numeric types, this actually does a double conversion (to NSNumber first). + let encodedValue: Any + if let numberValue = value as? NSNumber, let swiftValue = numberValue.swiftValue { + encodedValue = swiftValue + } else { + encodedValue = value + } + + // Based on https://github.com/apple/swift-corelibs-foundation/blob/ca3669eb9ac282c649e71824d9357dbe140c8251/Sources/Foundation/JSONSerialization.swift#L397 + switch encodedValue { + case let encodable as Encodable: + try encode(encodable) + case let str as String: + try encode(str) + case let boolValue as Bool: + try encode(boolValue) + case let num as Int: + try encode(num) + case let num as Int8: + try encode(num) + case let num as Int16: + try encode(num) + case let num as Int32: + try encode(num) + case let num as Int64: + try encode(num) + case let num as UInt: + try encode(num) + case let num as UInt8: + try encode(num) + case let num as UInt16: + try encode(num) + case let num as UInt32: + try encode(num) + case let num as UInt64: + try encode(num) + case let array as [Any?]: + var container = nestedUnkeyedContainer() + for element in array { + try container.encodeAny(element) + } + case let dict as [AnyHashable: Any?]: + var container = nestedContainer(keyedBy: JSONCodingKeys.self) + for (key, value) in dict { + try container.encodeAny(value, forKey: JSONCodingKeys(String(describing: key))) + } + case let num as Float: + try encode(num) + case let num as Double: + try encode(num) + case let num as Decimal: + try encode(num) + case let num as NSDecimalNumber: + try encode(num.decimalValue) + case is NSNull: + try encodeNil() + default: + // Ideally we would throw an error here, but we still want to complete the encode to maintain + // backwards compatibility. + try encode("[Non-Encodable]") + } + } +} + +extension KeyedEncodingContainer { + + mutating func encodeAny(_ value: Any?, forKey key: KeyedEncodingContainer.Key) throws { + guard let value = value else { + return + } + + // NSNumber bridges into many different types - try to extract the original. + // Note that for swift numeric types, this actually does a double conversion (to NSNumber first). + let encodedValue: Any + if let numberValue = value as? NSNumber, let swiftValue = numberValue.swiftValue { + encodedValue = swiftValue + } else { + encodedValue = value + } + + // Based on https://github.com/apple/swift-corelibs-foundation/blob/ca3669eb9ac282c649e71824d9357dbe140c8251/Sources/Foundation/JSONSerialization.swift#L397 + switch encodedValue { + case let encodable as Encodable: + try encode(encodable, forKey: key) + case let str as String: + try encode(str, forKey: key) + case let boolValue as Bool: + try encode(boolValue, forKey: key) + case let num as Int: + try encode(num, forKey: key) + case let num as Int8: + try encode(num, forKey: key) + case let num as Int16: + try encode(num, forKey: key) + case let num as Int32: + try encode(num, forKey: key) + case let num as Int64: + try encode(num, forKey: key) + case let num as UInt: + try encode(num, forKey: key) + case let num as UInt8: + try encode(num, forKey: key) + case let num as UInt16: + try encode(num, forKey: key) + case let num as UInt32: + try encode(num, forKey: key) + case let num as UInt64: + try encode(num, forKey: key) + case let array as [Any?]: + var container = nestedUnkeyedContainer(forKey: key) + for element in array { + try container.encodeAny(element) + } + case let dict as [AnyHashable: Any?]: + var container = nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key) + for (key, value) in dict { + try container.encodeAny(value, forKey: JSONCodingKeys(String(describing: key))) + } + case let num as Float: + try encode(num, forKey: key) + case let num as Double: + try encode(num, forKey: key) + case let num as Decimal: + try encode(num, forKey: key) + case let num as NSDecimalNumber: + try encode(num.decimalValue, forKey: key) + case is NSNull: + try encodeNil(forKey: key) + default: + // Ideally we would throw an error here, but we still want to complete the encode to maintain + // backwards compatibility. + try encode("[Non-Encodable]", forKey: key) + } + } +} + +fileprivate extension NSNumber { + + var swiftValue: Any? { + // https://developer.apple.com/documentation/foundation/nsnumber#1776615 + switch String(cString: objCType) { + case "c": + return self as? CBool + case "C": + return self as? CBool + case "s": + return self as? CShort + case "S": + return self as? CUnsignedShort + case "i": + return self as? CInt + case "I": + return self as? CUnsignedInt + case "l": + return self as? CLong + case "L": + return self as? CUnsignedLong + case "q": + return self as? CLongLong + case "Q": + return self as? CUnsignedLongLong + case "f": + return self as? CFloat + case "d": + return self as? CDouble + default: + return self + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/DefaultEventUtils.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/DefaultEventUtils.swift new file mode 100644 index 000000000..5e78a1855 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/DefaultEventUtils.swift @@ -0,0 +1,42 @@ +import Foundation + +public class DefaultEventUtils { + private weak var amplitude: Amplitude? + + public init(amplitude: Amplitude) { + self.amplitude = amplitude + } + + public func trackAppUpdatedInstalledEvent() { + let info = Bundle.main.infoDictionary + let currentBuild = info?["CFBundleVersion"] as? String + let currentVersion = info?["CFBundleShortVersionString"] as? String + let previousBuild: String? = amplitude?.storage.read(key: StorageKey.APP_BUILD) + let previousVersion: String? = amplitude?.storage.read(key: StorageKey.APP_VERSION) + + if amplitude?.configuration.autocapture.contains(.appLifecycles) ?? false { + let lastEventTime: Int64? = amplitude?.storage.read(key: StorageKey.LAST_EVENT_TIME) + if lastEventTime == nil { + self.amplitude?.track(eventType: Constants.AMP_APPLICATION_INSTALLED_EVENT, eventProperties: [ + Constants.AMP_APP_BUILD_PROPERTY: currentBuild ?? "", + Constants.AMP_APP_VERSION_PROPERTY: currentVersion ?? "", + ]) + } else if currentBuild != previousBuild { + self.amplitude?.track(eventType: Constants.AMP_APPLICATION_UPDATED_EVENT, eventProperties: [ + Constants.AMP_APP_BUILD_PROPERTY: currentBuild ?? "", + Constants.AMP_APP_VERSION_PROPERTY: currentVersion ?? "", + Constants.AMP_APP_PREVIOUS_BUILD_PROPERTY: previousBuild ?? "", + Constants.AMP_APP_PREVIOUS_VERSION_PROPERTY: previousVersion ?? "", + ]) + } + } + + if currentBuild != previousBuild { + try? amplitude?.storage.write(key: StorageKey.APP_BUILD, value: currentBuild) + } + if currentVersion != previousVersion { + try? amplitude?.storage.write(key: StorageKey.APP_VERSION, value: currentVersion) + } + } + +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/Diagonostics.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/Diagonostics.swift new file mode 100644 index 000000000..140ad8350 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/Diagonostics.swift @@ -0,0 +1,64 @@ +// +// Diagonostics.swift +// Amplitude-Swift +// +// Created by Qingzhuo Zhen on 3/4/24. +// + +import Foundation + +public class Diagnostics { + + private static let MAX_ERROR_LOGS = 10 + + private var malformedEvents: [String]? + private var errorLogs = NSMutableOrderedSet(capacity: 10) + + init(){} + + func addMalformedEvent(_ event: String) { + if malformedEvents == nil { + malformedEvents = [String]() + } + malformedEvents?.append(event) + } + + func addErrorLog(_ log: String) { + errorLogs.add(log) + + // trim to MAX_ERROR_LOGS elements + while errorLogs.count > Self.MAX_ERROR_LOGS { + errorLogs.removeObject(at: 0) + } + } + + func hasDiagnostics() -> Bool { + return (malformedEvents != nil && malformedEvents!.count > 0) || errorLogs.count > 0 + } + + /** + * Extracts the diagnostics as a JSON string. + * Warning: This will clear stored diagnostics. + * @return JSON string of diagnostics or empty if no diagnostics are present. + */ + func extractDiagonosticsToString() -> String { + if !hasDiagnostics() { + return "" + } + var diagnostics = [String: [String]]() + if malformedEvents != nil && malformedEvents!.count > 0 { + diagnostics["malformed_events"] = malformedEvents + } + if errorLogs.count > 0, let errorStrings = errorLogs.array as? [String] { + diagnostics["error_logs"] = errorStrings + } + do { + let data = try JSONSerialization.data(withJSONObject: diagnostics, options: []) + malformedEvents?.removeAll() + errorLogs.removeAllObjects() + return String(data: data, encoding: .utf8) ?? "" + } catch { + return "" + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/DispatchQueueHolder.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/DispatchQueueHolder.swift new file mode 100644 index 000000000..a31d517f0 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/DispatchQueueHolder.swift @@ -0,0 +1,12 @@ +// +// DispatchQueueHolder.swift +// Amplitude-Swift +// +// Created by Qingzhuo Zhen on 3/8/24. +// + +import Foundation + +class DispatchQueueHolder { + static let storageQueue = DispatchQueue(label: "syncPersistentStorage.amplitude.com") +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/EventPipeline.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/EventPipeline.swift new file mode 100644 index 000000000..0639bf1c6 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/EventPipeline.swift @@ -0,0 +1,117 @@ +// +// EventPipeline.swift +// +// +// Created by Marvin Liu on 10/28/22. +// + +import Foundation + +public class EventPipeline { + var httpClient: HttpClient + let storage: Storage? + let logger: (any Logger)? + let configuration: Configuration + + @Atomic internal var eventCount: Int = 0 + internal var flushTimer: QueueTimer? + private let uploadsQueue = DispatchQueue(label: "uploadsQueue.amplitude.com") + + internal struct UploadTaskInfo { + let events: String + let task: URLSessionDataTask + } + private var uploads = [URL: UploadTaskInfo]() + + init(amplitude: Amplitude) { + storage = amplitude.storage + logger = amplitude.logger + configuration = amplitude.configuration + httpClient = HttpClient(configuration: amplitude.configuration, + diagnostics: amplitude.configuration.diagonostics, + callbackQueue: amplitude.trackingQueue) + flushTimer = QueueTimer(interval: getFlushInterval(), queue: amplitude.trackingQueue) { [weak self] in + self?.flush() + } + } + + func put(event: BaseEvent, completion: (() -> Void)? = nil) { + guard let storage = self.storage else { return } + event.attempts += 1 + do { + try storage.write(key: StorageKey.EVENTS, value: event) + eventCount += 1 + if eventCount >= getFlushCount() { + flush() + } + completion?() + } catch { + logger?.error(message: "Error when storing event: \(error.localizedDescription)") + } + } + + func flush(completion: (() -> Void)? = nil) { + if configuration.offline == true { + logger?.debug(message: "Skipping flush while offline.") + return + } + + logger?.log(message: "Start flushing \(eventCount) events") + eventCount = 0 + guard let storage = self.storage else { return } + storage.rollover() + guard let eventFiles: [URL] = storage.read(key: StorageKey.EVENTS) else { return } + for eventFile in eventFiles { + uploadsQueue.sync { + guard uploads[eventFile] == nil else { + logger?.log(message: "Existing upload in progress, skipping...") + return + } + guard let eventsString = storage.getEventsString(eventBlock: eventFile), + !eventsString.isEmpty else { + return + } + let uploadTask = httpClient.upload(events: eventsString) { [self] result in + let responseHandler = storage.getResponseHandler( + configuration: self.configuration, + eventPipeline: self, + eventBlock: eventFile, + eventsString: eventsString + ) + responseHandler.handle(result: result) + self.completeUpload(for: eventFile) + } + if let uploadTask { + uploads[eventFile] = UploadTaskInfo(events: eventsString, task: uploadTask) + } + } + } + completion?() + } + + func start() { + flushTimer?.resume() + } + + func stop() { + flushTimer?.suspend() + } + + private func getFlushInterval() -> TimeInterval { + return TimeInterval.milliseconds(configuration.flushIntervalMillis) + } + + private func getFlushCount() -> Int { + let count = configuration.flushQueueSize + return count != 0 ? count : 1 + } +} + +extension EventPipeline { + + func completeUpload(for eventBlock: URL) { + uploadsQueue.sync { + uploads[eventBlock] = nil + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/HttpClient.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/HttpClient.swift new file mode 100644 index 000000000..1e8e7cc46 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/HttpClient.swift @@ -0,0 +1,139 @@ +// +// HttpClient.swift +// +// +// Created by Marvin Liu on 10/28/22. +// + +import Foundation + +class HttpClient { + let configuration: Configuration + let session: URLSession + let diagnostics: Diagnostics + let callbackQueue: DispatchQueue + + private lazy var dateFormatter: ISO8601DateFormatter = { + let formatter = ISO8601DateFormatter() + formatter.formatOptions.insert(.withFractionalSeconds) + return formatter + }() + + init(configuration: Configuration, diagnostics: Diagnostics, callbackQueue: DispatchQueue? = nil) { + self.configuration = configuration + self.diagnostics = diagnostics + self.callbackQueue = callbackQueue ?? .global() + + let sessionConfiguration = URLSessionConfiguration.default + sessionConfiguration.httpMaximumConnectionsPerHost = 2 + sessionConfiguration.urlCache = nil + self.session = URLSession(configuration: sessionConfiguration, delegate: nil, delegateQueue: nil) + } + + func upload(events: String, completion: @escaping (_ result: Result) -> Void) -> URLSessionDataTask? { + var sessionTask: URLSessionDataTask? + let backgroundTaskCompletion = VendorSystem.current.beginBackgroundTask() + do { + let request = try getRequest() + let requestData = getRequestData(events: events) + + sessionTask = session.uploadTask(with: request, from: requestData) { [callbackQueue] data, response, error in + callbackQueue.async { + if error != nil { + completion(.failure(error!)) + } else if let httpResponse = response as? HTTPURLResponse { + switch httpResponse.statusCode { + case 1..<300: + completion(.success(httpResponse.statusCode)) + default: + completion(.failure(Exception.httpError(code: httpResponse.statusCode, data: data))) + } + } + backgroundTaskCompletion?() + } + } + sessionTask!.resume() + } catch { + completion(.failure(Exception.httpError(code: 500, data: nil))) + backgroundTaskCompletion?() + } + return sessionTask + } + + func getUrl() -> String { + if let url = configuration.serverUrl, !url.isEmpty { + return url + } + if configuration.serverZone == ServerZone.EU { + return configuration.useBatch ? Constants.EU_BATCH_API_HOST : Constants.EU_DEFAULT_API_HOST + } + return configuration.useBatch ? Constants.BATCH_API_HOST : Constants.DEFAULT_API_HOST + } + + func getRequest() throws -> URLRequest { + let url = getUrl() + + let requestUrl: URL? +#if compiler(>=5.9) + if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) { + requestUrl = URL(string: url, encodingInvalidCharacters: false) + } else { + requestUrl = URL(string: url) + } +#else + requestUrl = URL(string: url) +#endif + + guard let requestUrl else { + throw Exception.invalidUrl(url: url) + } + var request = URLRequest(url: requestUrl, timeoutInterval: 60) + request.httpMethod = "POST" + request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + request.addValue("application/json", forHTTPHeaderField: "Accept") + return request + } + + func getRequestData(events: String) -> Data? { + let apiKey = configuration.apiKey + let clientUploadTime: String = dateFormatter.string(from: getDate()) + var requestPayload = """ + {"api_key":"\(apiKey)","client_upload_time":"\(clientUploadTime)","events":\(events) + """ + if let minIdLength = configuration.minIdLength { + requestPayload += """ + ,"options":{"min_id_length":\(minIdLength)} + """ + } + if diagnostics.hasDiagnostics() { + let diagnosticsInfo = diagnostics.extractDiagonosticsToString() + if !diagnosticsInfo.isEmpty { + requestPayload += """ + ,"request_metadata":{"sdk":\(diagnosticsInfo)} + """ + } + } + requestPayload += "}" + return requestPayload.data(using: .utf8) + } + + func getDate() -> Date { + return Date() + } +} + +extension HttpClient { + enum HttpStatus: Int { + case SUCCESS = 200 + case BAD_REQUEST = 400 + case TIMEOUT = 408 + case PAYLOAD_TOO_LARGE = 413 + case TOO_MANY_REQUESTS = 429 + case FAILED = 500 + } + + enum Exception: Error { + case invalidUrl(url: String) + case httpError(code: Int, data: Data?) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/IdentifyInterceptor.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/IdentifyInterceptor.swift new file mode 100644 index 000000000..d31801ded --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/IdentifyInterceptor.swift @@ -0,0 +1,220 @@ +import Foundation + +public class IdentifyInterceptor { + private struct Identity: Equatable { + let userId: String? + let deviceId: String? + + init(_ event: BaseEvent) { + userId = event.userId + deviceId = event.deviceId + } + + init(_ newUserId: String?, _ newDeviceId: String?) { + userId = newUserId + deviceId = newDeviceId + } + } + + private let configuration: Configuration + private let pipeline: EventPipeline + private let logger: (any Logger)? + private var identifyTransferTimer: QueueTimer? + private let identifyBatchIntervalMillis: Int + private var lastIdentity: Identity? + private let queue: DispatchQueue + + private lazy var storage: any Storage = { + return self.configuration.identifyStorageProvider + }() + + init( + configuration: Configuration, + pipeline: EventPipeline, + queue: DispatchQueue, + identifyBatchIntervalMillis: Int = Constants.Configuration.IDENTIFY_BATCH_INTERVAL_MILLIS + ) { + self.configuration = configuration + self.pipeline = pipeline + self.logger = configuration.loggerProvider + if identifyBatchIntervalMillis < Constants.MIN_IDENTIFY_BATCH_INTERVAL_MILLIS { + self.logger?.warn(message: "Minimum `identifyBatchIntervalMillis` is \(Constants.MIN_IDENTIFY_BATCH_INTERVAL_MILLIS).") + } + self.identifyBatchIntervalMillis = max(identifyBatchIntervalMillis, Constants.MIN_IDENTIFY_BATCH_INTERVAL_MILLIS) + self.lastIdentity = Identity(nil, nil) + self.queue = queue + } + + public func intercept(event: BaseEvent) -> BaseEvent? { + do { + return try interceptIdentifyEvent(event) + } catch { + logger?.error(message: "Error when intercept event: \(error.localizedDescription)") + } + + return event + } + + private func isIdentityUpdated(_ event: BaseEvent) -> Bool { + let eventIdentity = Identity(event) + + guard let currentIdenity = lastIdentity else { + lastIdentity = eventIdentity + return true + } + + if eventIdentity != currentIdenity { + lastIdentity = eventIdentity + return true + } + + return false + } + + private func interceptIdentifyEvent(_ event: BaseEvent) throws -> BaseEvent? { + if isIdentityUpdated(event) { + transferInterceptedIdentifyEvent() + } + + switch event.eventType { + case Constants.IDENTIFY_EVENT: + if isInterceptEvent(event) { + try writeEventToStorage(event) + return nil + } else if hasOperation(properties: event.userProperties, operation: Identify.Operation.CLEAR_ALL) { + removeEventsFromStorage() + } else { + transferInterceptedIdentifyEvent() + } + case Constants.GROUP_IDENTIFY_EVENT: + break + default: + if hasOperation(properties: event.userProperties, operation: Identify.Operation.CLEAR_ALL) { + removeEventsFromStorage() + } else { + transferInterceptedIdentifyEvent() + } + } + + return event + } + + func getCombinedInterceptedIdentify() -> IdentifyEvent? { + var combinedInterceptedIdentify: IdentifyEvent? + let eventFiles: [URL]? = storage.read(key: StorageKey.EVENTS) + + if let eventFiles { + for eventFile in eventFiles { + guard let eventsString = storage.getEventsString(eventBlock: eventFile) else { + continue + } + if eventsString.isEmpty { + continue + } + + if let events: [IdentifyEvent] = BaseEvent.fromArrayString(jsonString: eventsString) { + for event in events { + if let dest = combinedInterceptedIdentify { + combinedInterceptedIdentify = mergeEventUserProperties(destination: dest, source: event) + } else { + combinedInterceptedIdentify = event + } + } + } + } + + for eventFile in eventFiles { + storage.remove(eventBlock: eventFile) + } + } + + return combinedInterceptedIdentify + } + + func getUserPropertySetValues(_ event: BaseEvent) -> [String: Any?]? { + if let setProperties = event.userProperties?[Identify.Operation.SET.rawValue] as? [String: Any?]? { + return setProperties + } + + return nil + } + + func mergeEventUserProperties(destination: IdentifyEvent, source: IdentifyEvent) -> IdentifyEvent { + var sourceUserProperties: [String: Any?]? + var destinationUserProperties: [String: Any?]? + + // note destination/source contain only $set properties + sourceUserProperties = getUserPropertySetValues(source) + destinationUserProperties = getUserPropertySetValues(destination) + + destination.userProperties = destination.userProperties ?? [:] + destination.userProperties![Identify.Operation.SET.rawValue] = mergeUserProperties( + destination: destinationUserProperties, + source: sourceUserProperties + ) + + return destination + } + + func transferInterceptedIdentifyEvent() { + if let interceptedEvent = getCombinedInterceptedIdentify() { + pipeline.put(event: interceptedEvent) + } + } + + private func writeEventToStorage(_ event: BaseEvent) throws { + try storage.write(key: StorageKey.EVENTS, value: event) + scheduleTransferInterceptedIdentifyEvent() + } + + private func removeEventsFromStorage() { + guard let eventFiles: [URL] = storage.read(key: StorageKey.EVENTS) else { return } + for eventFile in eventFiles { + storage.remove(eventBlock: eventFile) + } + } + + private func scheduleTransferInterceptedIdentifyEvent() { + guard identifyTransferTimer == nil else { + return + } + + identifyTransferTimer = QueueTimer(interval: getIdentifyBatchInterval(), once: true, queue: queue) { [weak self] in + let transferInterceptedIdentifyEvent = self?.transferInterceptedIdentifyEvent + self?.identifyTransferTimer = nil + transferInterceptedIdentifyEvent?() + } + } + + func mergeUserProperties(destination: [String: Any?]?, source: [String: Any?]?) -> [String: Any?] { + var result = destination ?? [:] + result = result.merging(source ?? [:]) { old, new in (new == nil || new is NSNull) ? old : new } + + return result + } + + /** + * Returns true if the given event should be intercepted + */ + func isInterceptEvent(_ event: BaseEvent) -> Bool { + return event.eventType == Constants.IDENTIFY_EVENT + && isEmptyValues(event.groups) + && hasOnlyOperation(properties: event.userProperties, operation: Identify.Operation.SET) + } + + private func isEmptyValues(_ values: [String: Any?]?) -> Bool { + return values == nil || values?.isEmpty == true + } + + private func hasOnlyOperation(properties: [String: Any?]?, operation: Identify.Operation) -> Bool { + return hasOperation(properties: properties, operation: operation) && properties?.count == 1 + } + + private func hasOperation(properties: [String: Any?]?, operation: Identify.Operation) -> Bool { + return !isEmptyValues(properties) && properties![operation.rawValue] != nil + } + + public func getIdentifyBatchInterval() -> TimeInterval { + return TimeInterval.milliseconds(self.identifyBatchIntervalMillis) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/OutputFileStream.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/OutputFileStream.swift new file mode 100644 index 000000000..0bbddd445 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/OutputFileStream.swift @@ -0,0 +1,110 @@ +// +// OutputFileStream.swift +// +// +// Created by Marvin Liu on 11/22/22. +// +// Originally from Segment: https://github.com/segmentio/analytics-swift, under MIT license. + +import Foundation + +#if os(Linux) + import Glibc +#else + import Darwin.C +#endif + +internal class OutputFileStream { + enum OutputStreamError: Error { + case invalidPath(String) + case unableToOpen(String) + case unableToWrite(String) + case unableToCreate(String) + case unableToClose(String) + } + + var fileHandle: FileHandle? + let fileURL: URL + + init(fileURL: URL) throws { + self.fileURL = fileURL + let path = fileURL.path + guard path.isEmpty == false else { throw OutputStreamError.invalidPath(path) } + } + + func create() throws { + let path = fileURL.path + if FileManager.default.fileExists(atPath: path) { + throw OutputStreamError.unableToCreate(path) + } else { + let created = FileManager.default.createFile(atPath: fileURL.path, contents: nil) + if created == false { + throw OutputStreamError.unableToCreate(path) + } else { + try open() + } + } + } + + func open() throws { + if fileHandle != nil { return } + do { + fileHandle = try FileHandle(forWritingTo: fileURL) + seekToEnd() + } catch { + throw OutputStreamError.unableToOpen(fileURL.path) + } + } + + func seekToEnd() { + if fileHandle == nil { + return + } + if #available(macOS 10.15.4, iOS 13.4, macCatalyst 13.4, tvOS 13.4, watchOS 6.2, *) { + _ = try? fileHandle?.seekToEnd() + } else if #available(tvOS 13.0, *) { + try? fileHandle?.seek(toOffset: .max) + } else { + fileHandle?.seekToEndOfFile() + } + } + + func write(_ data: Data) throws { + guard data.isEmpty == false else { return } + if #available(macOS 10.15.4, iOS 13.4, macCatalyst 13.4, tvOS 13.4, watchOS 6.2, *) { + do { + try fileHandle?.write(contentsOf: data) + } catch { + throw OutputStreamError.unableToWrite(fileURL.path) + } + } else { + fileHandle?.write(data) + } + } + + func write(_ string: String, _ append: Bool = true) throws { + guard string.isEmpty == false else { return } + if let data = string.data(using: .utf8) { + if append { + seekToEnd() + } + try write(data) + } + } + + func close() throws { + do { + let existing = fileHandle + fileHandle = nil + if #available(tvOS 13.0, *) { + try existing?.synchronize() + try existing?.close() + } else { + existing?.synchronizeFile() + existing?.closeFile() + } + } catch { + throw OutputStreamError.unableToClose(fileURL.path) + } + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/PersistentStorageResponseHandler.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/PersistentStorageResponseHandler.swift new file mode 100644 index 000000000..2c5cdf959 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/PersistentStorageResponseHandler.swift @@ -0,0 +1,185 @@ +// +// PersistentStorageResponseHandler.swift +// +// +// Created by Marvin Liu on 11/30/22. +// + +import Foundation + +class PersistentStorageResponseHandler: ResponseHandler { + var configuration: Configuration + var storage: PersistentStorage + var eventPipeline: EventPipeline + var eventBlock: URL + var eventsString: String + + init( + configuration: Configuration, + storage: PersistentStorage, + eventPipeline: EventPipeline, + eventBlock: URL, + eventsString: String + ) { + self.configuration = configuration + self.storage = storage + self.eventPipeline = eventPipeline + self.eventBlock = eventBlock + self.eventsString = eventsString + } + + func handleSuccessResponse(code: Int) { + guard let events = BaseEvent.fromArrayString(jsonString: eventsString) else { + storage.remove(eventBlock: eventBlock) + removeEventCallbackByEventsString(eventsString: eventsString) + return + } + triggerEventsCallback(events: events, code: code, message: "Successfully send event") + storage.remove(eventBlock: eventBlock) + } + + func handleBadRequestResponse(data: [String: Any]) { + guard let events = BaseEvent.fromArrayString(jsonString: eventsString) else { + storage.remove(eventBlock: eventBlock) + removeEventCallbackByEventsString(eventsString: eventsString) + return + } + + if events.count == 1 { + let error = data["error"] as? String ?? "" + triggerEventsCallback( + events: events, + code: HttpClient.HttpStatus.BAD_REQUEST.rawValue, + message: error + ) + storage.remove(eventBlock: eventBlock) + return + } + + var dropIndexes = Set() + if let eventsWithInvalidFields = data["events_with_invalid_fields"] as? [String: [Int]] { + dropIndexes.formUnion(Self.collectIndices(data: eventsWithInvalidFields)) + } + if let eventsWithMissingFields = data["events_with_missing_fields"] as? [String: [Int]] { + dropIndexes.formUnion(Self.collectIndices(data: eventsWithMissingFields)) + } + if let silencedEvents = data["silenced_events"] as? [Int] { + dropIndexes.formUnion(silencedEvents) + } + var silencedDevices = Set() + if let silencedDevicesArray = data["silenced_devices"] as? [String] { + silencedDevices.formUnion(silencedDevicesArray) + } + + var eventsToDrop = [BaseEvent]() + var eventsToRetry = [BaseEvent]() + for (index, event) in events.enumerated() { + if dropIndexes.contains(index) || (event.deviceId != nil && silencedDevices.contains(event.deviceId!)) { + eventsToDrop.append(event) + } else { + eventsToRetry.append(event) + } + } + + let error = data["error"] as? String ?? "" + triggerEventsCallback(events: eventsToDrop, code: HttpClient.HttpStatus.BAD_REQUEST.rawValue, message: error) + + eventsToRetry.forEach { event in + eventPipeline.put(event: event) + } + + storage.remove(eventBlock: eventBlock) + } + + func handlePayloadTooLargeResponse(data: [String: Any]) { + guard let events = BaseEvent.fromArrayString(jsonString: eventsString) else { + storage.remove(eventBlock: eventBlock) + removeEventCallbackByEventsString(eventsString: eventsString) + return + } + if events.count == 1 { + let error = data["error"] as? String ?? "" + triggerEventsCallback( + events: events, + code: HttpClient.HttpStatus.PAYLOAD_TOO_LARGE.rawValue, + message: error + ) + storage.remove(eventBlock: eventBlock) + return + } + storage.splitBlock(eventBlock: eventBlock, events: events) + } + + func handleTooManyRequestsResponse(data: [String: Any]) { + // wait for next time to pick it up + } + + func handleTimeoutResponse(data: [String: Any]) { + // Wait for next time to pick it up + } + + func handleFailedResponse(data: [String: Any]) { + // wait for next time to try again + } + + func handle(result: Result) { + switch result { + case .success(let code): + // We don't care about the data when success + handleSuccessResponse(code: code) + case .failure(let error): + switch error { + case HttpClient.Exception.httpError(let code, let data): + var json = [String: Any]() + if data != nil { + json = (try? JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any]) ?? json + } + switch code { + case HttpClient.HttpStatus.BAD_REQUEST.rawValue: + handleBadRequestResponse(data: json) + case HttpClient.HttpStatus.PAYLOAD_TOO_LARGE.rawValue: + handlePayloadTooLargeResponse(data: json) + case HttpClient.HttpStatus.TIMEOUT.rawValue: + handleTimeoutResponse(data: json) + case HttpClient.HttpStatus.TOO_MANY_REQUESTS.rawValue: + handleTooManyRequestsResponse(data: json) + case HttpClient.HttpStatus.FAILED.rawValue: + handleFailedResponse(data: json) + default: + handleFailedResponse(data: json) + } + default: + break + } + } + } +} + +extension PersistentStorageResponseHandler { + private func triggerEventsCallback(events: [BaseEvent], code: Int, message: String) { + events.forEach { event in + configuration.callback?(event, code, message) + if let eventInsertId = event.insertId, let eventCallback = storage.getEventCallback(insertId: eventInsertId) + { + eventCallback(event, code, message) + storage.removeEventCallback(insertId: eventInsertId) + } + } + } + + func removeEventCallbackByEventsString(eventsString: String) { + guard let regex = try? NSRegularExpression(pattern: #"\"insert_id\":\"(.{36})\","#) else { + return + } + let eventsNSString = NSString(string: eventsString) + regex.matches(in: eventsString, options: [], range: NSRange(location: 0, length: eventsNSString.length)).forEach + { match in + (1.. Void + + @Atomic var state: State = .suspended + + static var timers = [QueueTimer]() + + static func schedule(interval: TimeInterval, queue: DispatchQueue = .main, handler: @escaping () -> Void) { + let timer = QueueTimer(interval: interval, queue: queue, handler: handler) + Self.timers.append(timer) + } + + init(interval: TimeInterval, once: Bool = false, queue: DispatchQueue = .main, handler: @escaping () -> Void) { + self.interval = interval + self.queue = queue + self.handler = handler + + timer = DispatchSource.makeTimerSource(flags: [], queue: queue) + timer.schedule(deadline: .now() + self.interval, repeating: once ? .infinity : self.interval) + timer.setEventHandler { [weak self] in + self?.handler() + } + resume() + } + + deinit { + timer.setEventHandler { + // do nothing ... + } + // if timer is suspended, we must resume if we're going to cancel. + timer.cancel() + resume() + } + + func suspend() { + if state == .suspended { + return + } + state = .suspended + timer.suspend() + } + + func resume() { + if state == .resumed { + return + } + state = .resumed + timer.resume() + } +} + +extension TimeInterval { + static func milliseconds(_ value: Int) -> TimeInterval { + return TimeInterval(value / 1000) + } + + static func seconds(_ value: Int) -> TimeInterval { + return TimeInterval(value) + } + + static func hours(_ value: Int) -> TimeInterval { + return TimeInterval(60 * value) + } + + static func days(_ value: Int) -> TimeInterval { + return TimeInterval((60 * value) * 24) + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/SandboxHelper.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/SandboxHelper.swift new file mode 100644 index 000000000..d51326692 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/SandboxHelper.swift @@ -0,0 +1,25 @@ +// +// SandboxHelper.swift +// Amplitude-Swift +// +// Created by Justin Fiedler on 2/1/24. +// + +import Foundation + +public class SandboxHelper { + internal func getEnvironment() -> [String: String] { + return ProcessInfo.processInfo.environment + } + + public func isSandboxEnabled() -> Bool { + #if os(macOS) + // Check if macOS app has "App Sandbox" enabled + let environment = getEnvironment() + return environment["APP_SANDBOX_CONTAINER_ID"] != nil + #else + // Other platforms (iOS, tvOS, watchOs) are sandboxed by default + return true + #endif + } +} diff --git a/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/UrlExtension.swift b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/UrlExtension.swift new file mode 100644 index 000000000..b2abf12d1 --- /dev/null +++ b/dydx/Pods/AmplitudeSwift/Sources/Amplitude/Utilities/UrlExtension.swift @@ -0,0 +1,31 @@ +// +// UrlExtension.swift +// +// +// Created by Marvin Liu on 12/7/22. +// + +import Foundation + +extension URL { + func appendFileNameSuffix(suffix: String) -> URL { + var filename = deletingPathExtension().lastPathComponent + "\(suffix)" + if !pathExtension.isEmpty { + filename += ".\(pathExtension)" + } + return deletingLastPathComponent().appendingPathComponent(filename) + } + + func appendFileNamePrefix(prefix: String) -> URL { + var filename = "\(prefix)" + deletingPathExtension().lastPathComponent + if !pathExtension.isEmpty { + filename += ".\(pathExtension)" + } + return deletingLastPathComponent().appendingPathComponent(filename) + } + + func hasPrefix(_ prefix: String) -> Bool { + let filename = deletingPathExtension().lastPathComponent + return filename.hasPrefix(prefix) + } +} diff --git a/dydx/Pods/AnalyticsConnector/LICENSE b/dydx/Pods/AnalyticsConnector/LICENSE new file mode 100644 index 000000000..1af0c5646 --- /dev/null +++ b/dydx/Pods/AnalyticsConnector/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Amplitude Analytics + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dydx/Pods/AnalyticsConnector/README.md b/dydx/Pods/AnalyticsConnector/README.md new file mode 100644 index 000000000..df2a9bd6f --- /dev/null +++ b/dydx/Pods/AnalyticsConnector/README.md @@ -0,0 +1,7 @@ +# analytics-connector-ios + +Connector library for seamless integration between Amplitude Analytics and Experiment SDKs. + +## Getting Started + +See the [Documentation](https://amplitude-lab.readme.io/docs/ios-sdk) for getting started. diff --git a/dydx/Pods/AnalyticsConnector/Sources/AnalyticsConnector/AnalyticsConnector.h b/dydx/Pods/AnalyticsConnector/Sources/AnalyticsConnector/AnalyticsConnector.h new file mode 100644 index 000000000..eeb76be95 --- /dev/null +++ b/dydx/Pods/AnalyticsConnector/Sources/AnalyticsConnector/AnalyticsConnector.h @@ -0,0 +1,17 @@ +// +// AnalyticsConnector.h +// AnalyticsConnector +// +// Created by Brian Giori on 12/20/21. +// + + +#import + +//! Project version number for AnalyticsConnector. +FOUNDATION_EXPORT double AnalyticsConnectorVersionNumber; + +//! Project version string for AnalyticsConnector. +FOUNDATION_EXPORT const unsigned char AnalyticsConnectorVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/dydx/Pods/AnalyticsConnector/Sources/AnalyticsConnector/AnalyticsConnector.swift b/dydx/Pods/AnalyticsConnector/Sources/AnalyticsConnector/AnalyticsConnector.swift new file mode 100644 index 000000000..d32d0510b --- /dev/null +++ b/dydx/Pods/AnalyticsConnector/Sources/AnalyticsConnector/AnalyticsConnector.swift @@ -0,0 +1,36 @@ +// +// AnalyticsConnector.swift +// AnalyticsConnector +// +// Created by Brian Giori on 12/20/21. +// + +import Foundation + +@objc public class AnalyticsConnector : NSObject { + + private static let instancesLock: DispatchSemaphore = DispatchSemaphore(value: 1) + private static var instances: [String:AnalyticsConnector] = [:] + + @objc public static func getInstance(_ instanceName: String) -> AnalyticsConnector { + instancesLock.wait() + defer { instancesLock.signal() } + if let instance = instances[instanceName] { + return instance + } else { + instances[instanceName] = AnalyticsConnector( + eventBridge: EventBridgeImpl(), + identityStore: IdentityStoreImpl() + ) + return instances[instanceName]! + } + } + + @objc public let eventBridge: EventBridge + @objc public let identityStore: IdentityStore + + private init(eventBridge: EventBridge, identityStore: IdentityStore) { + self.eventBridge = eventBridge + self.identityStore = identityStore + } +} diff --git a/dydx/Pods/AnalyticsConnector/Sources/AnalyticsConnector/EventBridge.swift b/dydx/Pods/AnalyticsConnector/Sources/AnalyticsConnector/EventBridge.swift new file mode 100644 index 000000000..966fa7ef5 --- /dev/null +++ b/dydx/Pods/AnalyticsConnector/Sources/AnalyticsConnector/EventBridge.swift @@ -0,0 +1,77 @@ +// +// EventBridge.swift +// EventBridge +// +// Created by Brian Giori on 12/21/21. +// + +import Foundation + +@objc public class AnalyticsEvent: NSObject { + @objc public let eventType: String + @objc public let eventProperties: NSDictionary? + @objc public let userProperties: NSDictionary? + + @objc public init(eventType: String, eventProperties: NSDictionary?, userProperties: NSDictionary?) { + self.eventType = eventType + self.eventProperties = eventProperties + self.userProperties = userProperties + } + + @objc public override func isEqual(_ object: Any?) -> Bool { + guard let other = object as? AnalyticsEvent else { + return false + } + return self.eventType == other.eventType && + dictionaryEquals(self.eventProperties, other.eventProperties) && + dictionaryEquals(self.userProperties, other.userProperties) + } +} + +@objc public protocol EventBridge { + @objc func setEventReceiver(_ eventReceiver: @escaping (AnalyticsEvent) -> ()) + @objc func logEvent(event: AnalyticsEvent) +} + +@objc internal class EventBridgeImpl: NSObject, EventBridge { + + private let eventReceiverLock = DispatchSemaphore(value: 1) + private var eventReceiver: ((AnalyticsEvent) -> ())? = nil + private var eventQueue: [AnalyticsEvent] = [] + + @objc func setEventReceiver(_ eventReceiver: @escaping (AnalyticsEvent) -> ()) { + eventReceiverLock.wait() + self.eventReceiver = eventReceiver + let events = eventQueue + eventQueue = [] + eventReceiverLock.signal() + for event in events { + eventReceiver(event) + } + } + + @objc func logEvent(event: AnalyticsEvent) { + eventReceiverLock.wait() + defer { eventReceiverLock.signal() } + guard let eventReceiver = self.eventReceiver else { + if (eventQueue.count < 512) { + eventQueue.append(event) + } + return + } + eventReceiver(event) + } +} + +internal func dictionaryEquals(_ d1: NSDictionary?, _ d2: NSDictionary?) -> Bool { + if let d1 = d1, let d2 = d2 as? [AnyHashable:Any] { + guard d1.isEqual(to: d2) else { + return false + } + } else { + guard d1 == nil, d2 == nil else { + return false + } + } + return true +} diff --git a/dydx/Pods/AnalyticsConnector/Sources/AnalyticsConnector/IdentityStore.swift b/dydx/Pods/AnalyticsConnector/Sources/AnalyticsConnector/IdentityStore.swift new file mode 100644 index 000000000..953d9c072 --- /dev/null +++ b/dydx/Pods/AnalyticsConnector/Sources/AnalyticsConnector/IdentityStore.swift @@ -0,0 +1,151 @@ +// +// IdentityStore.swift +// AnalyticsConnector +// +// Created by Brian Giori on 12/21/21. +// + +import Foundation + +internal let ID_OP_SET = "$set" +internal let ID_OP_UNSET = "$unset" +internal let ID_OP_CLEAR_ALL = "$clearAll" + +@objc public class Identity: NSObject { + @objc public let userId: String? + @objc public let deviceId: String? + @objc public let userProperties: NSDictionary + @objc public init(userId: String? = nil, deviceId: String? = nil, userProperties: NSDictionary? = nil) { + self.userId = userId + self.deviceId = deviceId + self.userProperties = userProperties ?? NSDictionary() + } + @objc public override func isEqual(_ object: Any?) -> Bool { + guard let other = object as? Identity else { + return false + } + return self.userId == other.userId && + self.deviceId == other.deviceId && + dictionaryEquals(self.userProperties, other.userProperties) + } +} + +@objc public protocol IdentityStore { + @objc func getIdentity() -> Identity + @objc func setIdentity(_ identity: Identity) + @objc func editIdentity() -> IdentityStoreEditor + @objc func addIdentityListener(key: String, _ listener: @escaping (Identity) -> ()) + @objc func removeIdentityListener(key: String) +} + +@objc public protocol IdentityStoreEditor { + @objc func setUserId(_ userId: String?) -> IdentityStoreEditor + @objc func setDeviceId(_ deviceId: String?) -> IdentityStoreEditor + @objc func setUserProperties(_ userProperties: NSDictionary) -> IdentityStoreEditor + @objc func updateUserProperties(_ userPropertyActions: NSDictionary) -> IdentityStoreEditor + @objc func commit() +} + +@objc internal class IdentityStoreImpl: NSObject, IdentityStore { + private let identityLock = DispatchSemaphore(value: 1) + private var identity = Identity() + private let listenersLock = DispatchSemaphore(value: 1) + private var listeners: [String: (Identity) -> ()] = [:] + + @objc func getIdentity() -> Identity { + identityLock.wait() + defer { identityLock.signal() } + return identity + } + + @objc func setIdentity(_ identity: Identity) { + identityLock.wait() + let identityChanged = self.identity != identity + self.identity = identity + identityLock.signal() + if identityChanged { + listenersLock.wait() + let safeListeners = listeners.values + listenersLock.signal() + for listener in safeListeners { + listener(identity) + } + } + } + + @objc func editIdentity() -> IdentityStoreEditor { + return IdentityStoreEditorImpl(identityStore: self) + } + + @objc func addIdentityListener(key: String, _ listener: @escaping (Identity) -> ()) { + listenersLock.wait() + defer { listenersLock.signal() } + listeners[key] = listener + } + + @objc func removeIdentityListener(key: String) { + listenersLock.wait() + defer { listenersLock.signal() } + listeners.removeValue(forKey: key) + } +} + +@objc internal class IdentityStoreEditorImpl: NSObject, IdentityStoreEditor { + + private let identityStore: IdentityStore + + private var userId: String? + private var deviceId: String? + private var userProperties: NSMutableDictionary + + internal init(identityStore: IdentityStore) { + let identity = identityStore.getIdentity() + self.userId = identity.userId + self.deviceId = identity.deviceId + self.userProperties = identity.userProperties.mutableCopy() as? NSMutableDictionary ?? NSMutableDictionary() + self.identityStore = identityStore + } + + @objc func setUserId(_ userId: String?) -> IdentityStoreEditor { + self.userId = userId + return self + } + + @objc func setDeviceId(_ deviceId: String?) -> IdentityStoreEditor { + self.deviceId = deviceId + return self + } + + @objc func setUserProperties(_ userProperties: NSDictionary) -> IdentityStoreEditor { + if let userProperties = userProperties.mutableCopy() as? NSMutableDictionary { + self.userProperties = userProperties + } + return self + } + + @objc func updateUserProperties(_ userPropertyActions: NSDictionary) -> IdentityStoreEditor { + userPropertyActions.forEach { (action: Any, properties: Any) in + guard let action = action as? String else { + return + } + guard let properties = properties as? [AnyHashable: Any] else { + return + } + switch (action) { + case ID_OP_SET: + self.userProperties.addEntries(from: properties) + case ID_OP_UNSET: + self.userProperties.removeObjects(forKeys: Array(properties.keys)) + case ID_OP_CLEAR_ALL: + self.userProperties.removeAllObjects() + default: break + } + } + return self + } + + @objc func commit() { + let identity = Identity(userId: userId, deviceId: deviceId, userProperties: userProperties) + self.identityStore.setIdentity(identity) + } +} diff --git a/dydx/Pods/AppsFlyerFramework/binaries/Resources/nonStrict/PrivacyInfo.xcprivacy b/dydx/Pods/AppsFlyerFramework/binaries/Resources/nonStrict/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..5da2889f7 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/Resources/nonStrict/PrivacyInfo.xcprivacy @@ -0,0 +1,73 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyTrackingDomains + + att-attr.whappsflyer.com + att-attr.appsflyer-cn.com + att-attr.hevents.appsflyer-cn.com + att-launches.whappsflyer.com + att-launches.appsflyer-cn.com + att-launches.hevents.appsflyer-cn.com + att-conversions.hevents.appsflyer-cn.com + att-conversions.appsflyer-cn.com + att-conversions.whappsflyer.com + att-dlsdk.hevents.appsflyer-cn.com + att-dlsdk.appsflyer-cn.com + att-dlsdk.whappsflyer.com + att-dlsdk.appsflyersdk.com + att-conversions.appsflyersdk.com + att-launches.appsflyersdk.com + att-attr.appsflyersdk.com + + NSPrivacyTracking + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + NSPrivacyAccessedAPITypeReasons + + C617.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + + + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/Info.plist b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/Info.plist new file mode 100644 index 000000000..f5fc808ea --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/Info.plist @@ -0,0 +1,107 @@ + + + + + AvailableLibraries + + + BinaryPath + AppsFlyerLib.framework/Versions/A/AppsFlyerLib + LibraryIdentifier + macos-arm64_x86_64 + LibraryPath + AppsFlyerLib.framework + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + macos + + + BinaryPath + AppsFlyerLib.framework/AppsFlyerLib + LibraryIdentifier + tvos-arm64_x86_64-simulator + LibraryPath + AppsFlyerLib.framework + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + tvos + SupportedPlatformVariant + simulator + + + BinaryPath + AppsFlyerLib.framework/AppsFlyerLib + LibraryIdentifier + ios-arm64 + LibraryPath + AppsFlyerLib.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + BinaryPath + AppsFlyerLib.framework/AppsFlyerLib + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + AppsFlyerLib.framework + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + BinaryPath + AppsFlyerLib.framework/AppsFlyerLib + LibraryIdentifier + tvos-arm64 + LibraryPath + AppsFlyerLib.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + tvos + + + BinaryPath + AppsFlyerLib.framework/Versions/A/AppsFlyerLib + LibraryIdentifier + ios-arm64_x86_64-maccatalyst + LibraryPath + AppsFlyerLib.framework + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + maccatalyst + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeDirectory b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeDirectory new file mode 100644 index 000000000..85df5e0ee Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeDirectory differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeRequirements b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeRequirements new file mode 100644 index 000000000..4b25460e6 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeRequirements differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeRequirements-1 b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeRequirements-1 new file mode 100644 index 000000000..825d0ceed Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeRequirements-1 differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeResources b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeResources new file mode 100644 index 000000000..f2341dcec --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeResources @@ -0,0 +1,2248 @@ + + + + + files + + ios-arm64/AppsFlyerLib.framework/AppsFlyerLib + + dyd5ME/sFEZuDK+C4I57EQgp3Zs= + + ios-arm64/AppsFlyerLib.framework/Headers/AFAdRevenueData.h + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + ios-arm64/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + ios-arm64/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h + + 26YQ/Pm+Ilp2zDJjuSccfLC+V0A= + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib.h + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h + + LysKB9CL90x+MWXOuuRPixmiVJo= + + ios-arm64/AppsFlyerLib.framework/Info.plist + + TYoGiVka9ZfpHbgCmmVRfEfbIhY= + + ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.private.swiftinterface + + iMeJNqlBcn4wSWoSYbcc105heA0= + + ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.swiftdoc + + Gn52dLwhNXom4k91P9HHSlAPFjo= + + ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.swiftinterface + + iMeJNqlBcn4wSWoSYbcc105heA0= + + ios-arm64/AppsFlyerLib.framework/Modules/module.modulemap + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + ios-arm64/AppsFlyerLib.framework/PrivacyInfo.xcprivacy + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/AppsFlyerLib + + YZ8xYTW61zoKso2TqVgeK5dzb9M= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFAdRevenueData.h + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFSDKPurchaseDetails.h + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFSDKValidateAndLogResult.h + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerConsent.h + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerCrossPromotionHelper.h + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLink.h + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLinkResult.h + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib-Swift.h + + FOoS78OxL9Z3oz6vB1wWn0PArv0= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib.h + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLinkGenerator.h + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerShareInviteHelper.h + + LysKB9CL90x+MWXOuuRPixmiVJo= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.private.swiftinterface + + gBZnNBYz2aEYupow29e9wPxTNdQ= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.swiftdoc + + rkRaNKF1Oee1Iby1uIIbSJDTM4o= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.swiftinterface + + gBZnNBYz2aEYupow29e9wPxTNdQ= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.private.swiftinterface + + eTOz6XNmXUiCwNP0jSwlERSPci4= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.swiftdoc + + 75DqHCaiZlbn1Yz0KOftB014IJg= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.swiftinterface + + eTOz6XNmXUiCwNP0jSwlERSPci4= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/module.modulemap + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Resources/Info.plist + + +kpkTPBuaY/TwXE7c8BxCa+Wohs= + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Resources/PrivacyInfo.xcprivacy + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/AppsFlyerLib + + B3dCzimbYur8R/rxKmJegQzz9AA= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFAdRevenueData.h + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h + + FOoS78OxL9Z3oz6vB1wWn0PArv0= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib.h + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h + + LysKB9CL90x+MWXOuuRPixmiVJo= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Info.plist + + R1HkFJpIbi3chDScxpiSYpL4nMw= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface + + i1iM4UdBHXU2lmDnJ/Czh5pfamI= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + ex2JxlYuxQFqky3dqJcNjo7d+qo= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftinterface + + i1iM4UdBHXU2lmDnJ/Czh5pfamI= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface + + wn2IXmAG5n5kmUW9IvrqLMpExQs= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + Jof2V2SbxYOsGiZHkRxK/hYz/Co= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftinterface + + wn2IXmAG5n5kmUW9IvrqLMpExQs= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/module.modulemap + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/PrivacyInfo.xcprivacy + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeDirectory + + HuLJpaYQiGbZjE1b+QhKTHrAx5M= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements + + OnX22wWFKRSOFN1+obRynMCeyXM= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements-1 + + y3CdoXblRLRuwf16oETs3yJJkiY= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeResources + + 46WmbKN58EkkSmW8MDGDbBBSUkg= + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeSignature + + 2jmj7l5rSw0yVb/vlWAYkK/YBwk= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/AppsFlyerLib + + zGKQq4IIRn5iEi66SD02YnUGh8I= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFAdRevenueData.h + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFSDKPurchaseDetails.h + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFSDKValidateAndLogResult.h + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerConsent.h + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerCrossPromotionHelper.h + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLink.h + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLinkResult.h + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib-Swift.h + + FOoS78OxL9Z3oz6vB1wWn0PArv0= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib.h + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLinkGenerator.h + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerShareInviteHelper.h + + LysKB9CL90x+MWXOuuRPixmiVJo= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.private.swiftinterface + + jsvLKdoRWGO+HkCcMBqlXVPyv0U= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.swiftdoc + + 415kRT2U4WlV4dTTPlcvqyA7w24= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.swiftinterface + + jsvLKdoRWGO+HkCcMBqlXVPyv0U= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.private.swiftinterface + + iYpvc2pf7F3wWJ7DDv4zVU8ppEw= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.swiftdoc + + i/MkainfYqjckQ1Uy9RGZGO8T3g= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.swiftinterface + + iYpvc2pf7F3wWJ7DDv4zVU8ppEw= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/module.modulemap + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Resources/Info.plist + + zePZQixRSD8kIhrDumpD+f4nxek= + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Resources/PrivacyInfo.xcprivacy + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + tvos-arm64/AppsFlyerLib.framework/AppsFlyerLib + + 5tdxfvaydlr5DkrSg35u9eZNUk4= + + tvos-arm64/AppsFlyerLib.framework/Headers/AFAdRevenueData.h + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + tvos-arm64/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + tvos-arm64/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h + + 26YQ/Pm+Ilp2zDJjuSccfLC+V0A= + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib.h + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h + + LysKB9CL90x+MWXOuuRPixmiVJo= + + tvos-arm64/AppsFlyerLib.framework/Info.plist + + 8Fwk7KiO/1Qp2LIovflf6lYJyaA= + + tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.private.swiftinterface + + f+rVaiYkUFCA0df61t6MDqxIL1Q= + + tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.swiftdoc + + AXvakhu1gsIKafoHXqzDDJqtg24= + + tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.swiftinterface + + f+rVaiYkUFCA0df61t6MDqxIL1Q= + + tvos-arm64/AppsFlyerLib.framework/Modules/module.modulemap + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + tvos-arm64/AppsFlyerLib.framework/PrivacyInfo.xcprivacy + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/AppsFlyerLib + + T4wGOjfddTsOJgEeQEeOG24QHKs= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFAdRevenueData.h + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h + + FOoS78OxL9Z3oz6vB1wWn0PArv0= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib.h + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h + + LysKB9CL90x+MWXOuuRPixmiVJo= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Info.plist + + QBaoiDQ7L0WkBy8Tl2WAWvE7sEA= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.private.swiftinterface + + L79lHVuEXcZQ8b/onXpEgE06X84= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftdoc + + apgpLdpztYHXcPhawO2GqU71RX0= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftinterface + + L79lHVuEXcZQ8b/onXpEgE06X84= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.private.swiftinterface + + NGZEK6+XnTIzCfCwhIPIg2HLBpw= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftdoc + + a1yMUwktSjfVrmA0kgB0AdoKRiw= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftinterface + + NGZEK6+XnTIzCfCwhIPIg2HLBpw= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/module.modulemap + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/PrivacyInfo.xcprivacy + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeDirectory + + J7HjlTVaKT93TwdH4X74P4uiLdg= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements + + OnX22wWFKRSOFN1+obRynMCeyXM= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements-1 + + S37pzQfQ17G7LI2IjIZ+KSmVd2Q= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeResources + + Ona7czlsmVwS2NMh+zLGOjTKFkY= + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeSignature + + 2jmj7l5rSw0yVb/vlWAYkK/YBwk= + + + files2 + + ios-arm64/AppsFlyerLib.framework/AppsFlyerLib + + hash + + dyd5ME/sFEZuDK+C4I57EQgp3Zs= + + hash2 + + KTM2jmwogPT+EZEzpXOtfy2gwQbgHVnYnCnMLLb4q+I= + + + ios-arm64/AppsFlyerLib.framework/Headers/AFAdRevenueData.h + + hash + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + hash2 + + yrAHizSHbgcNNFLOfLoHWfaZGyJOlAoDsri7G5c7ZiA= + + + ios-arm64/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h + + hash + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + hash2 + + UNK4h738nezlAQq+5weKs6WQUIVzBoBZIFYlfSGbhRA= + + + ios-arm64/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h + + hash + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + hash2 + + 8B0SIW1N+hhdxvGum5eyxLMW5e/T797WSXOd2i+irv4= + + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h + + hash + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + hash2 + + VIYU7EyY+VmJF4vKtTem3hkI/fjGfSK7lYs2zq/9D4E= + + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h + + hash + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + hash2 + + YgrwrWx/ZFYjXh2t5ZHY6S0EZTroYfe5Nprl3alq+Ho= + + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h + + hash + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + hash2 + + Z5nW/ynpNNV+krqJXqy1Gb+gdnruPFWutZYYX7hSt3I= + + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h + + hash + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + hash2 + + QsQGXKix5206DUBBUdDxfg5ykma/3V9MoHOxbz8NaOs= + + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h + + hash + + 26YQ/Pm+Ilp2zDJjuSccfLC+V0A= + + hash2 + + vBqBeBOkrIuJsxW6P5rkVRUVBaPhYZUkYDfplgyqRUU= + + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib.h + + hash + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + hash2 + + SQsJwXrqFChMPzjRNaG0wtOfEV17jrTEH4mn18wQQDY= + + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h + + hash + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + hash2 + + LskctIiGg0ZYNtNTDDkoSuDrB3xS58UmllXbrJDHg48= + + + ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h + + hash + + LysKB9CL90x+MWXOuuRPixmiVJo= + + hash2 + + PPGAG29u7O6ly0pq4HZsZxW1zcqL/viEKKBfZ6qzfdE= + + + ios-arm64/AppsFlyerLib.framework/Info.plist + + hash + + TYoGiVka9ZfpHbgCmmVRfEfbIhY= + + hash2 + + 0VrmJJjIi+KrdavDjGQ9tMhGppq79nc37daB3jmN2LM= + + + ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.private.swiftinterface + + hash + + iMeJNqlBcn4wSWoSYbcc105heA0= + + hash2 + + LpVq2bXLMp4Foo84LvBD8/xk839cgE10syoVPZXKEnI= + + + ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.swiftdoc + + hash + + Gn52dLwhNXom4k91P9HHSlAPFjo= + + hash2 + + W5HW5WcxmujmV82HcB9d7JUN0HlN8a5YVpk8vkq6uGk= + + + ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.swiftinterface + + hash + + iMeJNqlBcn4wSWoSYbcc105heA0= + + hash2 + + LpVq2bXLMp4Foo84LvBD8/xk839cgE10syoVPZXKEnI= + + + ios-arm64/AppsFlyerLib.framework/Modules/module.modulemap + + hash + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + hash2 + + uZJ3EIEDqGw+lkbzR0fNsIrgzthOwNUTrpyMAJQZFc8= + + + ios-arm64/AppsFlyerLib.framework/PrivacyInfo.xcprivacy + + hash + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + hash2 + + 6xKWyp82bfdpIC+qSEbhTjD40SP5BWjupWVxQg5FQkY= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/AppsFlyerLib + + symlink + Versions/Current/AppsFlyerLib + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Headers + + symlink + Versions/Current/Headers + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Modules + + symlink + Versions/Current/Modules + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Resources + + symlink + Versions/Current/Resources + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/AppsFlyerLib + + hash + + YZ8xYTW61zoKso2TqVgeK5dzb9M= + + hash2 + + IsY1y+rlok+MiQPk1FUhOlVoGU5p3p/d/8ruOe2YIfo= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFAdRevenueData.h + + hash + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + hash2 + + yrAHizSHbgcNNFLOfLoHWfaZGyJOlAoDsri7G5c7ZiA= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFSDKPurchaseDetails.h + + hash + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + hash2 + + UNK4h738nezlAQq+5weKs6WQUIVzBoBZIFYlfSGbhRA= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFSDKValidateAndLogResult.h + + hash + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + hash2 + + 8B0SIW1N+hhdxvGum5eyxLMW5e/T797WSXOd2i+irv4= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerConsent.h + + hash + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + hash2 + + VIYU7EyY+VmJF4vKtTem3hkI/fjGfSK7lYs2zq/9D4E= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerCrossPromotionHelper.h + + hash + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + hash2 + + YgrwrWx/ZFYjXh2t5ZHY6S0EZTroYfe5Nprl3alq+Ho= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLink.h + + hash + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + hash2 + + Z5nW/ynpNNV+krqJXqy1Gb+gdnruPFWutZYYX7hSt3I= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLinkResult.h + + hash + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + hash2 + + QsQGXKix5206DUBBUdDxfg5ykma/3V9MoHOxbz8NaOs= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib-Swift.h + + hash + + FOoS78OxL9Z3oz6vB1wWn0PArv0= + + hash2 + + s3bkkJu3kOu0GV4agwv8JVspCFp8hE12f3253m+G6T4= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib.h + + hash + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + hash2 + + SQsJwXrqFChMPzjRNaG0wtOfEV17jrTEH4mn18wQQDY= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLinkGenerator.h + + hash + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + hash2 + + LskctIiGg0ZYNtNTDDkoSuDrB3xS58UmllXbrJDHg48= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerShareInviteHelper.h + + hash + + LysKB9CL90x+MWXOuuRPixmiVJo= + + hash2 + + PPGAG29u7O6ly0pq4HZsZxW1zcqL/viEKKBfZ6qzfdE= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.private.swiftinterface + + hash + + gBZnNBYz2aEYupow29e9wPxTNdQ= + + hash2 + + 6J2Wjby/H7SQ0hv/nRxKSoe578iqOoxn2UQpQz0LeZQ= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.swiftdoc + + hash + + rkRaNKF1Oee1Iby1uIIbSJDTM4o= + + hash2 + + wdDvmP+Z7KjMyabw/lMb2DUOdqbASqIbdU8iVulnqFY= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.swiftinterface + + hash + + gBZnNBYz2aEYupow29e9wPxTNdQ= + + hash2 + + 6J2Wjby/H7SQ0hv/nRxKSoe578iqOoxn2UQpQz0LeZQ= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.private.swiftinterface + + hash + + eTOz6XNmXUiCwNP0jSwlERSPci4= + + hash2 + + TfZ2F9os8tzpjpoq4xuNLb+JO4/6+r3PfajaCvRlALI= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.swiftdoc + + hash + + 75DqHCaiZlbn1Yz0KOftB014IJg= + + hash2 + + wG8JnedcpHCzNrU5oN6nUnQVh3nb5qdoBvexLSfYIKM= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.swiftinterface + + hash + + eTOz6XNmXUiCwNP0jSwlERSPci4= + + hash2 + + TfZ2F9os8tzpjpoq4xuNLb+JO4/6+r3PfajaCvRlALI= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/module.modulemap + + hash + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + hash2 + + uZJ3EIEDqGw+lkbzR0fNsIrgzthOwNUTrpyMAJQZFc8= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Resources/Info.plist + + hash + + +kpkTPBuaY/TwXE7c8BxCa+Wohs= + + hash2 + + CS1fcKxn0j0vU/MWGe3rNdqwxJekXDoTf1jtycLmbUY= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Resources/PrivacyInfo.xcprivacy + + hash + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + hash2 + + 6xKWyp82bfdpIC+qSEbhTjD40SP5BWjupWVxQg5FQkY= + + + ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/Current + + symlink + A + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/AppsFlyerLib + + hash + + B3dCzimbYur8R/rxKmJegQzz9AA= + + hash2 + + Tx8ZhTRj47jTLACfwJOmmQsLjTTokakFDfbsaKLExzI= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFAdRevenueData.h + + hash + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + hash2 + + yrAHizSHbgcNNFLOfLoHWfaZGyJOlAoDsri7G5c7ZiA= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h + + hash + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + hash2 + + UNK4h738nezlAQq+5weKs6WQUIVzBoBZIFYlfSGbhRA= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h + + hash + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + hash2 + + 8B0SIW1N+hhdxvGum5eyxLMW5e/T797WSXOd2i+irv4= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h + + hash + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + hash2 + + VIYU7EyY+VmJF4vKtTem3hkI/fjGfSK7lYs2zq/9D4E= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h + + hash + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + hash2 + + YgrwrWx/ZFYjXh2t5ZHY6S0EZTroYfe5Nprl3alq+Ho= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h + + hash + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + hash2 + + Z5nW/ynpNNV+krqJXqy1Gb+gdnruPFWutZYYX7hSt3I= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h + + hash + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + hash2 + + QsQGXKix5206DUBBUdDxfg5ykma/3V9MoHOxbz8NaOs= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h + + hash + + FOoS78OxL9Z3oz6vB1wWn0PArv0= + + hash2 + + s3bkkJu3kOu0GV4agwv8JVspCFp8hE12f3253m+G6T4= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib.h + + hash + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + hash2 + + SQsJwXrqFChMPzjRNaG0wtOfEV17jrTEH4mn18wQQDY= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h + + hash + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + hash2 + + LskctIiGg0ZYNtNTDDkoSuDrB3xS58UmllXbrJDHg48= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h + + hash + + LysKB9CL90x+MWXOuuRPixmiVJo= + + hash2 + + PPGAG29u7O6ly0pq4HZsZxW1zcqL/viEKKBfZ6qzfdE= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Info.plist + + hash + + R1HkFJpIbi3chDScxpiSYpL4nMw= + + hash2 + + ZUbvrxXGYhyvVeemCE49/CxyJZUqtqbt+OgfK5pSqhs= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface + + hash + + i1iM4UdBHXU2lmDnJ/Czh5pfamI= + + hash2 + + 4MD9uoatCkuOKH9AlR6foTTNYj+7TNc6quxamFMRTSo= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + hash + + ex2JxlYuxQFqky3dqJcNjo7d+qo= + + hash2 + + Gz+v10EwWzXwNQ+WpHCUSx6bcILFZ323RxNGAcPJ2cU= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftinterface + + hash + + i1iM4UdBHXU2lmDnJ/Czh5pfamI= + + hash2 + + 4MD9uoatCkuOKH9AlR6foTTNYj+7TNc6quxamFMRTSo= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface + + hash + + wn2IXmAG5n5kmUW9IvrqLMpExQs= + + hash2 + + i7WPdwrs5dF/nuOzDt6Sb0RI/sYkAfzjItW5R21605M= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + hash + + Jof2V2SbxYOsGiZHkRxK/hYz/Co= + + hash2 + + j2GPaSZglQqvyzdUdahvuxDe8T3fLkm8aLul3md381s= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftinterface + + hash + + wn2IXmAG5n5kmUW9IvrqLMpExQs= + + hash2 + + i7WPdwrs5dF/nuOzDt6Sb0RI/sYkAfzjItW5R21605M= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/module.modulemap + + hash + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + hash2 + + uZJ3EIEDqGw+lkbzR0fNsIrgzthOwNUTrpyMAJQZFc8= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/PrivacyInfo.xcprivacy + + hash + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + hash2 + + 6xKWyp82bfdpIC+qSEbhTjD40SP5BWjupWVxQg5FQkY= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeDirectory + + hash + + HuLJpaYQiGbZjE1b+QhKTHrAx5M= + + hash2 + + /lpZk6KPyekAOSVj/Q9iEQk5lcKeYdrM6ZUyhJU90ss= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements + + hash + + OnX22wWFKRSOFN1+obRynMCeyXM= + + hash2 + + mHkgkE6rZQ51eIwFSqCwUk5qgL/HGqMt+NI3phdD+YY= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements-1 + + hash + + y3CdoXblRLRuwf16oETs3yJJkiY= + + hash2 + + 3BEUMmHSu5I0grA2vGCQOBuW4G6dHmQyXskAwvrOWTs= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeResources + + hash + + 46WmbKN58EkkSmW8MDGDbBBSUkg= + + hash2 + + KMVq1JenHSmoeZk/m4et3sqHJTZZgvy3g/zufyrIFCE= + + + ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeSignature + + hash + + 2jmj7l5rSw0yVb/vlWAYkK/YBwk= + + hash2 + + 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= + + + macos-arm64_x86_64/AppsFlyerLib.framework/AppsFlyerLib + + symlink + Versions/Current/AppsFlyerLib + + macos-arm64_x86_64/AppsFlyerLib.framework/Headers + + symlink + Versions/Current/Headers + + macos-arm64_x86_64/AppsFlyerLib.framework/Modules + + symlink + Versions/Current/Modules + + macos-arm64_x86_64/AppsFlyerLib.framework/Resources + + symlink + Versions/Current/Resources + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/AppsFlyerLib + + hash + + zGKQq4IIRn5iEi66SD02YnUGh8I= + + hash2 + + UYn3CjN1hIX+TuO0VGFv+yQgux4JK4PlHFP4TsVv+FA= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFAdRevenueData.h + + hash + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + hash2 + + yrAHizSHbgcNNFLOfLoHWfaZGyJOlAoDsri7G5c7ZiA= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFSDKPurchaseDetails.h + + hash + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + hash2 + + UNK4h738nezlAQq+5weKs6WQUIVzBoBZIFYlfSGbhRA= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFSDKValidateAndLogResult.h + + hash + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + hash2 + + 8B0SIW1N+hhdxvGum5eyxLMW5e/T797WSXOd2i+irv4= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerConsent.h + + hash + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + hash2 + + VIYU7EyY+VmJF4vKtTem3hkI/fjGfSK7lYs2zq/9D4E= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerCrossPromotionHelper.h + + hash + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + hash2 + + YgrwrWx/ZFYjXh2t5ZHY6S0EZTroYfe5Nprl3alq+Ho= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLink.h + + hash + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + hash2 + + Z5nW/ynpNNV+krqJXqy1Gb+gdnruPFWutZYYX7hSt3I= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLinkResult.h + + hash + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + hash2 + + QsQGXKix5206DUBBUdDxfg5ykma/3V9MoHOxbz8NaOs= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib-Swift.h + + hash + + FOoS78OxL9Z3oz6vB1wWn0PArv0= + + hash2 + + s3bkkJu3kOu0GV4agwv8JVspCFp8hE12f3253m+G6T4= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib.h + + hash + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + hash2 + + SQsJwXrqFChMPzjRNaG0wtOfEV17jrTEH4mn18wQQDY= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLinkGenerator.h + + hash + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + hash2 + + LskctIiGg0ZYNtNTDDkoSuDrB3xS58UmllXbrJDHg48= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerShareInviteHelper.h + + hash + + LysKB9CL90x+MWXOuuRPixmiVJo= + + hash2 + + PPGAG29u7O6ly0pq4HZsZxW1zcqL/viEKKBfZ6qzfdE= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.private.swiftinterface + + hash + + jsvLKdoRWGO+HkCcMBqlXVPyv0U= + + hash2 + + SDJmtrIjfb/hx3biCk1151Oui4B+ggrUWnqpq/gMfIo= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.swiftdoc + + hash + + 415kRT2U4WlV4dTTPlcvqyA7w24= + + hash2 + + U779Ot+/UVPm5Eg1zT5/HQxJev4psWVbu4AIrQzkKvc= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.swiftinterface + + hash + + jsvLKdoRWGO+HkCcMBqlXVPyv0U= + + hash2 + + SDJmtrIjfb/hx3biCk1151Oui4B+ggrUWnqpq/gMfIo= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.private.swiftinterface + + hash + + iYpvc2pf7F3wWJ7DDv4zVU8ppEw= + + hash2 + + AdLIvgmNPFuSo3Ygr+Bm1CtuswlaL6TTLMVTT6pYysY= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.swiftdoc + + hash + + i/MkainfYqjckQ1Uy9RGZGO8T3g= + + hash2 + + Ku0XG0U1vI67zR/XSQcagX7wVzRP4S1lMn1G6VwPtAM= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.swiftinterface + + hash + + iYpvc2pf7F3wWJ7DDv4zVU8ppEw= + + hash2 + + AdLIvgmNPFuSo3Ygr+Bm1CtuswlaL6TTLMVTT6pYysY= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/module.modulemap + + hash + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + hash2 + + uZJ3EIEDqGw+lkbzR0fNsIrgzthOwNUTrpyMAJQZFc8= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Resources/Info.plist + + hash + + zePZQixRSD8kIhrDumpD+f4nxek= + + hash2 + + T8GbNVhIArCaJjiPybq+tPNeu0Ns42Iw0Pto/WVgBDM= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Resources/PrivacyInfo.xcprivacy + + hash + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + hash2 + + 6xKWyp82bfdpIC+qSEbhTjD40SP5BWjupWVxQg5FQkY= + + + macos-arm64_x86_64/AppsFlyerLib.framework/Versions/Current + + symlink + A + + tvos-arm64/AppsFlyerLib.framework/AppsFlyerLib + + hash + + 5tdxfvaydlr5DkrSg35u9eZNUk4= + + hash2 + + ScHDMgzWUlCtMCJ8V8kKHjq4sNyTI9mjxkjx4fRLZ3Y= + + + tvos-arm64/AppsFlyerLib.framework/Headers/AFAdRevenueData.h + + hash + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + hash2 + + yrAHizSHbgcNNFLOfLoHWfaZGyJOlAoDsri7G5c7ZiA= + + + tvos-arm64/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h + + hash + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + hash2 + + UNK4h738nezlAQq+5weKs6WQUIVzBoBZIFYlfSGbhRA= + + + tvos-arm64/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h + + hash + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + hash2 + + 8B0SIW1N+hhdxvGum5eyxLMW5e/T797WSXOd2i+irv4= + + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h + + hash + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + hash2 + + VIYU7EyY+VmJF4vKtTem3hkI/fjGfSK7lYs2zq/9D4E= + + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h + + hash + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + hash2 + + YgrwrWx/ZFYjXh2t5ZHY6S0EZTroYfe5Nprl3alq+Ho= + + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h + + hash + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + hash2 + + Z5nW/ynpNNV+krqJXqy1Gb+gdnruPFWutZYYX7hSt3I= + + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h + + hash + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + hash2 + + QsQGXKix5206DUBBUdDxfg5ykma/3V9MoHOxbz8NaOs= + + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h + + hash + + 26YQ/Pm+Ilp2zDJjuSccfLC+V0A= + + hash2 + + vBqBeBOkrIuJsxW6P5rkVRUVBaPhYZUkYDfplgyqRUU= + + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib.h + + hash + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + hash2 + + SQsJwXrqFChMPzjRNaG0wtOfEV17jrTEH4mn18wQQDY= + + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h + + hash + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + hash2 + + LskctIiGg0ZYNtNTDDkoSuDrB3xS58UmllXbrJDHg48= + + + tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h + + hash + + LysKB9CL90x+MWXOuuRPixmiVJo= + + hash2 + + PPGAG29u7O6ly0pq4HZsZxW1zcqL/viEKKBfZ6qzfdE= + + + tvos-arm64/AppsFlyerLib.framework/Info.plist + + hash + + 8Fwk7KiO/1Qp2LIovflf6lYJyaA= + + hash2 + + 3tiNcYHiO/gvHvutoVnvGFYFge+3phKzY+rGtSRJb+k= + + + tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.private.swiftinterface + + hash + + f+rVaiYkUFCA0df61t6MDqxIL1Q= + + hash2 + + 5jvCiIsv0a7uVvG2fB+EoHGMx9TnVbw7HgprMxUxMnI= + + + tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.swiftdoc + + hash + + AXvakhu1gsIKafoHXqzDDJqtg24= + + hash2 + + F66PF58duFUyjLow7Wx0UiiVZpplWXx9IIV8F5J5FYo= + + + tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.swiftinterface + + hash + + f+rVaiYkUFCA0df61t6MDqxIL1Q= + + hash2 + + 5jvCiIsv0a7uVvG2fB+EoHGMx9TnVbw7HgprMxUxMnI= + + + tvos-arm64/AppsFlyerLib.framework/Modules/module.modulemap + + hash + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + hash2 + + uZJ3EIEDqGw+lkbzR0fNsIrgzthOwNUTrpyMAJQZFc8= + + + tvos-arm64/AppsFlyerLib.framework/PrivacyInfo.xcprivacy + + hash + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + hash2 + + 6xKWyp82bfdpIC+qSEbhTjD40SP5BWjupWVxQg5FQkY= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/AppsFlyerLib + + hash + + T4wGOjfddTsOJgEeQEeOG24QHKs= + + hash2 + + VQSJRLtSjRj7S2sTluEw8P/1KwAgQkxyXc9adZcFLro= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFAdRevenueData.h + + hash + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + hash2 + + yrAHizSHbgcNNFLOfLoHWfaZGyJOlAoDsri7G5c7ZiA= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h + + hash + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + hash2 + + UNK4h738nezlAQq+5weKs6WQUIVzBoBZIFYlfSGbhRA= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h + + hash + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + hash2 + + 8B0SIW1N+hhdxvGum5eyxLMW5e/T797WSXOd2i+irv4= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h + + hash + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + hash2 + + VIYU7EyY+VmJF4vKtTem3hkI/fjGfSK7lYs2zq/9D4E= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h + + hash + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + hash2 + + YgrwrWx/ZFYjXh2t5ZHY6S0EZTroYfe5Nprl3alq+Ho= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h + + hash + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + hash2 + + Z5nW/ynpNNV+krqJXqy1Gb+gdnruPFWutZYYX7hSt3I= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h + + hash + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + hash2 + + QsQGXKix5206DUBBUdDxfg5ykma/3V9MoHOxbz8NaOs= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h + + hash + + FOoS78OxL9Z3oz6vB1wWn0PArv0= + + hash2 + + s3bkkJu3kOu0GV4agwv8JVspCFp8hE12f3253m+G6T4= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib.h + + hash + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + hash2 + + SQsJwXrqFChMPzjRNaG0wtOfEV17jrTEH4mn18wQQDY= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h + + hash + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + hash2 + + LskctIiGg0ZYNtNTDDkoSuDrB3xS58UmllXbrJDHg48= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h + + hash + + LysKB9CL90x+MWXOuuRPixmiVJo= + + hash2 + + PPGAG29u7O6ly0pq4HZsZxW1zcqL/viEKKBfZ6qzfdE= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Info.plist + + hash + + QBaoiDQ7L0WkBy8Tl2WAWvE7sEA= + + hash2 + + C5gbxiCz7gZX2IZslV+kGkZ1CtXPeL/udyPwgTdahgk= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.private.swiftinterface + + hash + + L79lHVuEXcZQ8b/onXpEgE06X84= + + hash2 + + cOSE0wgXydlxxS9iArMjee5G+repqMF7orSYGd46F18= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftdoc + + hash + + apgpLdpztYHXcPhawO2GqU71RX0= + + hash2 + + GXaXgMVijpVzqRSxGRa8A6GnfMvbWmArSH/utOYMMiQ= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftinterface + + hash + + L79lHVuEXcZQ8b/onXpEgE06X84= + + hash2 + + cOSE0wgXydlxxS9iArMjee5G+repqMF7orSYGd46F18= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.private.swiftinterface + + hash + + NGZEK6+XnTIzCfCwhIPIg2HLBpw= + + hash2 + + 8QNd2ehYb5yhI9CDLrfsKFZlRMrTN0C0PuiBZgvjlAQ= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftdoc + + hash + + a1yMUwktSjfVrmA0kgB0AdoKRiw= + + hash2 + + FuWmWrMhtQE2cz17LvjUKHUHkE940WWqqYxdRcLzemI= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftinterface + + hash + + NGZEK6+XnTIzCfCwhIPIg2HLBpw= + + hash2 + + 8QNd2ehYb5yhI9CDLrfsKFZlRMrTN0C0PuiBZgvjlAQ= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/module.modulemap + + hash + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + hash2 + + uZJ3EIEDqGw+lkbzR0fNsIrgzthOwNUTrpyMAJQZFc8= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/PrivacyInfo.xcprivacy + + hash + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + hash2 + + 6xKWyp82bfdpIC+qSEbhTjD40SP5BWjupWVxQg5FQkY= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeDirectory + + hash + + J7HjlTVaKT93TwdH4X74P4uiLdg= + + hash2 + + I/IL39PIDcGIjoHVBbf5LngLMzj8TSN+Fw3LHWyLcuE= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements + + hash + + OnX22wWFKRSOFN1+obRynMCeyXM= + + hash2 + + mHkgkE6rZQ51eIwFSqCwUk5qgL/HGqMt+NI3phdD+YY= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements-1 + + hash + + S37pzQfQ17G7LI2IjIZ+KSmVd2Q= + + hash2 + + 4QLypEQ9/btXq8AXTUBDL2m+3yEwRm1jcfxW6l6Nf9o= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeResources + + hash + + Ona7czlsmVwS2NMh+zLGOjTKFkY= + + hash2 + + +zgkiAovBqNWK/1OzVi0U0CmksX0xrBhm1D+lXVUDuA= + + + tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeSignature + + hash + + 2jmj7l5rSw0yVb/vlWAYkK/YBwk= + + hash2 + + 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeSignature b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeSignature new file mode 100644 index 000000000..dd8e49ba4 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/_CodeSignature/CodeSignature differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/AppsFlyerLib b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/AppsFlyerLib new file mode 100644 index 000000000..2bc234e29 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/AppsFlyerLib differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AFAdRevenueData.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AFAdRevenueData.h new file mode 100644 index 000000000..2025468a3 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AFAdRevenueData.h @@ -0,0 +1,68 @@ +// +// AFAdRevenueData.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 26/06/2024. +// + +typedef NS_CLOSED_ENUM(NSUInteger, AppsFlyerAdRevenueMediationNetworkType) { + AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob = 1, + AppsFlyerAdRevenueMediationNetworkTypeIronSource = 2, + AppsFlyerAdRevenueMediationNetworkTypeApplovinMax= 3, + AppsFlyerAdRevenueMediationNetworkTypeFyber = 4, + AppsFlyerAdRevenueMediationNetworkTypeAppodeal = 5, + AppsFlyerAdRevenueMediationNetworkTypeAdmost = 6, + AppsFlyerAdRevenueMediationNetworkTypeTopon = 7, + AppsFlyerAdRevenueMediationNetworkTypeTradplus = 8, + AppsFlyerAdRevenueMediationNetworkTypeYandex = 9, + AppsFlyerAdRevenueMediationNetworkTypeChartBoost = 10, + AppsFlyerAdRevenueMediationNetworkTypeUnity = 11, + AppsFlyerAdRevenueMediationNetworkTypeToponPte = 12, + AppsFlyerAdRevenueMediationNetworkTypeCustom = 13, + AppsFlyerAdRevenueMediationNetworkTypeDirectMonetization = 14 +} NS_SWIFT_NAME(MediationNetworkType); + +#define kAppsFlyerAdRevenueMonetizationNetwork @"monetization_network" +#define kAppsFlyerAdRevenueMediationNetwork @"mediation_network" +#define kAppsFlyerAdRevenueEventRevenue @"event_revenue" +#define kAppsFlyerAdRevenueEventRevenueCurrency @"event_revenue_currency" +#define kAppsFlyerAdRevenueCustomParameters @"custom_parameters" +#define kAFADRWrapperTypeGeneric @"adrevenue_sdk" + +//Pre-defined keys for non-mandatory dictionary + +//Code ISO 3166-1 format +#define kAppsFlyerAdRevenueCountry @"country" + +//ID of the ad unit for the impression +#define kAppsFlyerAdRevenueAdUnit @"ad_unit" + +//Format of the ad +#define kAppsFlyerAdRevenueAdType @"ad_type" + +//ID of the ad placement for the impression +#define kAppsFlyerAdRevenuePlacement @"placement" + + +@interface AFAdRevenueData : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (strong, nonnull, nonatomic) NSString *monetizationNetwork; +@property AppsFlyerAdRevenueMediationNetworkType mediationNetwork; +@property (strong, nonnull, nonatomic) NSString *currencyIso4217Code; +@property (strong, nonnull, nonatomic) NSNumber *eventRevenue; + +/** +* @param monetizationNetwork network which monetized the impression (@"facebook") +* @param mediationNetwork mediation source that mediated the monetization network for the impression (AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob) +* @param currencyIso4217Code reported impression’s revenue currency ISO 4217 format (@"USD") +* @param eventRevenue reported impression’s revenue (@(0.001994303)) +*/ +- (instancetype _Nonnull )initWithMonetizationNetwork:(NSString *_Nonnull)monetizationNetwork + mediationNetwork:(AppsFlyerAdRevenueMediationNetworkType)mediationNetwork + currencyIso4217Code:(NSString *_Nonnull)currencyIso4217Code + eventRevenue:(NSNumber *_Nonnull)eventRevenue; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h new file mode 100644 index 000000000..89af8a702 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h @@ -0,0 +1,23 @@ +// +// AFSDKPurchaseDetails.h +// AppsFlyerLib +// +// Created by Moris Gateno on 13/03/2024. +// + +@interface AFSDKPurchaseDetails : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (strong, nullable, nonatomic) NSString *productId; +@property (strong, nullable, nonatomic) NSString *price; +@property (strong, nullable, nonatomic) NSString *currency; +@property (strong, nullable, nonatomic) NSString *transactionId; + +- (instancetype _Nonnull )initWithProductId:(NSString *_Nullable)productId + price:(NSString *_Nullable)price + currency:(NSString *_Nullable)currency + transactionId:(NSString *_Nullable)transactionId; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h new file mode 100644 index 000000000..94ef0f4c2 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h @@ -0,0 +1,35 @@ +// +// AFSDKValidateAndLogResult.h +// AppsFlyerLib +// +// Created by Moris Gateno on 13/03/2024. +// + + +typedef NS_CLOSED_ENUM(NSUInteger, AFSDKValidateAndLogStatus) { + AFSDKValidateAndLogStatusSuccess, + AFSDKValidateAndLogStatusFailure, + AFSDKValidateAndLogStatusError +} NS_SWIFT_NAME(ValidateAndLogStatus); + +NS_SWIFT_NAME(ValidateAndLogResult) +@interface AFSDKValidateAndLogResult : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +- (instancetype _Nonnull )initWithStatus:(AFSDKValidateAndLogStatus)status + result:(NSDictionary *_Nullable)result + errorData:(NSDictionary *_Nullable)errorData + error:(NSError *_Nullable)error; + +@property(readonly) AFSDKValidateAndLogStatus status; +// Success case +@property(readonly, nullable) NSDictionary *result; +// Server 200 with validation failure +@property(readonly, nullable) NSDictionary *errorData; +// for the error case +@property(readonly, nullable) NSError *error; + +@end + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h new file mode 100644 index 000000000..564912a92 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h @@ -0,0 +1,26 @@ +// +// AppsFlyerConsent.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 14/01/2024. +// +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AppsFlyerConsent : NSObject + +@property (nonatomic, readonly, assign) BOOL isUserSubjectToGDPR; +@property (nonatomic, readonly, assign) BOOL hasConsentForDataUsage; +@property (nonatomic, readonly, assign) BOOL hasConsentForAdsPersonalization; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (instancetype)initForGDPRUserWithHasConsentForDataUsage:(BOOL)hasConsentForDataUsage + hasConsentForAdsPersonalization:(BOOL)hasConsentForAdsPersonalization NS_DESIGNATED_INITIALIZER; +- (instancetype)initNonGDPRUser NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h new file mode 100644 index 000000000..483f8f863 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h @@ -0,0 +1,49 @@ +// +// CrossPromotionHelper.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + AppsFlyer allows you to log and attribute installs originating + from cross promotion campaigns of your existing apps. + Afterwards, you can optimize on your cross-promotion traffic to get even better results. + */ +@interface AppsFlyerCrossPromotionHelper : NSObject + +/** + To log an impression use the following API call. + Make sure to use the promoted App ID as it appears within the AppsFlyer dashboard. + + @param appID Promoted App ID + @param campaign A campaign name + @param parameters Additional params like `@{@"af_sub1": @"val", @"custom_param": @"val2" }` +*/ ++ (void)logCrossPromoteImpression:(nonnull NSString *)appID + campaign:(nullable NSString *)campaign + parameters:(nullable NSDictionary *)parameters; + +/** + iOS allows you to utilize the StoreKit component to open + the App Store while remaining in the context of your app. + More details at https://support.appsflyer.com/hc/en-us/articles/115004481946-Cross-Promotion-Tracking#tracking-cross-promotion-impressions + + @param appID Promoted App ID + @param campaign A campaign name + @param parameters Additional params like `@{@"af_sub1": @"val", @"custom_param": @"val2" }` + @param openStoreBlock Contains promoted `clickURL` + */ ++ (void)logAndOpenStore:(nonnull NSString *)appID + campaign:(nullable NSString *)campaign + parameters:(nullable NSDictionary *)parameters + openStore:(void (^)(NSURLSession *urlSession, NSURL *clickURL))openStoreBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h new file mode 100644 index 000000000..f099aceb9 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h @@ -0,0 +1,36 @@ +// +// AFSDKDeeplink.h +// AppsFlyerLib +// +// Created by Andrii Hahan on 20.08.2020. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(DeepLink) +@interface AppsFlyerDeepLink : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (readonly, nonnull) NSDictionary *clickEvent; +@property (readonly, nullable) NSString *deeplinkValue; +@property (readonly, nullable) NSString *matchType; +@property (readonly, nullable) NSString *clickHTTPReferrer; +@property (readonly, nullable) NSString *mediaSource; +@property (readonly, nullable) NSString *campaign; +@property (readonly, nullable) NSString *campaignId; +@property (readonly, nullable) NSString *afSub1; +@property (readonly, nullable) NSString *afSub2; +@property (readonly, nullable) NSString *afSub3; +@property (readonly, nullable) NSString *afSub4; +@property (readonly, nullable) NSString *afSub5; +@property (readonly) BOOL isDeferred; + +- (NSString *)toString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h new file mode 100644 index 000000000..50d41d71e --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h @@ -0,0 +1,29 @@ +// +// AFSDKDeeplinkResult.h +// AppsFlyerLib +// +// Created by Andrii Hahan on 20.08.2020. +// + +#import + +@class AppsFlyerDeepLink; + +typedef NS_CLOSED_ENUM(NSUInteger, AFSDKDeepLinkResultStatus) { + AFSDKDeepLinkResultStatusNotFound, + AFSDKDeepLinkResultStatusFound, + AFSDKDeepLinkResultStatusFailure, +} NS_SWIFT_NAME(DeepLinkResultStatus); + +NS_SWIFT_NAME(DeepLinkResult) +@interface AppsFlyerDeepLinkResult : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property(readonly) AFSDKDeepLinkResultStatus status; + +@property(readonly, nullable) AppsFlyerDeepLink *deepLink; +@property(readonly, nullable) NSError *error; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h new file mode 100644 index 000000000..1f1b87e0b --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h @@ -0,0 +1,311 @@ +#if 0 +#elif defined(__arm64__) && __arm64__ +// Generated by Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +#ifndef APPSFLYERLIB_SWIFT_H +#define APPSFLYERLIB_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="AppsFlyerLib",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#else +#error unsupported Swift architecture +#endif diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib.h new file mode 100644 index 000000000..182fe6e91 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib.h @@ -0,0 +1,743 @@ +// +// AppsFlyerLib.h +// AppsFlyerLib +// +// AppsFlyer iOS SDK 6.15.1 (211) +// Copyright (c) 2012-2023 AppsFlyer Ltd. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +// In app event names constants +#define AFEventLevelAchieved @"af_level_achieved" +#define AFEventAddPaymentInfo @"af_add_payment_info" +#define AFEventAddToCart @"af_add_to_cart" +#define AFEventAddToWishlist @"af_add_to_wishlist" +#define AFEventCompleteRegistration @"af_complete_registration" +#define AFEventTutorial_completion @"af_tutorial_completion" +#define AFEventInitiatedCheckout @"af_initiated_checkout" +#define AFEventPurchase @"af_purchase" +#define AFEventRate @"af_rate" +#define AFEventSearch @"af_search" +#define AFEventSpentCredits @"af_spent_credits" +#define AFEventAchievementUnlocked @"af_achievement_unlocked" +#define AFEventContentView @"af_content_view" +#define AFEventListView @"af_list_view" +#define AFEventTravelBooking @"af_travel_booking" +#define AFEventShare @"af_share" +#define AFEventInvite @"af_invite" +#define AFEventLogin @"af_login" +#define AFEventReEngage @"af_re_engage" +#define AFEventUpdate @"af_update" +#define AFEventOpenedFromPushNotification @"af_opened_from_push_notification" +#define AFEventLocation @"af_location_coordinates" +#define AFEventCustomerSegment @"af_customer_segment" + +#define AFEventSubscribe @"af_subscribe" +#define AFEventStartTrial @"af_start_trial" +#define AFEventAdClick @"af_ad_click" +#define AFEventAdView @"af_ad_view" + +// In app event parameter names +#define AFEventParamContent @"af_content" +#define AFEventParamAchievementId @"af_achievement_id" +#define AFEventParamLevel @"af_level" +#define AFEventParamScore @"af_score" +#define AFEventParamSuccess @"af_success" +#define AFEventParamPrice @"af_price" +#define AFEventParamContentType @"af_content_type" +#define AFEventParamContentId @"af_content_id" +#define AFEventParamContentList @"af_content_list" +#define AFEventParamCurrency @"af_currency" +#define AFEventParamQuantity @"af_quantity" +#define AFEventParamRegistrationMethod @"af_registration_method" +#define AFEventParamPaymentInfoAvailable @"af_payment_info_available" +#define AFEventParamMaxRatingValue @"af_max_rating_value" +#define AFEventParamRatingValue @"af_rating_value" +#define AFEventParamSearchString @"af_search_string" +#define AFEventParamDateA @"af_date_a" +#define AFEventParamDateB @"af_date_b" +#define AFEventParamDestinationA @"af_destination_a" +#define AFEventParamDestinationB @"af_destination_b" +#define AFEventParamDescription @"af_description" +#define AFEventParamClass @"af_class" +#define AFEventParamEventStart @"af_event_start" +#define AFEventParamEventEnd @"af_event_end" +#define AFEventParamLat @"af_lat" +#define AFEventParamLong @"af_long" +#define AFEventParamCustomerUserId @"af_customer_user_id" +#define AFEventParamValidated @"af_validated" +#define AFEventParamRevenue @"af_revenue" +#define AFEventProjectedParamRevenue @"af_projected_revenue" +#define AFEventParamReceiptId @"af_receipt_id" +#define AFEventParamTutorialId @"af_tutorial_id" +#define AFEventParamVirtualCurrencyName @"af_virtual_currency_name" +#define AFEventParamDeepLink @"af_deep_link" +#define AFEventParamOldVersion @"af_old_version" +#define AFEventParamNewVersion @"af_new_version" +#define AFEventParamReviewText @"af_review_text" +#define AFEventParamCouponCode @"af_coupon_code" +#define AFEventParamOrderId @"af_order_id" +#define AFEventParam1 @"af_param_1" +#define AFEventParam2 @"af_param_2" +#define AFEventParam3 @"af_param_3" +#define AFEventParam4 @"af_param_4" +#define AFEventParam5 @"af_param_5" +#define AFEventParam6 @"af_param_6" +#define AFEventParam7 @"af_param_7" +#define AFEventParam8 @"af_param_8" +#define AFEventParam9 @"af_param_9" +#define AFEventParam10 @"af_param_10" +#define AFEventParamTouch @"af_touch_obj" + +#define AFEventParamDepartingDepartureDate @"af_departing_departure_date" +#define AFEventParamReturningDepartureDate @"af_returning_departure_date" +#define AFEventParamDestinationList @"af_destination_list" //array of string +#define AFEventParamCity @"af_city" +#define AFEventParamRegion @"af_region" +#define AFEventParamCountry @"af_country" + + +#define AFEventParamDepartingArrivalDate @"af_departing_arrival_date" +#define AFEventParamReturningArrivalDate @"af_returning_arrival_date" +#define AFEventParamSuggestedDestinations @"af_suggested_destinations" //array of string +#define AFEventParamTravelStart @"af_travel_start" +#define AFEventParamTravelEnd @"af_travel_end" +#define AFEventParamNumAdults @"af_num_adults" +#define AFEventParamNumChildren @"af_num_children" +#define AFEventParamNumInfants @"af_num_infants" +#define AFEventParamSuggestedHotels @"af_suggested_hotels" //array of string + +#define AFEventParamUserScore @"af_user_score" +#define AFEventParamHotelScore @"af_hotel_score" +#define AFEventParamPurchaseCurrency @"af_purchase_currency" + +#define AFEventParamPreferredStarRatings @"af_preferred_star_ratings" //array of int (basically a tuple (min,max) but we'll use array of int and instruct the developer to use two values) + +#define AFEventParamPreferredPriceRange @"af_preferred_price_range" //array of int (basically a tuple (min,max) but we'll use array of int and instruct the developer to use two values) +#define AFEventParamPreferredNeighborhoods @"af_preferred_neighborhoods" //array of string +#define AFEventParamPreferredNumStops @"af_preferred_num_stops" + +/// Mail hashing type +typedef enum { + /// None + EmailCryptTypeNone = 0, + /// SHA256 + EmailCryptTypeSHA256 = 3 +} EmailCryptType; + +typedef NS_CLOSED_ENUM(NSInteger, AFSDKPlugin) { + AFSDKPluginIOSNative, + AFSDKPluginUnity, + AFSDKPluginFlutter, + AFSDKPluginReactNative, + AFSDKPluginAdobeAir, + AFSDKPluginAdobeMobile, + AFSDKPluginCocos2dx, + AFSDKPluginCordova, + AFSDKPluginMparticle, + AFSDKPluginNativeScript, + AFSDKPluginExpo, + AFSDKPluginUnreal, + AFSDKPluginXamarin, + AFSDKPluginCapacitor, + AFSDKPluginSegment, + AFSDKPluginAdobeSwiftAEP +} NS_SWIFT_NAME(Plugin); + + +NS_SWIFT_NAME(DeepLinkDelegate) +@protocol AppsFlyerDeepLinkDelegate + +@optional +- (void)didResolveDeepLink:(AppsFlyerDeepLinkResult *_Nonnull)result; + +@end + +/** + Conform and subscribe to this protocol to allow getting data about conversion and + install attribution + */ +@protocol AppsFlyerLibDelegate + +/** + `conversionInfo` contains information about install. + Organic/non-organic, etc. + @param conversionInfo May contain null values for some keys. Please handle this case. + */ +- (void)onConversionDataSuccess:(NSDictionary *)conversionInfo; + +/** + Any errors that occurred during the conversion request. + */ +- (void)onConversionDataFail:(NSError *)error; + +@optional + +/** + `attributionData` contains information about OneLink, deeplink. + */ +- (void)onAppOpenAttribution:(NSDictionary *)attributionData; + +/** + Any errors that occurred during the attribution request. + */ +- (void)onAppOpenAttributionFailure:(NSError *)error; + +/** + @abstract Sets the HTTP header fields of the ESP resolving to the given + dictionary. + @discussion This method replaces all header fields that may have + existed before this method ESP resolving call. + To keep default SDK behavior - return nil; + */ +- (NSDictionary * _Nullable)allHTTPHeaderFieldsForResolveDeepLinkURL:(NSURL *)URL; + +@end + +/** + You can log installs, app updates, sessions and additional in-app events + (including in-app purchases, game levels, etc.) + to evaluate ROI and user engagement. + The iOS SDK is compatible with all iOS/tvOS devices with iOS version 7 and above. + + @see [SDK Integration Validator](https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS) + for more information. + + */ +@interface AppsFlyerLib : NSObject + +/** + Gets the singleton instance of the AppsFlyerLib class, creating it if + necessary. + + @return The singleton instance of AppsFlyerLib. + */ ++ (AppsFlyerLib *)shared; + + +- (void)setUpInteroperabilityObject:(id)object; + +/** + In case you use your own user ID in your app, you can set this property to that ID. + Enables you to cross-reference your own unique ID with AppsFlyer’s unique ID and the other devices’ IDs + */ +@property(nonatomic, strong, nullable) NSString * customerUserID; + +/** + In case you use custom data and you want to receive it in the raw reports. + + @see [Setting additional custom data](https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS#setting-additional-custom-data) for more information. + */ +@property(nonatomic, strong, nullable, setter = setAdditionalData:) NSDictionary * customData; + +/** + Use this property to set your AppsFlyer's dev key + */ +@property(nonatomic, strong) NSString * appsFlyerDevKey; + +/** + Use this property to set your app's Apple ID(taken from the app's page on iTunes Connect) + */ +@property(nonatomic, strong) NSString * appleAppID; + +#ifndef AFSDK_NO_IDFA +/** + AppsFlyer SDK collect Apple's `advertisingIdentifier` if the `AdSupport.framework` included in the SDK. + You can disable this behavior by setting the following property to YES +*/ +@property(nonatomic) BOOL disableAdvertisingIdentifier; + +@property(nonatomic, strong, readonly) NSString *advertisingIdentifier; + +/** + Waits for request user authorization to access app-related data + */ +- (void)waitForATTUserAuthorizationWithTimeoutInterval:(NSTimeInterval)timeoutInterval +NS_SWIFT_NAME(waitForATTUserAuthorization(timeoutInterval:)); + +#endif + +@property(nonatomic) BOOL disableSKAdNetwork; + +/** + In case of in app purchase events, you can set the currency code your user has purchased with. + The currency code is a 3 letter code according to ISO standards + + Objective-C: + +
+ [[AppsFlyerLib shared] setCurrencyCode:@"USD"];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().currencyCode = "USD"
+ 
+ */ +@property(nonatomic, strong, nullable) NSString *currencyCode; + +/** + Prints SDK messages to the console log. This property should only be used in `DEBUG` mode. + The default value is `NO` + */ +@property(nonatomic) BOOL isDebug; + +/** + Set this flag to `YES`, to collect the current device name(e.g. "My iPhone"). Default value is `NO` + */ +@property(nonatomic) BOOL shouldCollectDeviceName; + +/** + Set your `OneLink ID` from OneLink configuration. Used in User Invites to generate a OneLink. + */ +@property(nonatomic, strong, nullable, setter = setAppInviteOneLink:) NSString * appInviteOneLinkID; + +/** + Opt-out logging for specific user + */ +@property(atomic) BOOL anonymizeUser; + +/** + Opt-out for Apple Search Ads attributions + */ +@property(atomic) BOOL disableCollectASA; + +/** + Disable Apple Ads Attribution API +[AAAtribution attributionTokenWithError:] + */ +@property(nonatomic) BOOL disableAppleAdsAttribution; + +/** + AppsFlyer delegate. See `AppsFlyerLibDelegate` + */ +@property(weak, nonatomic) id delegate; + +@property(weak, nonatomic) id deepLinkDelegate; + +/** + In app purchase receipt validation Apple environment(production or sandbox). The default value is NO + */ +@property(nonatomic) BOOL useReceiptValidationSandbox; + +/** + Set this flag to test uninstall on Apple environment(production or sandbox). The default value is NO + */ +@property(nonatomic) BOOL useUninstallSandbox; + +/** + For advertisers who wrap OneLink within another Universal Link. + An advertiser will be able to deeplink from a OneLink wrapped within another Universal Link and also log this retargeting conversion. + + Objective-C: + +
+ [[AppsFlyerLib shared] setResolveDeepLinkURLs:@[@"domain.com", @"subdomain.domain.com"]];
+ 
+ */ +@property(nonatomic, nullable, copy) NSArray *resolveDeepLinkURLs; + +/** + For advertisers who use vanity OneLinks. + + Objective-C: + +
+ [[AppsFlyerLib shared] oneLinkCustomDomains:@[@"domain.com", @"subdomain.domain.com"]];
+ 
+ */ +@property(nonatomic, nullable, copy) NSArray *oneLinkCustomDomains; + +/* + * Set phone number for each `start` event. `phoneNumber` will be sent as SHA256 string + */ +@property(nonatomic, nullable, copy) NSString *phoneNumber; + +- (NSString *)phoneNumber UNAVAILABLE_ATTRIBUTE; + +/** + To disable app's vendor identifier(IDFV), set disableIDFVCollection to true + */ +@property(nonatomic) BOOL disableIDFVCollection; + +/** + Set the language of the device. The data will be displayed in Raw Data Reports + Objective-C: + +
+ [[AppsFlyerLib shared] setCurrentDeviceLanguage:@"EN"]
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().currentDeviceLanguage("EN")
+ 
+ */ +@property(nonatomic, nullable, copy) NSString *currentDeviceLanguage; + +/** + Internal API. Please don't use. + */ +- (void)setPluginInfoWith:(AFSDKPlugin)plugin + pluginVersion:(NSString *)version + additionalParams:(NSDictionary * _Nullable)additionalParams +NS_SWIFT_NAME(setPluginInfo(plugin:version:additionalParams:)); + +/** + Enable the collection of Facebook Deferred AppLinks + Requires Facebook SDK and Facebook app on target/client device. + This API must be invoked prior to initializing the AppsFlyer SDK in order to function properly. + + Objective-C: + +
+ [[AppsFlyerLib shared] enableFacebookDeferredApplinksWithClass:[FBSDKAppLinkUtility class]]
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().enableFacebookDeferredApplinks(with: FBSDKAppLinkUtility.self)
+ 
+ + @param facebookAppLinkUtilityClass requeries method call `[FBSDKAppLinkUtility class]` as param. + */ +- (void)enableFacebookDeferredApplinksWithClass:(Class _Nullable)facebookAppLinkUtilityClass; + +/** + Use this to send the user's emails + + @param userEmails The list of strings that hold mails + @param type Hash algoritm + */ +- (void)setUserEmails:(NSArray * _Nullable)userEmails withCryptType:(EmailCryptType)type; + +/** + Start SDK session + Add the following method at the `applicationDidBecomeActive` in AppDelegate class + */ +- (void)start; + +- (void)startWithCompletionHandler:(void (^ _Nullable)(NSDictionary * _Nullable dictionary, NSError * _Nullable error))completionHandler; + +/** + Use this method to log an events with multiple values. See AppsFlyer's documentation for details. + + Objective-C: + +
+ [[AppsFlyerLib shared] logEvent:AFEventPurchase
+        withValues: @{AFEventParamRevenue  : @200,
+                      AFEventParamCurrency : @"USD",
+                      AFEventParamQuantity : @2,
+                      AFEventParamContentId: @"092",
+                      AFEventParamReceiptId: @"9277"}];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().logEvent(AFEventPurchase,
+        withValues: [AFEventParamRevenue  : "1200",
+                     AFEventParamContent  : "shoes",
+                     AFEventParamContentId: "123"])
+ 
+ + @param eventName Contains name of event that could be provided from predefined constants in `AppsFlyerLib.h` + @param values Contains dictionary of values for handling by backend + */ +- (void)logEvent:(NSString *)eventName withValues:(NSDictionary * _Nullable)values; + +- (void)logEventWithEventName:(NSString *)eventName + eventValues:(NSDictionary * _Nullable)eventValues + completionHandler:(void (^ _Nullable)(NSDictionary * _Nullable dictionary, NSError * _Nullable error))completionHandler +NS_SWIFT_NAME(logEvent(name:values:completionHandler:)); + +/** + To log and validate in app purchases you can call this method from the completeTransaction: method on + your `SKPaymentTransactionObserver`. + + @param productIdentifier The product identifier + @param price The product price + @param currency The product currency + @param transactionId The purchase transaction Id + @param params The additional param, which you want to receive it in the raw reports + @param successBlock The success callback + @param failedBlock The failure callback + */ +- (void)validateAndLogInAppPurchase:(NSString * _Nullable)productIdentifier + price:(NSString * _Nullable)price + currency:(NSString * _Nullable)currency + transactionId:(NSString * _Nullable)transactionId + additionalParameters:(NSDictionary * _Nullable)params + success:(void (^ _Nullable)(NSDictionary * response))successBlock + failure:(void (^ _Nullable)(NSError * _Nullable error, id _Nullable reponse))failedBlock NS_AVAILABLE(10_7, 7_0); + +typedef void (^AFSDKValidateAndLogCompletion)(AFSDKValidateAndLogResult * _Nullable result); + +/** + To log and validate in app purchases you can call this method from the completeTransaction: method on + your `SKPaymentTransactionObserver`. + + @param details The product details + @param extraEventValues The additional param, which you want to receive it in the raw reports + @param completionHandler The callback + */ +- (void)validateAndLogInAppPurchase:(AFSDKPurchaseDetails *)details + extraEventValues:(NSDictionary * _Nullable)extraEventValues + completionHandler:(AFSDKValidateAndLogCompletion)completionHandler NS_AVAILABLE(10_7, 7_0); + +/** + An API to provide the data from the impression payload to AdRevenue. + + @param adRevenueData object used to hold all mandatory parameters of AdRevenue event. + @param additionalParameters non-mandatory dictionary which can include pre-defined keys (kAppsFlyerAdRevenueCountry, etc) + */ +- (void)logAdRevenue:(AFAdRevenueData *)adRevenueData additionalParameters:(NSDictionary * _Nullable)additionalParameters; + +/** + To log location for geo-fencing. Does the same as code below. + +
+ AppsFlyerLib.shared().logEvent(AFEventLocation, withValues: [AFEventParamLong:longitude, AFEventParamLat:latitude])
+ 
+ + @param longitude The location longitude + @param latitude The location latitude + */ +- (void)logLocation:(double)longitude latitude:(double)latitude NS_SWIFT_NAME(logLocation(longitude:latitude:)); + +/** + This method returns AppsFlyer's internal id(unique for your app) + + @return Internal AppsFlyer Id + */ +- (NSString *)getAppsFlyerUID; + +/** + In case you want to log deep linking. Does the same as `-handleOpenURL:sourceApplication:withAnnotation`. + + @warning Preferred to use `-handleOpenURL:sourceApplication:withAnnotation`. + + @param url The URL that was passed to your AppDelegate. + @param sourceApplication The sourceApplication that passed to your AppDelegate. + */ +- (void)handleOpenURL:(NSURL * _Nullable)url sourceApplication:(NSString * _Nullable)sourceApplication API_UNAVAILABLE(macos); + +/** + In case you want to log deep linking. + Call this method from inside your AppDelegate `-application:openURL:sourceApplication:annotation:` + + @param url The URL that was passed to your AppDelegate. + @param sourceApplication The sourceApplication that passed to your AppDelegate. + @param annotation The annotation that passed to your app delegate. + */ +- (void)handleOpenURL:(NSURL * _Nullable)url + sourceApplication:(NSString * _Nullable)sourceApplication + withAnnotation:(id _Nullable)annotation API_UNAVAILABLE(macos); + +/** + Call this method from inside of your AppDelegate `-application:openURL:options:` method. + This method is functionally the same as calling the AppsFlyer method + `-handleOpenURL:sourceApplication:withAnnotation`. + + @param url The URL that was passed to your app delegate + @param options The options dictionary that was passed to your AppDelegate. + */ +- (void)handleOpenUrl:(NSURL * _Nullable)url options:(NSDictionary * _Nullable)options API_UNAVAILABLE(macos); + +/** + Allow AppsFlyer to handle restoration from an NSUserActivity. + Use this method to log deep links with OneLink. + + @param userActivity The NSUserActivity that caused the app to be opened. + */ +- (BOOL)continueUserActivity:(NSUserActivity * _Nullable)userActivity + restorationHandler:(void (^ _Nullable)(NSArray * _Nullable))restorationHandler NS_AVAILABLE_IOS(9_0) API_UNAVAILABLE(macos); + +/** + Enable AppsFlyer to handle a push notification. + + @see [Learn more here](https://support.appsflyer.com/hc/en-us/articles/207364076-Measuring-Push-Notification-Re-Engagement-Campaigns) + + @warning To make it work - set data, related to AppsFlyer under key @"af". + + @param pushPayload The `userInfo` from received remote notification. One of root keys should be @"af". + */ +- (void)handlePushNotification:(NSDictionary * _Nullable)pushPayload; + + +/** + Register uninstall - you should register for remote notification and provide AppsFlyer the push device token. + + @param deviceToken The `deviceToken` from `-application:didRegisterForRemoteNotificationsWithDeviceToken:` + */ +- (void)registerUninstall:(NSData * _Nullable)deviceToken; + +/** + Get SDK version. + + @return The AppsFlyer SDK version info. + */ +- (NSString *)getSDKVersion; + +/** + This is for internal use. + */ +- (void)remoteDebuggingCallWithData:(NSString *)data; + +/** + This is for internal use. + */ +- (void)remoteDebuggingCallV2WithData:(NSString *)dataAsString; + +/** + Used to force the trigger `onAppOpenAttribution` delegate. + Notice, re-engagement, session and launch won't be counted. + Only for OneLink/UniversalLink/Deeplink resolving. + + @param URL The param to resolve into -[AppsFlyerLibDelegate onAppOpenAttribution:] + */ +- (void)performOnAppAttributionWithURL:(NSURL * _Nullable)URL; + +/** + @brief This property accepts a string value representing the host name for all endpoints. + Can be used to Zero rate your application’s data usage. Contact your CSM for more information. + + @warning To use `default` SDK endpoint – set value to `nil`. + + Objective-C: + +
+ [[AppsFlyerLib shared] setHost:@"example.com"];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().host = "example.com"
+ 
+ */ +@property(nonatomic, strong, readonly) NSString *host; + +/** + * This function set the host name and prefix host name for all the endpoints + **/ +- (void)setHost:(NSString *)host withHostPrefix:(NSString *)hostPrefix; + +/** + * This property accepts a string value representing the prefix host name for all endpoints. + * for example "test" prefix with default host name will have the address "host.appsflyer.com" + */ +@property(nonatomic, strong, readonly) NSString *hostPrefix; + +/** + This property is responsible for timeout between sessions in seconds. + Default value is 5 seconds. + */ +@property(atomic) NSUInteger minTimeBetweenSessions; + +/** + API to shut down all SDK activities. + + @warning This will disable all requests from AppsFlyer SDK. + */ +@property(atomic) BOOL isStopped; + +/** + API to set manually Facebook deferred app link + */ +@property(nonatomic, nullable, copy) NSURL *facebookDeferredAppLink; + +/** + Block an events from being shared with ad networks and other 3rd party integrations + Must only include letters/digits or underscore, maximum length: 45 + */ +@property(nonatomic, nullable, copy) NSArray *sharingFilter DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); + +@property(nonatomic) NSUInteger deepLinkTimeout; + +/** + Block an events from being shared with any partner + This method overwrite -[AppsFlyerLib setSharingFilter:] + */ +- (void)setSharingFilterForAllPartners DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); + +/** + Block an events from being shared with ad networks and other 3rd party integrations + Must only include letters/digits or underscore, maximum length: 45 + + The sharing filter is cleared in case if `nil` or empty array passed as a parameter. + "all" keyword sets sharing filter for ALL partners, it is case insencitive and has highest priority + if passed along with another values. For example, if ["all", "examplePartner1_int", "examplePartner2_int" ] passed, + the sharing filter will be set for ALL partners. + */ +- (void)setSharingFilterForPartners:(NSArray * _Nullable)sharingFilter; + + +/** + Sets or updates the user consent data related to GDPR and DMA regulations for advertising and data usage + purposes within the application. This method must be invoked with the user's current consent status each + time the app starts or whenever there is a change in the user's consent preferences. + + Note that this method does not persist the consent data across app sessions; it only applies for the + duration of the current app session. If you wish to stop providing the consent data, you should + cease calling this method. + + @param consent an instance of AppsFlyerConsent that encapsulates the user's consent information. + */ +- (void)setConsentData:(AppsFlyerConsent *)consent; + +/** + Enable the SDK to collect and send TCF data + + @param shouldCollectConsentData indicates if the TCF data collection is enabled. + */ +- (void)enableTCFDataCollection:(BOOL)shouldCollectConsentData; + +/** + Validate if URL contains certain string and append quiery + parameters to deeplink URL. In case if URL does not contain user-defined string, + parameters are not appended to the url. + + @param containsString string to check in URL. + @param parameters NSDictionary, which containins parameters to append to the deeplink url after it passed validation. + */ +- (void)appendParametersToDeepLinkingURLWithString:(NSString *)containsString + parameters:(NSDictionary *)parameters +NS_SWIFT_NAME(appendParametersToDeeplinkURL(contains:parameters:)); + +/** + Adds array of keys, which are used to compose key path + to resolve deeplink from push notification payload `userInfo`. + + @param deepLinkPath an array of strings which contains keys to search for deeplink in payload. + */ +- (void)addPushNotificationDeepLinkPath:(NSArray *)deepLinkPath; + +/** + * Allows sending custom data for partner integration purposes. + * + * @param partnerId ID of the partner (usually has "_int" suffix) + * @param partnerInfo customer data, depends on the integration nature with specific partner + */ + +- (void)setPartnerDataWithPartnerId:(NSString * _Nullable)partnerId partnerInfo:(NSDictionary * _Nullable)partnerInfo +NS_SWIFT_NAME(setPartnerData(partnerId:partnerInfo:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h new file mode 100644 index 000000000..d3ec8f4e4 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h @@ -0,0 +1,52 @@ +// +// LinkGenerator.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Payload container for the `generateInviteUrlWithLinkGenerator:completionHandler:` from `AppsFlyerShareInviteHelper` + */ +@interface AppsFlyerLinkGenerator : NSObject + +/// Instance initialization is not allowed. Use generated instance +/// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` +- (instancetype)init NS_UNAVAILABLE; +/// Instance initialization is not allowed. Use generated instance +/// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` ++ (instancetype)new NS_UNAVAILABLE; + +@property(nonatomic, nullable, copy) NSString *brandDomain; + +/// The channel through which the invite was sent (e.g. Facebook/Gmail/etc.). Usage: Recommended +- (void)setChannel :(nonnull NSString *)channel; +/// ReferrerCustomerId setter +- (void)setReferrerCustomerId:(nonnull NSString *)referrerCustomerId; +/// A campaign name. Usage: Optional +- (void)setCampaign :(nonnull NSString *)campaign; +/// ReferrerUID setter +- (void)setReferrerUID :(nonnull NSString *)referrerUID; +/// Referrer name +- (void)setReferrerName :(nonnull NSString *)referrerName; +/// The URL to referrer user avatar. Usage: Optional +- (void)setReferrerImageURL :(nonnull NSString *)referrerImageURL; +/// AppleAppID +- (void)setAppleAppID :(nonnull NSString *)appleAppID; +/// Deeplink path +- (void)setDeeplinkPath :(nonnull NSString *)deeplinkPath; +/// Base deeplink path +- (void)setBaseDeeplink :(nonnull NSString *)baseDeeplink; +/// A single key value custom parameter. Usage: Optional +- (void)addParameterValue :(nonnull NSString *)value forKey:(NSString *)key; +/// Multiple key value custom parameters. Usage: Optional +- (void)addParameters :(nonnull NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h new file mode 100644 index 000000000..f55dbf9d0 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h @@ -0,0 +1,35 @@ +// +// ShareInviteHelper.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import +#import + +/** + AppsFlyerShareInviteHelper + */ +@interface AppsFlyerShareInviteHelper : NSObject + +NS_ASSUME_NONNULL_BEGIN + +/** + * The AppsFlyerShareInviteHelper class builds the invite URL according to various setter methods + * which allow passing on additional information on the click. + * This information is available through `onConversionDataReceived:` when the user accepts the invite and installs the app. + * In addition, campaign and channel parameters are visible within the AppsFlyer Dashboard. + */ ++ (void)generateInviteUrlWithLinkGenerator:(AppsFlyerLinkGenerator *(^)(AppsFlyerLinkGenerator *generator))generatorCreator completionHandler:(void (^)(NSURL *_Nullable url))completionHandler; + +/** + * It is recommended to generate an in-app event after the invite is sent to log the invites from the senders' perspective. + * This enables you to find the users that tend most to invite friends, and the media sources that get you these users. + */ ++ (void)logInvite:(nullable NSString *)channel parameters:(nullable NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Info.plist b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Info.plist new file mode 100644 index 000000000..8cba11fac Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Info.plist differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.abi.json b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.abi.json new file mode 100644 index 000000000..4d7c1be56 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.abi.json @@ -0,0 +1,458 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "TopLevel", + "printedName": "TopLevel", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKConnectorCommunication", + "printedName": "AFSDKConnectorCommunication", + "children": [ + { + "kind": "Function", + "name": "setPurchaseDataSendingDelegate", + "printedName": "setPurchaseDataSendingDelegate(delegate:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AppsFlyerLib.AFSDKPurchaseDataSendingDelegate", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate" + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication(im)setPurchaseDataSendingDelegateWithDelegate:", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP30setPurchaseDataSendingDelegate8delegateyAA013AFSDKPurchasehiJ0_p_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKConnectorCommunication>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "setPurchaseDataSendingDelegateWithDelegate:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AFSDKPurchaseDataSendingDelegate", + "children": [ + { + "kind": "Function", + "name": "sendDecryptReceiptRequest", + "printedName": "sendDecryptReceiptRequest(environment:receipt:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP25sendDecryptReceiptRequest11environment7receipt17completionHandlerySS_SSySDys11AnyHashableVypGSg_s5Error_pSgtctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendARSRequest", + "printedName": "sendARSRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendARSRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP14sendARSRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendARSRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendVIAPRequest", + "printedName": "sendVIAPRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendVIAPRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP15sendVIAPRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendVIAPRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendCachedPurchaseConnectorEvents", + "printedName": "sendCachedPurchaseConnectorEvents(completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "() -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP33sendCachedPurchaseConnectorEvents17completionHandleryyyc_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.private.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.private.swiftinterface new file mode 100644 index 000000000..1845db80c --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.private.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target arm64-apple-ios12.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.swiftdoc b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.swiftdoc new file mode 100644 index 000000000..4a0d93260 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.swiftdoc differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.swiftinterface new file mode 100644 index 000000000..1845db80c --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target arm64-apple-ios12.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/module.modulemap b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/module.modulemap new file mode 100644 index 000000000..abf5a4827 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/Modules/module.modulemap @@ -0,0 +1,11 @@ +framework module AppsFlyerLib { + umbrella header "AppsFlyerLib.h" + + export * + module * { export * } +} + +module AppsFlyerLib.Swift { + header "AppsFlyerLib-Swift.h" + requires objc +} diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/PrivacyInfo.xcprivacy b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..5da2889f7 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64/AppsFlyerLib.framework/PrivacyInfo.xcprivacy @@ -0,0 +1,73 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyTrackingDomains + + att-attr.whappsflyer.com + att-attr.appsflyer-cn.com + att-attr.hevents.appsflyer-cn.com + att-launches.whappsflyer.com + att-launches.appsflyer-cn.com + att-launches.hevents.appsflyer-cn.com + att-conversions.hevents.appsflyer-cn.com + att-conversions.appsflyer-cn.com + att-conversions.whappsflyer.com + att-dlsdk.hevents.appsflyer-cn.com + att-dlsdk.appsflyer-cn.com + att-dlsdk.whappsflyer.com + att-dlsdk.appsflyersdk.com + att-conversions.appsflyersdk.com + att-launches.appsflyersdk.com + att-attr.appsflyersdk.com + + NSPrivacyTracking + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + NSPrivacyAccessedAPITypeReasons + + C617.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + + + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/AppsFlyerLib b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/AppsFlyerLib new file mode 120000 index 000000000..fd96586f8 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/AppsFlyerLib @@ -0,0 +1 @@ +Versions/Current/AppsFlyerLib \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Headers b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Headers new file mode 120000 index 000000000..a177d2a6b --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Modules b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Modules new file mode 120000 index 000000000..5736f3186 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Modules @@ -0,0 +1 @@ +Versions/Current/Modules \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Resources b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Resources new file mode 120000 index 000000000..953ee36f3 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/AppsFlyerLib b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/AppsFlyerLib new file mode 100644 index 000000000..e327b5d5d Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/AppsFlyerLib differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFAdRevenueData.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFAdRevenueData.h new file mode 100644 index 000000000..2025468a3 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFAdRevenueData.h @@ -0,0 +1,68 @@ +// +// AFAdRevenueData.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 26/06/2024. +// + +typedef NS_CLOSED_ENUM(NSUInteger, AppsFlyerAdRevenueMediationNetworkType) { + AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob = 1, + AppsFlyerAdRevenueMediationNetworkTypeIronSource = 2, + AppsFlyerAdRevenueMediationNetworkTypeApplovinMax= 3, + AppsFlyerAdRevenueMediationNetworkTypeFyber = 4, + AppsFlyerAdRevenueMediationNetworkTypeAppodeal = 5, + AppsFlyerAdRevenueMediationNetworkTypeAdmost = 6, + AppsFlyerAdRevenueMediationNetworkTypeTopon = 7, + AppsFlyerAdRevenueMediationNetworkTypeTradplus = 8, + AppsFlyerAdRevenueMediationNetworkTypeYandex = 9, + AppsFlyerAdRevenueMediationNetworkTypeChartBoost = 10, + AppsFlyerAdRevenueMediationNetworkTypeUnity = 11, + AppsFlyerAdRevenueMediationNetworkTypeToponPte = 12, + AppsFlyerAdRevenueMediationNetworkTypeCustom = 13, + AppsFlyerAdRevenueMediationNetworkTypeDirectMonetization = 14 +} NS_SWIFT_NAME(MediationNetworkType); + +#define kAppsFlyerAdRevenueMonetizationNetwork @"monetization_network" +#define kAppsFlyerAdRevenueMediationNetwork @"mediation_network" +#define kAppsFlyerAdRevenueEventRevenue @"event_revenue" +#define kAppsFlyerAdRevenueEventRevenueCurrency @"event_revenue_currency" +#define kAppsFlyerAdRevenueCustomParameters @"custom_parameters" +#define kAFADRWrapperTypeGeneric @"adrevenue_sdk" + +//Pre-defined keys for non-mandatory dictionary + +//Code ISO 3166-1 format +#define kAppsFlyerAdRevenueCountry @"country" + +//ID of the ad unit for the impression +#define kAppsFlyerAdRevenueAdUnit @"ad_unit" + +//Format of the ad +#define kAppsFlyerAdRevenueAdType @"ad_type" + +//ID of the ad placement for the impression +#define kAppsFlyerAdRevenuePlacement @"placement" + + +@interface AFAdRevenueData : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (strong, nonnull, nonatomic) NSString *monetizationNetwork; +@property AppsFlyerAdRevenueMediationNetworkType mediationNetwork; +@property (strong, nonnull, nonatomic) NSString *currencyIso4217Code; +@property (strong, nonnull, nonatomic) NSNumber *eventRevenue; + +/** +* @param monetizationNetwork network which monetized the impression (@"facebook") +* @param mediationNetwork mediation source that mediated the monetization network for the impression (AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob) +* @param currencyIso4217Code reported impression’s revenue currency ISO 4217 format (@"USD") +* @param eventRevenue reported impression’s revenue (@(0.001994303)) +*/ +- (instancetype _Nonnull )initWithMonetizationNetwork:(NSString *_Nonnull)monetizationNetwork + mediationNetwork:(AppsFlyerAdRevenueMediationNetworkType)mediationNetwork + currencyIso4217Code:(NSString *_Nonnull)currencyIso4217Code + eventRevenue:(NSNumber *_Nonnull)eventRevenue; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFSDKPurchaseDetails.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFSDKPurchaseDetails.h new file mode 100644 index 000000000..89af8a702 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFSDKPurchaseDetails.h @@ -0,0 +1,23 @@ +// +// AFSDKPurchaseDetails.h +// AppsFlyerLib +// +// Created by Moris Gateno on 13/03/2024. +// + +@interface AFSDKPurchaseDetails : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (strong, nullable, nonatomic) NSString *productId; +@property (strong, nullable, nonatomic) NSString *price; +@property (strong, nullable, nonatomic) NSString *currency; +@property (strong, nullable, nonatomic) NSString *transactionId; + +- (instancetype _Nonnull )initWithProductId:(NSString *_Nullable)productId + price:(NSString *_Nullable)price + currency:(NSString *_Nullable)currency + transactionId:(NSString *_Nullable)transactionId; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFSDKValidateAndLogResult.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFSDKValidateAndLogResult.h new file mode 100644 index 000000000..94ef0f4c2 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AFSDKValidateAndLogResult.h @@ -0,0 +1,35 @@ +// +// AFSDKValidateAndLogResult.h +// AppsFlyerLib +// +// Created by Moris Gateno on 13/03/2024. +// + + +typedef NS_CLOSED_ENUM(NSUInteger, AFSDKValidateAndLogStatus) { + AFSDKValidateAndLogStatusSuccess, + AFSDKValidateAndLogStatusFailure, + AFSDKValidateAndLogStatusError +} NS_SWIFT_NAME(ValidateAndLogStatus); + +NS_SWIFT_NAME(ValidateAndLogResult) +@interface AFSDKValidateAndLogResult : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +- (instancetype _Nonnull )initWithStatus:(AFSDKValidateAndLogStatus)status + result:(NSDictionary *_Nullable)result + errorData:(NSDictionary *_Nullable)errorData + error:(NSError *_Nullable)error; + +@property(readonly) AFSDKValidateAndLogStatus status; +// Success case +@property(readonly, nullable) NSDictionary *result; +// Server 200 with validation failure +@property(readonly, nullable) NSDictionary *errorData; +// for the error case +@property(readonly, nullable) NSError *error; + +@end + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerConsent.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerConsent.h new file mode 100644 index 000000000..564912a92 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerConsent.h @@ -0,0 +1,26 @@ +// +// AppsFlyerConsent.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 14/01/2024. +// +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AppsFlyerConsent : NSObject + +@property (nonatomic, readonly, assign) BOOL isUserSubjectToGDPR; +@property (nonatomic, readonly, assign) BOOL hasConsentForDataUsage; +@property (nonatomic, readonly, assign) BOOL hasConsentForAdsPersonalization; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (instancetype)initForGDPRUserWithHasConsentForDataUsage:(BOOL)hasConsentForDataUsage + hasConsentForAdsPersonalization:(BOOL)hasConsentForAdsPersonalization NS_DESIGNATED_INITIALIZER; +- (instancetype)initNonGDPRUser NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerCrossPromotionHelper.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerCrossPromotionHelper.h new file mode 100644 index 000000000..483f8f863 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerCrossPromotionHelper.h @@ -0,0 +1,49 @@ +// +// CrossPromotionHelper.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + AppsFlyer allows you to log and attribute installs originating + from cross promotion campaigns of your existing apps. + Afterwards, you can optimize on your cross-promotion traffic to get even better results. + */ +@interface AppsFlyerCrossPromotionHelper : NSObject + +/** + To log an impression use the following API call. + Make sure to use the promoted App ID as it appears within the AppsFlyer dashboard. + + @param appID Promoted App ID + @param campaign A campaign name + @param parameters Additional params like `@{@"af_sub1": @"val", @"custom_param": @"val2" }` +*/ ++ (void)logCrossPromoteImpression:(nonnull NSString *)appID + campaign:(nullable NSString *)campaign + parameters:(nullable NSDictionary *)parameters; + +/** + iOS allows you to utilize the StoreKit component to open + the App Store while remaining in the context of your app. + More details at https://support.appsflyer.com/hc/en-us/articles/115004481946-Cross-Promotion-Tracking#tracking-cross-promotion-impressions + + @param appID Promoted App ID + @param campaign A campaign name + @param parameters Additional params like `@{@"af_sub1": @"val", @"custom_param": @"val2" }` + @param openStoreBlock Contains promoted `clickURL` + */ ++ (void)logAndOpenStore:(nonnull NSString *)appID + campaign:(nullable NSString *)campaign + parameters:(nullable NSDictionary *)parameters + openStore:(void (^)(NSURLSession *urlSession, NSURL *clickURL))openStoreBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLink.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLink.h new file mode 100644 index 000000000..f099aceb9 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLink.h @@ -0,0 +1,36 @@ +// +// AFSDKDeeplink.h +// AppsFlyerLib +// +// Created by Andrii Hahan on 20.08.2020. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(DeepLink) +@interface AppsFlyerDeepLink : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (readonly, nonnull) NSDictionary *clickEvent; +@property (readonly, nullable) NSString *deeplinkValue; +@property (readonly, nullable) NSString *matchType; +@property (readonly, nullable) NSString *clickHTTPReferrer; +@property (readonly, nullable) NSString *mediaSource; +@property (readonly, nullable) NSString *campaign; +@property (readonly, nullable) NSString *campaignId; +@property (readonly, nullable) NSString *afSub1; +@property (readonly, nullable) NSString *afSub2; +@property (readonly, nullable) NSString *afSub3; +@property (readonly, nullable) NSString *afSub4; +@property (readonly, nullable) NSString *afSub5; +@property (readonly) BOOL isDeferred; + +- (NSString *)toString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLinkResult.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLinkResult.h new file mode 100644 index 000000000..50d41d71e --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLinkResult.h @@ -0,0 +1,29 @@ +// +// AFSDKDeeplinkResult.h +// AppsFlyerLib +// +// Created by Andrii Hahan on 20.08.2020. +// + +#import + +@class AppsFlyerDeepLink; + +typedef NS_CLOSED_ENUM(NSUInteger, AFSDKDeepLinkResultStatus) { + AFSDKDeepLinkResultStatusNotFound, + AFSDKDeepLinkResultStatusFound, + AFSDKDeepLinkResultStatusFailure, +} NS_SWIFT_NAME(DeepLinkResultStatus); + +NS_SWIFT_NAME(DeepLinkResult) +@interface AppsFlyerDeepLinkResult : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property(readonly) AFSDKDeepLinkResultStatus status; + +@property(readonly, nullable) AppsFlyerDeepLink *deepLink; +@property(readonly, nullable) NSError *error; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib-Swift.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib-Swift.h new file mode 100644 index 000000000..4155b266d --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib-Swift.h @@ -0,0 +1,618 @@ +#if 0 +#elif defined(__arm64__) && __arm64__ +// Generated by Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +#ifndef APPSFLYERLIB_SWIFT_H +#define APPSFLYERLIB_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="AppsFlyerLib",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#elif defined(__x86_64__) && __x86_64__ +// Generated by Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +#ifndef APPSFLYERLIB_SWIFT_H +#define APPSFLYERLIB_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="AppsFlyerLib",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#else +#error unsupported Swift architecture +#endif diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib.h new file mode 100644 index 000000000..182fe6e91 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib.h @@ -0,0 +1,743 @@ +// +// AppsFlyerLib.h +// AppsFlyerLib +// +// AppsFlyer iOS SDK 6.15.1 (211) +// Copyright (c) 2012-2023 AppsFlyer Ltd. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +// In app event names constants +#define AFEventLevelAchieved @"af_level_achieved" +#define AFEventAddPaymentInfo @"af_add_payment_info" +#define AFEventAddToCart @"af_add_to_cart" +#define AFEventAddToWishlist @"af_add_to_wishlist" +#define AFEventCompleteRegistration @"af_complete_registration" +#define AFEventTutorial_completion @"af_tutorial_completion" +#define AFEventInitiatedCheckout @"af_initiated_checkout" +#define AFEventPurchase @"af_purchase" +#define AFEventRate @"af_rate" +#define AFEventSearch @"af_search" +#define AFEventSpentCredits @"af_spent_credits" +#define AFEventAchievementUnlocked @"af_achievement_unlocked" +#define AFEventContentView @"af_content_view" +#define AFEventListView @"af_list_view" +#define AFEventTravelBooking @"af_travel_booking" +#define AFEventShare @"af_share" +#define AFEventInvite @"af_invite" +#define AFEventLogin @"af_login" +#define AFEventReEngage @"af_re_engage" +#define AFEventUpdate @"af_update" +#define AFEventOpenedFromPushNotification @"af_opened_from_push_notification" +#define AFEventLocation @"af_location_coordinates" +#define AFEventCustomerSegment @"af_customer_segment" + +#define AFEventSubscribe @"af_subscribe" +#define AFEventStartTrial @"af_start_trial" +#define AFEventAdClick @"af_ad_click" +#define AFEventAdView @"af_ad_view" + +// In app event parameter names +#define AFEventParamContent @"af_content" +#define AFEventParamAchievementId @"af_achievement_id" +#define AFEventParamLevel @"af_level" +#define AFEventParamScore @"af_score" +#define AFEventParamSuccess @"af_success" +#define AFEventParamPrice @"af_price" +#define AFEventParamContentType @"af_content_type" +#define AFEventParamContentId @"af_content_id" +#define AFEventParamContentList @"af_content_list" +#define AFEventParamCurrency @"af_currency" +#define AFEventParamQuantity @"af_quantity" +#define AFEventParamRegistrationMethod @"af_registration_method" +#define AFEventParamPaymentInfoAvailable @"af_payment_info_available" +#define AFEventParamMaxRatingValue @"af_max_rating_value" +#define AFEventParamRatingValue @"af_rating_value" +#define AFEventParamSearchString @"af_search_string" +#define AFEventParamDateA @"af_date_a" +#define AFEventParamDateB @"af_date_b" +#define AFEventParamDestinationA @"af_destination_a" +#define AFEventParamDestinationB @"af_destination_b" +#define AFEventParamDescription @"af_description" +#define AFEventParamClass @"af_class" +#define AFEventParamEventStart @"af_event_start" +#define AFEventParamEventEnd @"af_event_end" +#define AFEventParamLat @"af_lat" +#define AFEventParamLong @"af_long" +#define AFEventParamCustomerUserId @"af_customer_user_id" +#define AFEventParamValidated @"af_validated" +#define AFEventParamRevenue @"af_revenue" +#define AFEventProjectedParamRevenue @"af_projected_revenue" +#define AFEventParamReceiptId @"af_receipt_id" +#define AFEventParamTutorialId @"af_tutorial_id" +#define AFEventParamVirtualCurrencyName @"af_virtual_currency_name" +#define AFEventParamDeepLink @"af_deep_link" +#define AFEventParamOldVersion @"af_old_version" +#define AFEventParamNewVersion @"af_new_version" +#define AFEventParamReviewText @"af_review_text" +#define AFEventParamCouponCode @"af_coupon_code" +#define AFEventParamOrderId @"af_order_id" +#define AFEventParam1 @"af_param_1" +#define AFEventParam2 @"af_param_2" +#define AFEventParam3 @"af_param_3" +#define AFEventParam4 @"af_param_4" +#define AFEventParam5 @"af_param_5" +#define AFEventParam6 @"af_param_6" +#define AFEventParam7 @"af_param_7" +#define AFEventParam8 @"af_param_8" +#define AFEventParam9 @"af_param_9" +#define AFEventParam10 @"af_param_10" +#define AFEventParamTouch @"af_touch_obj" + +#define AFEventParamDepartingDepartureDate @"af_departing_departure_date" +#define AFEventParamReturningDepartureDate @"af_returning_departure_date" +#define AFEventParamDestinationList @"af_destination_list" //array of string +#define AFEventParamCity @"af_city" +#define AFEventParamRegion @"af_region" +#define AFEventParamCountry @"af_country" + + +#define AFEventParamDepartingArrivalDate @"af_departing_arrival_date" +#define AFEventParamReturningArrivalDate @"af_returning_arrival_date" +#define AFEventParamSuggestedDestinations @"af_suggested_destinations" //array of string +#define AFEventParamTravelStart @"af_travel_start" +#define AFEventParamTravelEnd @"af_travel_end" +#define AFEventParamNumAdults @"af_num_adults" +#define AFEventParamNumChildren @"af_num_children" +#define AFEventParamNumInfants @"af_num_infants" +#define AFEventParamSuggestedHotels @"af_suggested_hotels" //array of string + +#define AFEventParamUserScore @"af_user_score" +#define AFEventParamHotelScore @"af_hotel_score" +#define AFEventParamPurchaseCurrency @"af_purchase_currency" + +#define AFEventParamPreferredStarRatings @"af_preferred_star_ratings" //array of int (basically a tuple (min,max) but we'll use array of int and instruct the developer to use two values) + +#define AFEventParamPreferredPriceRange @"af_preferred_price_range" //array of int (basically a tuple (min,max) but we'll use array of int and instruct the developer to use two values) +#define AFEventParamPreferredNeighborhoods @"af_preferred_neighborhoods" //array of string +#define AFEventParamPreferredNumStops @"af_preferred_num_stops" + +/// Mail hashing type +typedef enum { + /// None + EmailCryptTypeNone = 0, + /// SHA256 + EmailCryptTypeSHA256 = 3 +} EmailCryptType; + +typedef NS_CLOSED_ENUM(NSInteger, AFSDKPlugin) { + AFSDKPluginIOSNative, + AFSDKPluginUnity, + AFSDKPluginFlutter, + AFSDKPluginReactNative, + AFSDKPluginAdobeAir, + AFSDKPluginAdobeMobile, + AFSDKPluginCocos2dx, + AFSDKPluginCordova, + AFSDKPluginMparticle, + AFSDKPluginNativeScript, + AFSDKPluginExpo, + AFSDKPluginUnreal, + AFSDKPluginXamarin, + AFSDKPluginCapacitor, + AFSDKPluginSegment, + AFSDKPluginAdobeSwiftAEP +} NS_SWIFT_NAME(Plugin); + + +NS_SWIFT_NAME(DeepLinkDelegate) +@protocol AppsFlyerDeepLinkDelegate + +@optional +- (void)didResolveDeepLink:(AppsFlyerDeepLinkResult *_Nonnull)result; + +@end + +/** + Conform and subscribe to this protocol to allow getting data about conversion and + install attribution + */ +@protocol AppsFlyerLibDelegate + +/** + `conversionInfo` contains information about install. + Organic/non-organic, etc. + @param conversionInfo May contain null values for some keys. Please handle this case. + */ +- (void)onConversionDataSuccess:(NSDictionary *)conversionInfo; + +/** + Any errors that occurred during the conversion request. + */ +- (void)onConversionDataFail:(NSError *)error; + +@optional + +/** + `attributionData` contains information about OneLink, deeplink. + */ +- (void)onAppOpenAttribution:(NSDictionary *)attributionData; + +/** + Any errors that occurred during the attribution request. + */ +- (void)onAppOpenAttributionFailure:(NSError *)error; + +/** + @abstract Sets the HTTP header fields of the ESP resolving to the given + dictionary. + @discussion This method replaces all header fields that may have + existed before this method ESP resolving call. + To keep default SDK behavior - return nil; + */ +- (NSDictionary * _Nullable)allHTTPHeaderFieldsForResolveDeepLinkURL:(NSURL *)URL; + +@end + +/** + You can log installs, app updates, sessions and additional in-app events + (including in-app purchases, game levels, etc.) + to evaluate ROI and user engagement. + The iOS SDK is compatible with all iOS/tvOS devices with iOS version 7 and above. + + @see [SDK Integration Validator](https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS) + for more information. + + */ +@interface AppsFlyerLib : NSObject + +/** + Gets the singleton instance of the AppsFlyerLib class, creating it if + necessary. + + @return The singleton instance of AppsFlyerLib. + */ ++ (AppsFlyerLib *)shared; + + +- (void)setUpInteroperabilityObject:(id)object; + +/** + In case you use your own user ID in your app, you can set this property to that ID. + Enables you to cross-reference your own unique ID with AppsFlyer’s unique ID and the other devices’ IDs + */ +@property(nonatomic, strong, nullable) NSString * customerUserID; + +/** + In case you use custom data and you want to receive it in the raw reports. + + @see [Setting additional custom data](https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS#setting-additional-custom-data) for more information. + */ +@property(nonatomic, strong, nullable, setter = setAdditionalData:) NSDictionary * customData; + +/** + Use this property to set your AppsFlyer's dev key + */ +@property(nonatomic, strong) NSString * appsFlyerDevKey; + +/** + Use this property to set your app's Apple ID(taken from the app's page on iTunes Connect) + */ +@property(nonatomic, strong) NSString * appleAppID; + +#ifndef AFSDK_NO_IDFA +/** + AppsFlyer SDK collect Apple's `advertisingIdentifier` if the `AdSupport.framework` included in the SDK. + You can disable this behavior by setting the following property to YES +*/ +@property(nonatomic) BOOL disableAdvertisingIdentifier; + +@property(nonatomic, strong, readonly) NSString *advertisingIdentifier; + +/** + Waits for request user authorization to access app-related data + */ +- (void)waitForATTUserAuthorizationWithTimeoutInterval:(NSTimeInterval)timeoutInterval +NS_SWIFT_NAME(waitForATTUserAuthorization(timeoutInterval:)); + +#endif + +@property(nonatomic) BOOL disableSKAdNetwork; + +/** + In case of in app purchase events, you can set the currency code your user has purchased with. + The currency code is a 3 letter code according to ISO standards + + Objective-C: + +
+ [[AppsFlyerLib shared] setCurrencyCode:@"USD"];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().currencyCode = "USD"
+ 
+ */ +@property(nonatomic, strong, nullable) NSString *currencyCode; + +/** + Prints SDK messages to the console log. This property should only be used in `DEBUG` mode. + The default value is `NO` + */ +@property(nonatomic) BOOL isDebug; + +/** + Set this flag to `YES`, to collect the current device name(e.g. "My iPhone"). Default value is `NO` + */ +@property(nonatomic) BOOL shouldCollectDeviceName; + +/** + Set your `OneLink ID` from OneLink configuration. Used in User Invites to generate a OneLink. + */ +@property(nonatomic, strong, nullable, setter = setAppInviteOneLink:) NSString * appInviteOneLinkID; + +/** + Opt-out logging for specific user + */ +@property(atomic) BOOL anonymizeUser; + +/** + Opt-out for Apple Search Ads attributions + */ +@property(atomic) BOOL disableCollectASA; + +/** + Disable Apple Ads Attribution API +[AAAtribution attributionTokenWithError:] + */ +@property(nonatomic) BOOL disableAppleAdsAttribution; + +/** + AppsFlyer delegate. See `AppsFlyerLibDelegate` + */ +@property(weak, nonatomic) id delegate; + +@property(weak, nonatomic) id deepLinkDelegate; + +/** + In app purchase receipt validation Apple environment(production or sandbox). The default value is NO + */ +@property(nonatomic) BOOL useReceiptValidationSandbox; + +/** + Set this flag to test uninstall on Apple environment(production or sandbox). The default value is NO + */ +@property(nonatomic) BOOL useUninstallSandbox; + +/** + For advertisers who wrap OneLink within another Universal Link. + An advertiser will be able to deeplink from a OneLink wrapped within another Universal Link and also log this retargeting conversion. + + Objective-C: + +
+ [[AppsFlyerLib shared] setResolveDeepLinkURLs:@[@"domain.com", @"subdomain.domain.com"]];
+ 
+ */ +@property(nonatomic, nullable, copy) NSArray *resolveDeepLinkURLs; + +/** + For advertisers who use vanity OneLinks. + + Objective-C: + +
+ [[AppsFlyerLib shared] oneLinkCustomDomains:@[@"domain.com", @"subdomain.domain.com"]];
+ 
+ */ +@property(nonatomic, nullable, copy) NSArray *oneLinkCustomDomains; + +/* + * Set phone number for each `start` event. `phoneNumber` will be sent as SHA256 string + */ +@property(nonatomic, nullable, copy) NSString *phoneNumber; + +- (NSString *)phoneNumber UNAVAILABLE_ATTRIBUTE; + +/** + To disable app's vendor identifier(IDFV), set disableIDFVCollection to true + */ +@property(nonatomic) BOOL disableIDFVCollection; + +/** + Set the language of the device. The data will be displayed in Raw Data Reports + Objective-C: + +
+ [[AppsFlyerLib shared] setCurrentDeviceLanguage:@"EN"]
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().currentDeviceLanguage("EN")
+ 
+ */ +@property(nonatomic, nullable, copy) NSString *currentDeviceLanguage; + +/** + Internal API. Please don't use. + */ +- (void)setPluginInfoWith:(AFSDKPlugin)plugin + pluginVersion:(NSString *)version + additionalParams:(NSDictionary * _Nullable)additionalParams +NS_SWIFT_NAME(setPluginInfo(plugin:version:additionalParams:)); + +/** + Enable the collection of Facebook Deferred AppLinks + Requires Facebook SDK and Facebook app on target/client device. + This API must be invoked prior to initializing the AppsFlyer SDK in order to function properly. + + Objective-C: + +
+ [[AppsFlyerLib shared] enableFacebookDeferredApplinksWithClass:[FBSDKAppLinkUtility class]]
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().enableFacebookDeferredApplinks(with: FBSDKAppLinkUtility.self)
+ 
+ + @param facebookAppLinkUtilityClass requeries method call `[FBSDKAppLinkUtility class]` as param. + */ +- (void)enableFacebookDeferredApplinksWithClass:(Class _Nullable)facebookAppLinkUtilityClass; + +/** + Use this to send the user's emails + + @param userEmails The list of strings that hold mails + @param type Hash algoritm + */ +- (void)setUserEmails:(NSArray * _Nullable)userEmails withCryptType:(EmailCryptType)type; + +/** + Start SDK session + Add the following method at the `applicationDidBecomeActive` in AppDelegate class + */ +- (void)start; + +- (void)startWithCompletionHandler:(void (^ _Nullable)(NSDictionary * _Nullable dictionary, NSError * _Nullable error))completionHandler; + +/** + Use this method to log an events with multiple values. See AppsFlyer's documentation for details. + + Objective-C: + +
+ [[AppsFlyerLib shared] logEvent:AFEventPurchase
+        withValues: @{AFEventParamRevenue  : @200,
+                      AFEventParamCurrency : @"USD",
+                      AFEventParamQuantity : @2,
+                      AFEventParamContentId: @"092",
+                      AFEventParamReceiptId: @"9277"}];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().logEvent(AFEventPurchase,
+        withValues: [AFEventParamRevenue  : "1200",
+                     AFEventParamContent  : "shoes",
+                     AFEventParamContentId: "123"])
+ 
+ + @param eventName Contains name of event that could be provided from predefined constants in `AppsFlyerLib.h` + @param values Contains dictionary of values for handling by backend + */ +- (void)logEvent:(NSString *)eventName withValues:(NSDictionary * _Nullable)values; + +- (void)logEventWithEventName:(NSString *)eventName + eventValues:(NSDictionary * _Nullable)eventValues + completionHandler:(void (^ _Nullable)(NSDictionary * _Nullable dictionary, NSError * _Nullable error))completionHandler +NS_SWIFT_NAME(logEvent(name:values:completionHandler:)); + +/** + To log and validate in app purchases you can call this method from the completeTransaction: method on + your `SKPaymentTransactionObserver`. + + @param productIdentifier The product identifier + @param price The product price + @param currency The product currency + @param transactionId The purchase transaction Id + @param params The additional param, which you want to receive it in the raw reports + @param successBlock The success callback + @param failedBlock The failure callback + */ +- (void)validateAndLogInAppPurchase:(NSString * _Nullable)productIdentifier + price:(NSString * _Nullable)price + currency:(NSString * _Nullable)currency + transactionId:(NSString * _Nullable)transactionId + additionalParameters:(NSDictionary * _Nullable)params + success:(void (^ _Nullable)(NSDictionary * response))successBlock + failure:(void (^ _Nullable)(NSError * _Nullable error, id _Nullable reponse))failedBlock NS_AVAILABLE(10_7, 7_0); + +typedef void (^AFSDKValidateAndLogCompletion)(AFSDKValidateAndLogResult * _Nullable result); + +/** + To log and validate in app purchases you can call this method from the completeTransaction: method on + your `SKPaymentTransactionObserver`. + + @param details The product details + @param extraEventValues The additional param, which you want to receive it in the raw reports + @param completionHandler The callback + */ +- (void)validateAndLogInAppPurchase:(AFSDKPurchaseDetails *)details + extraEventValues:(NSDictionary * _Nullable)extraEventValues + completionHandler:(AFSDKValidateAndLogCompletion)completionHandler NS_AVAILABLE(10_7, 7_0); + +/** + An API to provide the data from the impression payload to AdRevenue. + + @param adRevenueData object used to hold all mandatory parameters of AdRevenue event. + @param additionalParameters non-mandatory dictionary which can include pre-defined keys (kAppsFlyerAdRevenueCountry, etc) + */ +- (void)logAdRevenue:(AFAdRevenueData *)adRevenueData additionalParameters:(NSDictionary * _Nullable)additionalParameters; + +/** + To log location for geo-fencing. Does the same as code below. + +
+ AppsFlyerLib.shared().logEvent(AFEventLocation, withValues: [AFEventParamLong:longitude, AFEventParamLat:latitude])
+ 
+ + @param longitude The location longitude + @param latitude The location latitude + */ +- (void)logLocation:(double)longitude latitude:(double)latitude NS_SWIFT_NAME(logLocation(longitude:latitude:)); + +/** + This method returns AppsFlyer's internal id(unique for your app) + + @return Internal AppsFlyer Id + */ +- (NSString *)getAppsFlyerUID; + +/** + In case you want to log deep linking. Does the same as `-handleOpenURL:sourceApplication:withAnnotation`. + + @warning Preferred to use `-handleOpenURL:sourceApplication:withAnnotation`. + + @param url The URL that was passed to your AppDelegate. + @param sourceApplication The sourceApplication that passed to your AppDelegate. + */ +- (void)handleOpenURL:(NSURL * _Nullable)url sourceApplication:(NSString * _Nullable)sourceApplication API_UNAVAILABLE(macos); + +/** + In case you want to log deep linking. + Call this method from inside your AppDelegate `-application:openURL:sourceApplication:annotation:` + + @param url The URL that was passed to your AppDelegate. + @param sourceApplication The sourceApplication that passed to your AppDelegate. + @param annotation The annotation that passed to your app delegate. + */ +- (void)handleOpenURL:(NSURL * _Nullable)url + sourceApplication:(NSString * _Nullable)sourceApplication + withAnnotation:(id _Nullable)annotation API_UNAVAILABLE(macos); + +/** + Call this method from inside of your AppDelegate `-application:openURL:options:` method. + This method is functionally the same as calling the AppsFlyer method + `-handleOpenURL:sourceApplication:withAnnotation`. + + @param url The URL that was passed to your app delegate + @param options The options dictionary that was passed to your AppDelegate. + */ +- (void)handleOpenUrl:(NSURL * _Nullable)url options:(NSDictionary * _Nullable)options API_UNAVAILABLE(macos); + +/** + Allow AppsFlyer to handle restoration from an NSUserActivity. + Use this method to log deep links with OneLink. + + @param userActivity The NSUserActivity that caused the app to be opened. + */ +- (BOOL)continueUserActivity:(NSUserActivity * _Nullable)userActivity + restorationHandler:(void (^ _Nullable)(NSArray * _Nullable))restorationHandler NS_AVAILABLE_IOS(9_0) API_UNAVAILABLE(macos); + +/** + Enable AppsFlyer to handle a push notification. + + @see [Learn more here](https://support.appsflyer.com/hc/en-us/articles/207364076-Measuring-Push-Notification-Re-Engagement-Campaigns) + + @warning To make it work - set data, related to AppsFlyer under key @"af". + + @param pushPayload The `userInfo` from received remote notification. One of root keys should be @"af". + */ +- (void)handlePushNotification:(NSDictionary * _Nullable)pushPayload; + + +/** + Register uninstall - you should register for remote notification and provide AppsFlyer the push device token. + + @param deviceToken The `deviceToken` from `-application:didRegisterForRemoteNotificationsWithDeviceToken:` + */ +- (void)registerUninstall:(NSData * _Nullable)deviceToken; + +/** + Get SDK version. + + @return The AppsFlyer SDK version info. + */ +- (NSString *)getSDKVersion; + +/** + This is for internal use. + */ +- (void)remoteDebuggingCallWithData:(NSString *)data; + +/** + This is for internal use. + */ +- (void)remoteDebuggingCallV2WithData:(NSString *)dataAsString; + +/** + Used to force the trigger `onAppOpenAttribution` delegate. + Notice, re-engagement, session and launch won't be counted. + Only for OneLink/UniversalLink/Deeplink resolving. + + @param URL The param to resolve into -[AppsFlyerLibDelegate onAppOpenAttribution:] + */ +- (void)performOnAppAttributionWithURL:(NSURL * _Nullable)URL; + +/** + @brief This property accepts a string value representing the host name for all endpoints. + Can be used to Zero rate your application’s data usage. Contact your CSM for more information. + + @warning To use `default` SDK endpoint – set value to `nil`. + + Objective-C: + +
+ [[AppsFlyerLib shared] setHost:@"example.com"];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().host = "example.com"
+ 
+ */ +@property(nonatomic, strong, readonly) NSString *host; + +/** + * This function set the host name and prefix host name for all the endpoints + **/ +- (void)setHost:(NSString *)host withHostPrefix:(NSString *)hostPrefix; + +/** + * This property accepts a string value representing the prefix host name for all endpoints. + * for example "test" prefix with default host name will have the address "host.appsflyer.com" + */ +@property(nonatomic, strong, readonly) NSString *hostPrefix; + +/** + This property is responsible for timeout between sessions in seconds. + Default value is 5 seconds. + */ +@property(atomic) NSUInteger minTimeBetweenSessions; + +/** + API to shut down all SDK activities. + + @warning This will disable all requests from AppsFlyer SDK. + */ +@property(atomic) BOOL isStopped; + +/** + API to set manually Facebook deferred app link + */ +@property(nonatomic, nullable, copy) NSURL *facebookDeferredAppLink; + +/** + Block an events from being shared with ad networks and other 3rd party integrations + Must only include letters/digits or underscore, maximum length: 45 + */ +@property(nonatomic, nullable, copy) NSArray *sharingFilter DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); + +@property(nonatomic) NSUInteger deepLinkTimeout; + +/** + Block an events from being shared with any partner + This method overwrite -[AppsFlyerLib setSharingFilter:] + */ +- (void)setSharingFilterForAllPartners DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); + +/** + Block an events from being shared with ad networks and other 3rd party integrations + Must only include letters/digits or underscore, maximum length: 45 + + The sharing filter is cleared in case if `nil` or empty array passed as a parameter. + "all" keyword sets sharing filter for ALL partners, it is case insencitive and has highest priority + if passed along with another values. For example, if ["all", "examplePartner1_int", "examplePartner2_int" ] passed, + the sharing filter will be set for ALL partners. + */ +- (void)setSharingFilterForPartners:(NSArray * _Nullable)sharingFilter; + + +/** + Sets or updates the user consent data related to GDPR and DMA regulations for advertising and data usage + purposes within the application. This method must be invoked with the user's current consent status each + time the app starts or whenever there is a change in the user's consent preferences. + + Note that this method does not persist the consent data across app sessions; it only applies for the + duration of the current app session. If you wish to stop providing the consent data, you should + cease calling this method. + + @param consent an instance of AppsFlyerConsent that encapsulates the user's consent information. + */ +- (void)setConsentData:(AppsFlyerConsent *)consent; + +/** + Enable the SDK to collect and send TCF data + + @param shouldCollectConsentData indicates if the TCF data collection is enabled. + */ +- (void)enableTCFDataCollection:(BOOL)shouldCollectConsentData; + +/** + Validate if URL contains certain string and append quiery + parameters to deeplink URL. In case if URL does not contain user-defined string, + parameters are not appended to the url. + + @param containsString string to check in URL. + @param parameters NSDictionary, which containins parameters to append to the deeplink url after it passed validation. + */ +- (void)appendParametersToDeepLinkingURLWithString:(NSString *)containsString + parameters:(NSDictionary *)parameters +NS_SWIFT_NAME(appendParametersToDeeplinkURL(contains:parameters:)); + +/** + Adds array of keys, which are used to compose key path + to resolve deeplink from push notification payload `userInfo`. + + @param deepLinkPath an array of strings which contains keys to search for deeplink in payload. + */ +- (void)addPushNotificationDeepLinkPath:(NSArray *)deepLinkPath; + +/** + * Allows sending custom data for partner integration purposes. + * + * @param partnerId ID of the partner (usually has "_int" suffix) + * @param partnerInfo customer data, depends on the integration nature with specific partner + */ + +- (void)setPartnerDataWithPartnerId:(NSString * _Nullable)partnerId partnerInfo:(NSDictionary * _Nullable)partnerInfo +NS_SWIFT_NAME(setPartnerData(partnerId:partnerInfo:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLinkGenerator.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLinkGenerator.h new file mode 100644 index 000000000..d3ec8f4e4 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLinkGenerator.h @@ -0,0 +1,52 @@ +// +// LinkGenerator.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Payload container for the `generateInviteUrlWithLinkGenerator:completionHandler:` from `AppsFlyerShareInviteHelper` + */ +@interface AppsFlyerLinkGenerator : NSObject + +/// Instance initialization is not allowed. Use generated instance +/// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` +- (instancetype)init NS_UNAVAILABLE; +/// Instance initialization is not allowed. Use generated instance +/// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` ++ (instancetype)new NS_UNAVAILABLE; + +@property(nonatomic, nullable, copy) NSString *brandDomain; + +/// The channel through which the invite was sent (e.g. Facebook/Gmail/etc.). Usage: Recommended +- (void)setChannel :(nonnull NSString *)channel; +/// ReferrerCustomerId setter +- (void)setReferrerCustomerId:(nonnull NSString *)referrerCustomerId; +/// A campaign name. Usage: Optional +- (void)setCampaign :(nonnull NSString *)campaign; +/// ReferrerUID setter +- (void)setReferrerUID :(nonnull NSString *)referrerUID; +/// Referrer name +- (void)setReferrerName :(nonnull NSString *)referrerName; +/// The URL to referrer user avatar. Usage: Optional +- (void)setReferrerImageURL :(nonnull NSString *)referrerImageURL; +/// AppleAppID +- (void)setAppleAppID :(nonnull NSString *)appleAppID; +/// Deeplink path +- (void)setDeeplinkPath :(nonnull NSString *)deeplinkPath; +/// Base deeplink path +- (void)setBaseDeeplink :(nonnull NSString *)baseDeeplink; +/// A single key value custom parameter. Usage: Optional +- (void)addParameterValue :(nonnull NSString *)value forKey:(NSString *)key; +/// Multiple key value custom parameters. Usage: Optional +- (void)addParameters :(nonnull NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerShareInviteHelper.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerShareInviteHelper.h new file mode 100644 index 000000000..f55dbf9d0 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerShareInviteHelper.h @@ -0,0 +1,35 @@ +// +// ShareInviteHelper.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import +#import + +/** + AppsFlyerShareInviteHelper + */ +@interface AppsFlyerShareInviteHelper : NSObject + +NS_ASSUME_NONNULL_BEGIN + +/** + * The AppsFlyerShareInviteHelper class builds the invite URL according to various setter methods + * which allow passing on additional information on the click. + * This information is available through `onConversionDataReceived:` when the user accepts the invite and installs the app. + * In addition, campaign and channel parameters are visible within the AppsFlyer Dashboard. + */ ++ (void)generateInviteUrlWithLinkGenerator:(AppsFlyerLinkGenerator *(^)(AppsFlyerLinkGenerator *generator))generatorCreator completionHandler:(void (^)(NSURL *_Nullable url))completionHandler; + +/** + * It is recommended to generate an in-app event after the invite is sent to log the invites from the senders' perspective. + * This enables you to find the users that tend most to invite friends, and the media sources that get you these users. + */ ++ (void)logInvite:(nullable NSString *)channel parameters:(nullable NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.abi.json b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.abi.json new file mode 100644 index 000000000..4d7c1be56 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.abi.json @@ -0,0 +1,458 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "TopLevel", + "printedName": "TopLevel", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKConnectorCommunication", + "printedName": "AFSDKConnectorCommunication", + "children": [ + { + "kind": "Function", + "name": "setPurchaseDataSendingDelegate", + "printedName": "setPurchaseDataSendingDelegate(delegate:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AppsFlyerLib.AFSDKPurchaseDataSendingDelegate", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate" + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication(im)setPurchaseDataSendingDelegateWithDelegate:", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP30setPurchaseDataSendingDelegate8delegateyAA013AFSDKPurchasehiJ0_p_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKConnectorCommunication>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "setPurchaseDataSendingDelegateWithDelegate:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AFSDKPurchaseDataSendingDelegate", + "children": [ + { + "kind": "Function", + "name": "sendDecryptReceiptRequest", + "printedName": "sendDecryptReceiptRequest(environment:receipt:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP25sendDecryptReceiptRequest11environment7receipt17completionHandlerySS_SSySDys11AnyHashableVypGSg_s5Error_pSgtctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendARSRequest", + "printedName": "sendARSRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendARSRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP14sendARSRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendARSRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendVIAPRequest", + "printedName": "sendVIAPRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendVIAPRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP15sendVIAPRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendVIAPRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendCachedPurchaseConnectorEvents", + "printedName": "sendCachedPurchaseConnectorEvents(completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "() -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP33sendCachedPurchaseConnectorEvents17completionHandleryyyc_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.private.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.private.swiftinterface new file mode 100644 index 000000000..8be461c6f --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.private.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target arm64-apple-ios13.1-macabi -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.swiftdoc b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.swiftdoc new file mode 100644 index 000000000..823145651 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.swiftdoc differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.swiftinterface new file mode 100644 index 000000000..8be461c6f --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-macabi.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target arm64-apple-ios13.1-macabi -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.abi.json b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.abi.json new file mode 100644 index 000000000..4d7c1be56 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.abi.json @@ -0,0 +1,458 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "TopLevel", + "printedName": "TopLevel", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKConnectorCommunication", + "printedName": "AFSDKConnectorCommunication", + "children": [ + { + "kind": "Function", + "name": "setPurchaseDataSendingDelegate", + "printedName": "setPurchaseDataSendingDelegate(delegate:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AppsFlyerLib.AFSDKPurchaseDataSendingDelegate", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate" + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication(im)setPurchaseDataSendingDelegateWithDelegate:", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP30setPurchaseDataSendingDelegate8delegateyAA013AFSDKPurchasehiJ0_p_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKConnectorCommunication>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "setPurchaseDataSendingDelegateWithDelegate:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AFSDKPurchaseDataSendingDelegate", + "children": [ + { + "kind": "Function", + "name": "sendDecryptReceiptRequest", + "printedName": "sendDecryptReceiptRequest(environment:receipt:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP25sendDecryptReceiptRequest11environment7receipt17completionHandlerySS_SSySDys11AnyHashableVypGSg_s5Error_pSgtctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendARSRequest", + "printedName": "sendARSRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendARSRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP14sendARSRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendARSRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendVIAPRequest", + "printedName": "sendVIAPRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendVIAPRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP15sendVIAPRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendVIAPRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendCachedPurchaseConnectorEvents", + "printedName": "sendCachedPurchaseConnectorEvents(completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "() -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP33sendCachedPurchaseConnectorEvents17completionHandleryyyc_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.private.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.private.swiftinterface new file mode 100644 index 000000000..2181b0386 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.private.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target x86_64-apple-ios13.1-macabi -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.swiftdoc b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.swiftdoc new file mode 100644 index 000000000..da217482f Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.swiftdoc differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.swiftinterface new file mode 100644 index 000000000..2181b0386 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-macabi.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target x86_64-apple-ios13.1-macabi -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/module.modulemap b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/module.modulemap new file mode 100644 index 000000000..abf5a4827 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Modules/module.modulemap @@ -0,0 +1,11 @@ +framework module AppsFlyerLib { + umbrella header "AppsFlyerLib.h" + + export * + module * { export * } +} + +module AppsFlyerLib.Swift { + header "AppsFlyerLib-Swift.h" + requires objc +} diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Resources/Info.plist b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Resources/Info.plist new file mode 100644 index 000000000..f0af5f706 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Resources/Info.plist @@ -0,0 +1,52 @@ + + + + + BuildMachineOSBuild + 23C71 + CFBundleDevelopmentRegion + en + CFBundleExecutable + AppsFlyerLib + CFBundleIdentifier + com.appsflyer.sdk.lib + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + AppsFlyerLib + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 6.15.1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 21A326 + DTPlatformName + macosx + DTPlatformVersion + 14.0 + DTSDKBuild + 23A334 + DTSDKName + macosx14.0 + DTXcode + 1501 + DTXcodeBuild + 15A507 + LSMinimumSystemVersion + 10.15 + UIDeviceFamily + + 2 + + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Resources/PrivacyInfo.xcprivacy b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..5da2889f7 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/A/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,73 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyTrackingDomains + + att-attr.whappsflyer.com + att-attr.appsflyer-cn.com + att-attr.hevents.appsflyer-cn.com + att-launches.whappsflyer.com + att-launches.appsflyer-cn.com + att-launches.hevents.appsflyer-cn.com + att-conversions.hevents.appsflyer-cn.com + att-conversions.appsflyer-cn.com + att-conversions.whappsflyer.com + att-dlsdk.hevents.appsflyer-cn.com + att-dlsdk.appsflyer-cn.com + att-dlsdk.whappsflyer.com + att-dlsdk.appsflyersdk.com + att-conversions.appsflyersdk.com + att-launches.appsflyersdk.com + att-attr.appsflyersdk.com + + NSPrivacyTracking + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + NSPrivacyAccessedAPITypeReasons + + C617.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + + + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/Current b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/Current new file mode 120000 index 000000000..8c7e5a667 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-maccatalyst/AppsFlyerLib.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/AppsFlyerLib b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/AppsFlyerLib new file mode 100644 index 000000000..38dedb80f Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/AppsFlyerLib differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFAdRevenueData.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFAdRevenueData.h new file mode 100644 index 000000000..2025468a3 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFAdRevenueData.h @@ -0,0 +1,68 @@ +// +// AFAdRevenueData.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 26/06/2024. +// + +typedef NS_CLOSED_ENUM(NSUInteger, AppsFlyerAdRevenueMediationNetworkType) { + AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob = 1, + AppsFlyerAdRevenueMediationNetworkTypeIronSource = 2, + AppsFlyerAdRevenueMediationNetworkTypeApplovinMax= 3, + AppsFlyerAdRevenueMediationNetworkTypeFyber = 4, + AppsFlyerAdRevenueMediationNetworkTypeAppodeal = 5, + AppsFlyerAdRevenueMediationNetworkTypeAdmost = 6, + AppsFlyerAdRevenueMediationNetworkTypeTopon = 7, + AppsFlyerAdRevenueMediationNetworkTypeTradplus = 8, + AppsFlyerAdRevenueMediationNetworkTypeYandex = 9, + AppsFlyerAdRevenueMediationNetworkTypeChartBoost = 10, + AppsFlyerAdRevenueMediationNetworkTypeUnity = 11, + AppsFlyerAdRevenueMediationNetworkTypeToponPte = 12, + AppsFlyerAdRevenueMediationNetworkTypeCustom = 13, + AppsFlyerAdRevenueMediationNetworkTypeDirectMonetization = 14 +} NS_SWIFT_NAME(MediationNetworkType); + +#define kAppsFlyerAdRevenueMonetizationNetwork @"monetization_network" +#define kAppsFlyerAdRevenueMediationNetwork @"mediation_network" +#define kAppsFlyerAdRevenueEventRevenue @"event_revenue" +#define kAppsFlyerAdRevenueEventRevenueCurrency @"event_revenue_currency" +#define kAppsFlyerAdRevenueCustomParameters @"custom_parameters" +#define kAFADRWrapperTypeGeneric @"adrevenue_sdk" + +//Pre-defined keys for non-mandatory dictionary + +//Code ISO 3166-1 format +#define kAppsFlyerAdRevenueCountry @"country" + +//ID of the ad unit for the impression +#define kAppsFlyerAdRevenueAdUnit @"ad_unit" + +//Format of the ad +#define kAppsFlyerAdRevenueAdType @"ad_type" + +//ID of the ad placement for the impression +#define kAppsFlyerAdRevenuePlacement @"placement" + + +@interface AFAdRevenueData : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (strong, nonnull, nonatomic) NSString *monetizationNetwork; +@property AppsFlyerAdRevenueMediationNetworkType mediationNetwork; +@property (strong, nonnull, nonatomic) NSString *currencyIso4217Code; +@property (strong, nonnull, nonatomic) NSNumber *eventRevenue; + +/** +* @param monetizationNetwork network which monetized the impression (@"facebook") +* @param mediationNetwork mediation source that mediated the monetization network for the impression (AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob) +* @param currencyIso4217Code reported impression’s revenue currency ISO 4217 format (@"USD") +* @param eventRevenue reported impression’s revenue (@(0.001994303)) +*/ +- (instancetype _Nonnull )initWithMonetizationNetwork:(NSString *_Nonnull)monetizationNetwork + mediationNetwork:(AppsFlyerAdRevenueMediationNetworkType)mediationNetwork + currencyIso4217Code:(NSString *_Nonnull)currencyIso4217Code + eventRevenue:(NSNumber *_Nonnull)eventRevenue; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h new file mode 100644 index 000000000..89af8a702 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h @@ -0,0 +1,23 @@ +// +// AFSDKPurchaseDetails.h +// AppsFlyerLib +// +// Created by Moris Gateno on 13/03/2024. +// + +@interface AFSDKPurchaseDetails : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (strong, nullable, nonatomic) NSString *productId; +@property (strong, nullable, nonatomic) NSString *price; +@property (strong, nullable, nonatomic) NSString *currency; +@property (strong, nullable, nonatomic) NSString *transactionId; + +- (instancetype _Nonnull )initWithProductId:(NSString *_Nullable)productId + price:(NSString *_Nullable)price + currency:(NSString *_Nullable)currency + transactionId:(NSString *_Nullable)transactionId; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h new file mode 100644 index 000000000..94ef0f4c2 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h @@ -0,0 +1,35 @@ +// +// AFSDKValidateAndLogResult.h +// AppsFlyerLib +// +// Created by Moris Gateno on 13/03/2024. +// + + +typedef NS_CLOSED_ENUM(NSUInteger, AFSDKValidateAndLogStatus) { + AFSDKValidateAndLogStatusSuccess, + AFSDKValidateAndLogStatusFailure, + AFSDKValidateAndLogStatusError +} NS_SWIFT_NAME(ValidateAndLogStatus); + +NS_SWIFT_NAME(ValidateAndLogResult) +@interface AFSDKValidateAndLogResult : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +- (instancetype _Nonnull )initWithStatus:(AFSDKValidateAndLogStatus)status + result:(NSDictionary *_Nullable)result + errorData:(NSDictionary *_Nullable)errorData + error:(NSError *_Nullable)error; + +@property(readonly) AFSDKValidateAndLogStatus status; +// Success case +@property(readonly, nullable) NSDictionary *result; +// Server 200 with validation failure +@property(readonly, nullable) NSDictionary *errorData; +// for the error case +@property(readonly, nullable) NSError *error; + +@end + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h new file mode 100644 index 000000000..564912a92 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h @@ -0,0 +1,26 @@ +// +// AppsFlyerConsent.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 14/01/2024. +// +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AppsFlyerConsent : NSObject + +@property (nonatomic, readonly, assign) BOOL isUserSubjectToGDPR; +@property (nonatomic, readonly, assign) BOOL hasConsentForDataUsage; +@property (nonatomic, readonly, assign) BOOL hasConsentForAdsPersonalization; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (instancetype)initForGDPRUserWithHasConsentForDataUsage:(BOOL)hasConsentForDataUsage + hasConsentForAdsPersonalization:(BOOL)hasConsentForAdsPersonalization NS_DESIGNATED_INITIALIZER; +- (instancetype)initNonGDPRUser NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h new file mode 100644 index 000000000..483f8f863 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h @@ -0,0 +1,49 @@ +// +// CrossPromotionHelper.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + AppsFlyer allows you to log and attribute installs originating + from cross promotion campaigns of your existing apps. + Afterwards, you can optimize on your cross-promotion traffic to get even better results. + */ +@interface AppsFlyerCrossPromotionHelper : NSObject + +/** + To log an impression use the following API call. + Make sure to use the promoted App ID as it appears within the AppsFlyer dashboard. + + @param appID Promoted App ID + @param campaign A campaign name + @param parameters Additional params like `@{@"af_sub1": @"val", @"custom_param": @"val2" }` +*/ ++ (void)logCrossPromoteImpression:(nonnull NSString *)appID + campaign:(nullable NSString *)campaign + parameters:(nullable NSDictionary *)parameters; + +/** + iOS allows you to utilize the StoreKit component to open + the App Store while remaining in the context of your app. + More details at https://support.appsflyer.com/hc/en-us/articles/115004481946-Cross-Promotion-Tracking#tracking-cross-promotion-impressions + + @param appID Promoted App ID + @param campaign A campaign name + @param parameters Additional params like `@{@"af_sub1": @"val", @"custom_param": @"val2" }` + @param openStoreBlock Contains promoted `clickURL` + */ ++ (void)logAndOpenStore:(nonnull NSString *)appID + campaign:(nullable NSString *)campaign + parameters:(nullable NSDictionary *)parameters + openStore:(void (^)(NSURLSession *urlSession, NSURL *clickURL))openStoreBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h new file mode 100644 index 000000000..f099aceb9 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h @@ -0,0 +1,36 @@ +// +// AFSDKDeeplink.h +// AppsFlyerLib +// +// Created by Andrii Hahan on 20.08.2020. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(DeepLink) +@interface AppsFlyerDeepLink : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (readonly, nonnull) NSDictionary *clickEvent; +@property (readonly, nullable) NSString *deeplinkValue; +@property (readonly, nullable) NSString *matchType; +@property (readonly, nullable) NSString *clickHTTPReferrer; +@property (readonly, nullable) NSString *mediaSource; +@property (readonly, nullable) NSString *campaign; +@property (readonly, nullable) NSString *campaignId; +@property (readonly, nullable) NSString *afSub1; +@property (readonly, nullable) NSString *afSub2; +@property (readonly, nullable) NSString *afSub3; +@property (readonly, nullable) NSString *afSub4; +@property (readonly, nullable) NSString *afSub5; +@property (readonly) BOOL isDeferred; + +- (NSString *)toString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h new file mode 100644 index 000000000..50d41d71e --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h @@ -0,0 +1,29 @@ +// +// AFSDKDeeplinkResult.h +// AppsFlyerLib +// +// Created by Andrii Hahan on 20.08.2020. +// + +#import + +@class AppsFlyerDeepLink; + +typedef NS_CLOSED_ENUM(NSUInteger, AFSDKDeepLinkResultStatus) { + AFSDKDeepLinkResultStatusNotFound, + AFSDKDeepLinkResultStatusFound, + AFSDKDeepLinkResultStatusFailure, +} NS_SWIFT_NAME(DeepLinkResultStatus); + +NS_SWIFT_NAME(DeepLinkResult) +@interface AppsFlyerDeepLinkResult : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property(readonly) AFSDKDeepLinkResultStatus status; + +@property(readonly, nullable) AppsFlyerDeepLink *deepLink; +@property(readonly, nullable) NSError *error; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h new file mode 100644 index 000000000..4155b266d --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h @@ -0,0 +1,618 @@ +#if 0 +#elif defined(__arm64__) && __arm64__ +// Generated by Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +#ifndef APPSFLYERLIB_SWIFT_H +#define APPSFLYERLIB_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="AppsFlyerLib",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#elif defined(__x86_64__) && __x86_64__ +// Generated by Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +#ifndef APPSFLYERLIB_SWIFT_H +#define APPSFLYERLIB_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="AppsFlyerLib",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#else +#error unsupported Swift architecture +#endif diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib.h new file mode 100644 index 000000000..182fe6e91 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib.h @@ -0,0 +1,743 @@ +// +// AppsFlyerLib.h +// AppsFlyerLib +// +// AppsFlyer iOS SDK 6.15.1 (211) +// Copyright (c) 2012-2023 AppsFlyer Ltd. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +// In app event names constants +#define AFEventLevelAchieved @"af_level_achieved" +#define AFEventAddPaymentInfo @"af_add_payment_info" +#define AFEventAddToCart @"af_add_to_cart" +#define AFEventAddToWishlist @"af_add_to_wishlist" +#define AFEventCompleteRegistration @"af_complete_registration" +#define AFEventTutorial_completion @"af_tutorial_completion" +#define AFEventInitiatedCheckout @"af_initiated_checkout" +#define AFEventPurchase @"af_purchase" +#define AFEventRate @"af_rate" +#define AFEventSearch @"af_search" +#define AFEventSpentCredits @"af_spent_credits" +#define AFEventAchievementUnlocked @"af_achievement_unlocked" +#define AFEventContentView @"af_content_view" +#define AFEventListView @"af_list_view" +#define AFEventTravelBooking @"af_travel_booking" +#define AFEventShare @"af_share" +#define AFEventInvite @"af_invite" +#define AFEventLogin @"af_login" +#define AFEventReEngage @"af_re_engage" +#define AFEventUpdate @"af_update" +#define AFEventOpenedFromPushNotification @"af_opened_from_push_notification" +#define AFEventLocation @"af_location_coordinates" +#define AFEventCustomerSegment @"af_customer_segment" + +#define AFEventSubscribe @"af_subscribe" +#define AFEventStartTrial @"af_start_trial" +#define AFEventAdClick @"af_ad_click" +#define AFEventAdView @"af_ad_view" + +// In app event parameter names +#define AFEventParamContent @"af_content" +#define AFEventParamAchievementId @"af_achievement_id" +#define AFEventParamLevel @"af_level" +#define AFEventParamScore @"af_score" +#define AFEventParamSuccess @"af_success" +#define AFEventParamPrice @"af_price" +#define AFEventParamContentType @"af_content_type" +#define AFEventParamContentId @"af_content_id" +#define AFEventParamContentList @"af_content_list" +#define AFEventParamCurrency @"af_currency" +#define AFEventParamQuantity @"af_quantity" +#define AFEventParamRegistrationMethod @"af_registration_method" +#define AFEventParamPaymentInfoAvailable @"af_payment_info_available" +#define AFEventParamMaxRatingValue @"af_max_rating_value" +#define AFEventParamRatingValue @"af_rating_value" +#define AFEventParamSearchString @"af_search_string" +#define AFEventParamDateA @"af_date_a" +#define AFEventParamDateB @"af_date_b" +#define AFEventParamDestinationA @"af_destination_a" +#define AFEventParamDestinationB @"af_destination_b" +#define AFEventParamDescription @"af_description" +#define AFEventParamClass @"af_class" +#define AFEventParamEventStart @"af_event_start" +#define AFEventParamEventEnd @"af_event_end" +#define AFEventParamLat @"af_lat" +#define AFEventParamLong @"af_long" +#define AFEventParamCustomerUserId @"af_customer_user_id" +#define AFEventParamValidated @"af_validated" +#define AFEventParamRevenue @"af_revenue" +#define AFEventProjectedParamRevenue @"af_projected_revenue" +#define AFEventParamReceiptId @"af_receipt_id" +#define AFEventParamTutorialId @"af_tutorial_id" +#define AFEventParamVirtualCurrencyName @"af_virtual_currency_name" +#define AFEventParamDeepLink @"af_deep_link" +#define AFEventParamOldVersion @"af_old_version" +#define AFEventParamNewVersion @"af_new_version" +#define AFEventParamReviewText @"af_review_text" +#define AFEventParamCouponCode @"af_coupon_code" +#define AFEventParamOrderId @"af_order_id" +#define AFEventParam1 @"af_param_1" +#define AFEventParam2 @"af_param_2" +#define AFEventParam3 @"af_param_3" +#define AFEventParam4 @"af_param_4" +#define AFEventParam5 @"af_param_5" +#define AFEventParam6 @"af_param_6" +#define AFEventParam7 @"af_param_7" +#define AFEventParam8 @"af_param_8" +#define AFEventParam9 @"af_param_9" +#define AFEventParam10 @"af_param_10" +#define AFEventParamTouch @"af_touch_obj" + +#define AFEventParamDepartingDepartureDate @"af_departing_departure_date" +#define AFEventParamReturningDepartureDate @"af_returning_departure_date" +#define AFEventParamDestinationList @"af_destination_list" //array of string +#define AFEventParamCity @"af_city" +#define AFEventParamRegion @"af_region" +#define AFEventParamCountry @"af_country" + + +#define AFEventParamDepartingArrivalDate @"af_departing_arrival_date" +#define AFEventParamReturningArrivalDate @"af_returning_arrival_date" +#define AFEventParamSuggestedDestinations @"af_suggested_destinations" //array of string +#define AFEventParamTravelStart @"af_travel_start" +#define AFEventParamTravelEnd @"af_travel_end" +#define AFEventParamNumAdults @"af_num_adults" +#define AFEventParamNumChildren @"af_num_children" +#define AFEventParamNumInfants @"af_num_infants" +#define AFEventParamSuggestedHotels @"af_suggested_hotels" //array of string + +#define AFEventParamUserScore @"af_user_score" +#define AFEventParamHotelScore @"af_hotel_score" +#define AFEventParamPurchaseCurrency @"af_purchase_currency" + +#define AFEventParamPreferredStarRatings @"af_preferred_star_ratings" //array of int (basically a tuple (min,max) but we'll use array of int and instruct the developer to use two values) + +#define AFEventParamPreferredPriceRange @"af_preferred_price_range" //array of int (basically a tuple (min,max) but we'll use array of int and instruct the developer to use two values) +#define AFEventParamPreferredNeighborhoods @"af_preferred_neighborhoods" //array of string +#define AFEventParamPreferredNumStops @"af_preferred_num_stops" + +/// Mail hashing type +typedef enum { + /// None + EmailCryptTypeNone = 0, + /// SHA256 + EmailCryptTypeSHA256 = 3 +} EmailCryptType; + +typedef NS_CLOSED_ENUM(NSInteger, AFSDKPlugin) { + AFSDKPluginIOSNative, + AFSDKPluginUnity, + AFSDKPluginFlutter, + AFSDKPluginReactNative, + AFSDKPluginAdobeAir, + AFSDKPluginAdobeMobile, + AFSDKPluginCocos2dx, + AFSDKPluginCordova, + AFSDKPluginMparticle, + AFSDKPluginNativeScript, + AFSDKPluginExpo, + AFSDKPluginUnreal, + AFSDKPluginXamarin, + AFSDKPluginCapacitor, + AFSDKPluginSegment, + AFSDKPluginAdobeSwiftAEP +} NS_SWIFT_NAME(Plugin); + + +NS_SWIFT_NAME(DeepLinkDelegate) +@protocol AppsFlyerDeepLinkDelegate + +@optional +- (void)didResolveDeepLink:(AppsFlyerDeepLinkResult *_Nonnull)result; + +@end + +/** + Conform and subscribe to this protocol to allow getting data about conversion and + install attribution + */ +@protocol AppsFlyerLibDelegate + +/** + `conversionInfo` contains information about install. + Organic/non-organic, etc. + @param conversionInfo May contain null values for some keys. Please handle this case. + */ +- (void)onConversionDataSuccess:(NSDictionary *)conversionInfo; + +/** + Any errors that occurred during the conversion request. + */ +- (void)onConversionDataFail:(NSError *)error; + +@optional + +/** + `attributionData` contains information about OneLink, deeplink. + */ +- (void)onAppOpenAttribution:(NSDictionary *)attributionData; + +/** + Any errors that occurred during the attribution request. + */ +- (void)onAppOpenAttributionFailure:(NSError *)error; + +/** + @abstract Sets the HTTP header fields of the ESP resolving to the given + dictionary. + @discussion This method replaces all header fields that may have + existed before this method ESP resolving call. + To keep default SDK behavior - return nil; + */ +- (NSDictionary * _Nullable)allHTTPHeaderFieldsForResolveDeepLinkURL:(NSURL *)URL; + +@end + +/** + You can log installs, app updates, sessions and additional in-app events + (including in-app purchases, game levels, etc.) + to evaluate ROI and user engagement. + The iOS SDK is compatible with all iOS/tvOS devices with iOS version 7 and above. + + @see [SDK Integration Validator](https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS) + for more information. + + */ +@interface AppsFlyerLib : NSObject + +/** + Gets the singleton instance of the AppsFlyerLib class, creating it if + necessary. + + @return The singleton instance of AppsFlyerLib. + */ ++ (AppsFlyerLib *)shared; + + +- (void)setUpInteroperabilityObject:(id)object; + +/** + In case you use your own user ID in your app, you can set this property to that ID. + Enables you to cross-reference your own unique ID with AppsFlyer’s unique ID and the other devices’ IDs + */ +@property(nonatomic, strong, nullable) NSString * customerUserID; + +/** + In case you use custom data and you want to receive it in the raw reports. + + @see [Setting additional custom data](https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS#setting-additional-custom-data) for more information. + */ +@property(nonatomic, strong, nullable, setter = setAdditionalData:) NSDictionary * customData; + +/** + Use this property to set your AppsFlyer's dev key + */ +@property(nonatomic, strong) NSString * appsFlyerDevKey; + +/** + Use this property to set your app's Apple ID(taken from the app's page on iTunes Connect) + */ +@property(nonatomic, strong) NSString * appleAppID; + +#ifndef AFSDK_NO_IDFA +/** + AppsFlyer SDK collect Apple's `advertisingIdentifier` if the `AdSupport.framework` included in the SDK. + You can disable this behavior by setting the following property to YES +*/ +@property(nonatomic) BOOL disableAdvertisingIdentifier; + +@property(nonatomic, strong, readonly) NSString *advertisingIdentifier; + +/** + Waits for request user authorization to access app-related data + */ +- (void)waitForATTUserAuthorizationWithTimeoutInterval:(NSTimeInterval)timeoutInterval +NS_SWIFT_NAME(waitForATTUserAuthorization(timeoutInterval:)); + +#endif + +@property(nonatomic) BOOL disableSKAdNetwork; + +/** + In case of in app purchase events, you can set the currency code your user has purchased with. + The currency code is a 3 letter code according to ISO standards + + Objective-C: + +
+ [[AppsFlyerLib shared] setCurrencyCode:@"USD"];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().currencyCode = "USD"
+ 
+ */ +@property(nonatomic, strong, nullable) NSString *currencyCode; + +/** + Prints SDK messages to the console log. This property should only be used in `DEBUG` mode. + The default value is `NO` + */ +@property(nonatomic) BOOL isDebug; + +/** + Set this flag to `YES`, to collect the current device name(e.g. "My iPhone"). Default value is `NO` + */ +@property(nonatomic) BOOL shouldCollectDeviceName; + +/** + Set your `OneLink ID` from OneLink configuration. Used in User Invites to generate a OneLink. + */ +@property(nonatomic, strong, nullable, setter = setAppInviteOneLink:) NSString * appInviteOneLinkID; + +/** + Opt-out logging for specific user + */ +@property(atomic) BOOL anonymizeUser; + +/** + Opt-out for Apple Search Ads attributions + */ +@property(atomic) BOOL disableCollectASA; + +/** + Disable Apple Ads Attribution API +[AAAtribution attributionTokenWithError:] + */ +@property(nonatomic) BOOL disableAppleAdsAttribution; + +/** + AppsFlyer delegate. See `AppsFlyerLibDelegate` + */ +@property(weak, nonatomic) id delegate; + +@property(weak, nonatomic) id deepLinkDelegate; + +/** + In app purchase receipt validation Apple environment(production or sandbox). The default value is NO + */ +@property(nonatomic) BOOL useReceiptValidationSandbox; + +/** + Set this flag to test uninstall on Apple environment(production or sandbox). The default value is NO + */ +@property(nonatomic) BOOL useUninstallSandbox; + +/** + For advertisers who wrap OneLink within another Universal Link. + An advertiser will be able to deeplink from a OneLink wrapped within another Universal Link and also log this retargeting conversion. + + Objective-C: + +
+ [[AppsFlyerLib shared] setResolveDeepLinkURLs:@[@"domain.com", @"subdomain.domain.com"]];
+ 
+ */ +@property(nonatomic, nullable, copy) NSArray *resolveDeepLinkURLs; + +/** + For advertisers who use vanity OneLinks. + + Objective-C: + +
+ [[AppsFlyerLib shared] oneLinkCustomDomains:@[@"domain.com", @"subdomain.domain.com"]];
+ 
+ */ +@property(nonatomic, nullable, copy) NSArray *oneLinkCustomDomains; + +/* + * Set phone number for each `start` event. `phoneNumber` will be sent as SHA256 string + */ +@property(nonatomic, nullable, copy) NSString *phoneNumber; + +- (NSString *)phoneNumber UNAVAILABLE_ATTRIBUTE; + +/** + To disable app's vendor identifier(IDFV), set disableIDFVCollection to true + */ +@property(nonatomic) BOOL disableIDFVCollection; + +/** + Set the language of the device. The data will be displayed in Raw Data Reports + Objective-C: + +
+ [[AppsFlyerLib shared] setCurrentDeviceLanguage:@"EN"]
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().currentDeviceLanguage("EN")
+ 
+ */ +@property(nonatomic, nullable, copy) NSString *currentDeviceLanguage; + +/** + Internal API. Please don't use. + */ +- (void)setPluginInfoWith:(AFSDKPlugin)plugin + pluginVersion:(NSString *)version + additionalParams:(NSDictionary * _Nullable)additionalParams +NS_SWIFT_NAME(setPluginInfo(plugin:version:additionalParams:)); + +/** + Enable the collection of Facebook Deferred AppLinks + Requires Facebook SDK and Facebook app on target/client device. + This API must be invoked prior to initializing the AppsFlyer SDK in order to function properly. + + Objective-C: + +
+ [[AppsFlyerLib shared] enableFacebookDeferredApplinksWithClass:[FBSDKAppLinkUtility class]]
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().enableFacebookDeferredApplinks(with: FBSDKAppLinkUtility.self)
+ 
+ + @param facebookAppLinkUtilityClass requeries method call `[FBSDKAppLinkUtility class]` as param. + */ +- (void)enableFacebookDeferredApplinksWithClass:(Class _Nullable)facebookAppLinkUtilityClass; + +/** + Use this to send the user's emails + + @param userEmails The list of strings that hold mails + @param type Hash algoritm + */ +- (void)setUserEmails:(NSArray * _Nullable)userEmails withCryptType:(EmailCryptType)type; + +/** + Start SDK session + Add the following method at the `applicationDidBecomeActive` in AppDelegate class + */ +- (void)start; + +- (void)startWithCompletionHandler:(void (^ _Nullable)(NSDictionary * _Nullable dictionary, NSError * _Nullable error))completionHandler; + +/** + Use this method to log an events with multiple values. See AppsFlyer's documentation for details. + + Objective-C: + +
+ [[AppsFlyerLib shared] logEvent:AFEventPurchase
+        withValues: @{AFEventParamRevenue  : @200,
+                      AFEventParamCurrency : @"USD",
+                      AFEventParamQuantity : @2,
+                      AFEventParamContentId: @"092",
+                      AFEventParamReceiptId: @"9277"}];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().logEvent(AFEventPurchase,
+        withValues: [AFEventParamRevenue  : "1200",
+                     AFEventParamContent  : "shoes",
+                     AFEventParamContentId: "123"])
+ 
+ + @param eventName Contains name of event that could be provided from predefined constants in `AppsFlyerLib.h` + @param values Contains dictionary of values for handling by backend + */ +- (void)logEvent:(NSString *)eventName withValues:(NSDictionary * _Nullable)values; + +- (void)logEventWithEventName:(NSString *)eventName + eventValues:(NSDictionary * _Nullable)eventValues + completionHandler:(void (^ _Nullable)(NSDictionary * _Nullable dictionary, NSError * _Nullable error))completionHandler +NS_SWIFT_NAME(logEvent(name:values:completionHandler:)); + +/** + To log and validate in app purchases you can call this method from the completeTransaction: method on + your `SKPaymentTransactionObserver`. + + @param productIdentifier The product identifier + @param price The product price + @param currency The product currency + @param transactionId The purchase transaction Id + @param params The additional param, which you want to receive it in the raw reports + @param successBlock The success callback + @param failedBlock The failure callback + */ +- (void)validateAndLogInAppPurchase:(NSString * _Nullable)productIdentifier + price:(NSString * _Nullable)price + currency:(NSString * _Nullable)currency + transactionId:(NSString * _Nullable)transactionId + additionalParameters:(NSDictionary * _Nullable)params + success:(void (^ _Nullable)(NSDictionary * response))successBlock + failure:(void (^ _Nullable)(NSError * _Nullable error, id _Nullable reponse))failedBlock NS_AVAILABLE(10_7, 7_0); + +typedef void (^AFSDKValidateAndLogCompletion)(AFSDKValidateAndLogResult * _Nullable result); + +/** + To log and validate in app purchases you can call this method from the completeTransaction: method on + your `SKPaymentTransactionObserver`. + + @param details The product details + @param extraEventValues The additional param, which you want to receive it in the raw reports + @param completionHandler The callback + */ +- (void)validateAndLogInAppPurchase:(AFSDKPurchaseDetails *)details + extraEventValues:(NSDictionary * _Nullable)extraEventValues + completionHandler:(AFSDKValidateAndLogCompletion)completionHandler NS_AVAILABLE(10_7, 7_0); + +/** + An API to provide the data from the impression payload to AdRevenue. + + @param adRevenueData object used to hold all mandatory parameters of AdRevenue event. + @param additionalParameters non-mandatory dictionary which can include pre-defined keys (kAppsFlyerAdRevenueCountry, etc) + */ +- (void)logAdRevenue:(AFAdRevenueData *)adRevenueData additionalParameters:(NSDictionary * _Nullable)additionalParameters; + +/** + To log location for geo-fencing. Does the same as code below. + +
+ AppsFlyerLib.shared().logEvent(AFEventLocation, withValues: [AFEventParamLong:longitude, AFEventParamLat:latitude])
+ 
+ + @param longitude The location longitude + @param latitude The location latitude + */ +- (void)logLocation:(double)longitude latitude:(double)latitude NS_SWIFT_NAME(logLocation(longitude:latitude:)); + +/** + This method returns AppsFlyer's internal id(unique for your app) + + @return Internal AppsFlyer Id + */ +- (NSString *)getAppsFlyerUID; + +/** + In case you want to log deep linking. Does the same as `-handleOpenURL:sourceApplication:withAnnotation`. + + @warning Preferred to use `-handleOpenURL:sourceApplication:withAnnotation`. + + @param url The URL that was passed to your AppDelegate. + @param sourceApplication The sourceApplication that passed to your AppDelegate. + */ +- (void)handleOpenURL:(NSURL * _Nullable)url sourceApplication:(NSString * _Nullable)sourceApplication API_UNAVAILABLE(macos); + +/** + In case you want to log deep linking. + Call this method from inside your AppDelegate `-application:openURL:sourceApplication:annotation:` + + @param url The URL that was passed to your AppDelegate. + @param sourceApplication The sourceApplication that passed to your AppDelegate. + @param annotation The annotation that passed to your app delegate. + */ +- (void)handleOpenURL:(NSURL * _Nullable)url + sourceApplication:(NSString * _Nullable)sourceApplication + withAnnotation:(id _Nullable)annotation API_UNAVAILABLE(macos); + +/** + Call this method from inside of your AppDelegate `-application:openURL:options:` method. + This method is functionally the same as calling the AppsFlyer method + `-handleOpenURL:sourceApplication:withAnnotation`. + + @param url The URL that was passed to your app delegate + @param options The options dictionary that was passed to your AppDelegate. + */ +- (void)handleOpenUrl:(NSURL * _Nullable)url options:(NSDictionary * _Nullable)options API_UNAVAILABLE(macos); + +/** + Allow AppsFlyer to handle restoration from an NSUserActivity. + Use this method to log deep links with OneLink. + + @param userActivity The NSUserActivity that caused the app to be opened. + */ +- (BOOL)continueUserActivity:(NSUserActivity * _Nullable)userActivity + restorationHandler:(void (^ _Nullable)(NSArray * _Nullable))restorationHandler NS_AVAILABLE_IOS(9_0) API_UNAVAILABLE(macos); + +/** + Enable AppsFlyer to handle a push notification. + + @see [Learn more here](https://support.appsflyer.com/hc/en-us/articles/207364076-Measuring-Push-Notification-Re-Engagement-Campaigns) + + @warning To make it work - set data, related to AppsFlyer under key @"af". + + @param pushPayload The `userInfo` from received remote notification. One of root keys should be @"af". + */ +- (void)handlePushNotification:(NSDictionary * _Nullable)pushPayload; + + +/** + Register uninstall - you should register for remote notification and provide AppsFlyer the push device token. + + @param deviceToken The `deviceToken` from `-application:didRegisterForRemoteNotificationsWithDeviceToken:` + */ +- (void)registerUninstall:(NSData * _Nullable)deviceToken; + +/** + Get SDK version. + + @return The AppsFlyer SDK version info. + */ +- (NSString *)getSDKVersion; + +/** + This is for internal use. + */ +- (void)remoteDebuggingCallWithData:(NSString *)data; + +/** + This is for internal use. + */ +- (void)remoteDebuggingCallV2WithData:(NSString *)dataAsString; + +/** + Used to force the trigger `onAppOpenAttribution` delegate. + Notice, re-engagement, session and launch won't be counted. + Only for OneLink/UniversalLink/Deeplink resolving. + + @param URL The param to resolve into -[AppsFlyerLibDelegate onAppOpenAttribution:] + */ +- (void)performOnAppAttributionWithURL:(NSURL * _Nullable)URL; + +/** + @brief This property accepts a string value representing the host name for all endpoints. + Can be used to Zero rate your application’s data usage. Contact your CSM for more information. + + @warning To use `default` SDK endpoint – set value to `nil`. + + Objective-C: + +
+ [[AppsFlyerLib shared] setHost:@"example.com"];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().host = "example.com"
+ 
+ */ +@property(nonatomic, strong, readonly) NSString *host; + +/** + * This function set the host name and prefix host name for all the endpoints + **/ +- (void)setHost:(NSString *)host withHostPrefix:(NSString *)hostPrefix; + +/** + * This property accepts a string value representing the prefix host name for all endpoints. + * for example "test" prefix with default host name will have the address "host.appsflyer.com" + */ +@property(nonatomic, strong, readonly) NSString *hostPrefix; + +/** + This property is responsible for timeout between sessions in seconds. + Default value is 5 seconds. + */ +@property(atomic) NSUInteger minTimeBetweenSessions; + +/** + API to shut down all SDK activities. + + @warning This will disable all requests from AppsFlyer SDK. + */ +@property(atomic) BOOL isStopped; + +/** + API to set manually Facebook deferred app link + */ +@property(nonatomic, nullable, copy) NSURL *facebookDeferredAppLink; + +/** + Block an events from being shared with ad networks and other 3rd party integrations + Must only include letters/digits or underscore, maximum length: 45 + */ +@property(nonatomic, nullable, copy) NSArray *sharingFilter DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); + +@property(nonatomic) NSUInteger deepLinkTimeout; + +/** + Block an events from being shared with any partner + This method overwrite -[AppsFlyerLib setSharingFilter:] + */ +- (void)setSharingFilterForAllPartners DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); + +/** + Block an events from being shared with ad networks and other 3rd party integrations + Must only include letters/digits or underscore, maximum length: 45 + + The sharing filter is cleared in case if `nil` or empty array passed as a parameter. + "all" keyword sets sharing filter for ALL partners, it is case insencitive and has highest priority + if passed along with another values. For example, if ["all", "examplePartner1_int", "examplePartner2_int" ] passed, + the sharing filter will be set for ALL partners. + */ +- (void)setSharingFilterForPartners:(NSArray * _Nullable)sharingFilter; + + +/** + Sets or updates the user consent data related to GDPR and DMA regulations for advertising and data usage + purposes within the application. This method must be invoked with the user's current consent status each + time the app starts or whenever there is a change in the user's consent preferences. + + Note that this method does not persist the consent data across app sessions; it only applies for the + duration of the current app session. If you wish to stop providing the consent data, you should + cease calling this method. + + @param consent an instance of AppsFlyerConsent that encapsulates the user's consent information. + */ +- (void)setConsentData:(AppsFlyerConsent *)consent; + +/** + Enable the SDK to collect and send TCF data + + @param shouldCollectConsentData indicates if the TCF data collection is enabled. + */ +- (void)enableTCFDataCollection:(BOOL)shouldCollectConsentData; + +/** + Validate if URL contains certain string and append quiery + parameters to deeplink URL. In case if URL does not contain user-defined string, + parameters are not appended to the url. + + @param containsString string to check in URL. + @param parameters NSDictionary, which containins parameters to append to the deeplink url after it passed validation. + */ +- (void)appendParametersToDeepLinkingURLWithString:(NSString *)containsString + parameters:(NSDictionary *)parameters +NS_SWIFT_NAME(appendParametersToDeeplinkURL(contains:parameters:)); + +/** + Adds array of keys, which are used to compose key path + to resolve deeplink from push notification payload `userInfo`. + + @param deepLinkPath an array of strings which contains keys to search for deeplink in payload. + */ +- (void)addPushNotificationDeepLinkPath:(NSArray *)deepLinkPath; + +/** + * Allows sending custom data for partner integration purposes. + * + * @param partnerId ID of the partner (usually has "_int" suffix) + * @param partnerInfo customer data, depends on the integration nature with specific partner + */ + +- (void)setPartnerDataWithPartnerId:(NSString * _Nullable)partnerId partnerInfo:(NSDictionary * _Nullable)partnerInfo +NS_SWIFT_NAME(setPartnerData(partnerId:partnerInfo:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h new file mode 100644 index 000000000..d3ec8f4e4 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h @@ -0,0 +1,52 @@ +// +// LinkGenerator.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Payload container for the `generateInviteUrlWithLinkGenerator:completionHandler:` from `AppsFlyerShareInviteHelper` + */ +@interface AppsFlyerLinkGenerator : NSObject + +/// Instance initialization is not allowed. Use generated instance +/// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` +- (instancetype)init NS_UNAVAILABLE; +/// Instance initialization is not allowed. Use generated instance +/// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` ++ (instancetype)new NS_UNAVAILABLE; + +@property(nonatomic, nullable, copy) NSString *brandDomain; + +/// The channel through which the invite was sent (e.g. Facebook/Gmail/etc.). Usage: Recommended +- (void)setChannel :(nonnull NSString *)channel; +/// ReferrerCustomerId setter +- (void)setReferrerCustomerId:(nonnull NSString *)referrerCustomerId; +/// A campaign name. Usage: Optional +- (void)setCampaign :(nonnull NSString *)campaign; +/// ReferrerUID setter +- (void)setReferrerUID :(nonnull NSString *)referrerUID; +/// Referrer name +- (void)setReferrerName :(nonnull NSString *)referrerName; +/// The URL to referrer user avatar. Usage: Optional +- (void)setReferrerImageURL :(nonnull NSString *)referrerImageURL; +/// AppleAppID +- (void)setAppleAppID :(nonnull NSString *)appleAppID; +/// Deeplink path +- (void)setDeeplinkPath :(nonnull NSString *)deeplinkPath; +/// Base deeplink path +- (void)setBaseDeeplink :(nonnull NSString *)baseDeeplink; +/// A single key value custom parameter. Usage: Optional +- (void)addParameterValue :(nonnull NSString *)value forKey:(NSString *)key; +/// Multiple key value custom parameters. Usage: Optional +- (void)addParameters :(nonnull NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h new file mode 100644 index 000000000..f55dbf9d0 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h @@ -0,0 +1,35 @@ +// +// ShareInviteHelper.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import +#import + +/** + AppsFlyerShareInviteHelper + */ +@interface AppsFlyerShareInviteHelper : NSObject + +NS_ASSUME_NONNULL_BEGIN + +/** + * The AppsFlyerShareInviteHelper class builds the invite URL according to various setter methods + * which allow passing on additional information on the click. + * This information is available through `onConversionDataReceived:` when the user accepts the invite and installs the app. + * In addition, campaign and channel parameters are visible within the AppsFlyer Dashboard. + */ ++ (void)generateInviteUrlWithLinkGenerator:(AppsFlyerLinkGenerator *(^)(AppsFlyerLinkGenerator *generator))generatorCreator completionHandler:(void (^)(NSURL *_Nullable url))completionHandler; + +/** + * It is recommended to generate an in-app event after the invite is sent to log the invites from the senders' perspective. + * This enables you to find the users that tend most to invite friends, and the media sources that get you these users. + */ ++ (void)logInvite:(nullable NSString *)channel parameters:(nullable NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Info.plist b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Info.plist new file mode 100644 index 000000000..9aae406a9 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Info.plist differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.abi.json b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.abi.json new file mode 100644 index 000000000..4d7c1be56 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.abi.json @@ -0,0 +1,458 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "TopLevel", + "printedName": "TopLevel", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKConnectorCommunication", + "printedName": "AFSDKConnectorCommunication", + "children": [ + { + "kind": "Function", + "name": "setPurchaseDataSendingDelegate", + "printedName": "setPurchaseDataSendingDelegate(delegate:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AppsFlyerLib.AFSDKPurchaseDataSendingDelegate", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate" + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication(im)setPurchaseDataSendingDelegateWithDelegate:", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP30setPurchaseDataSendingDelegate8delegateyAA013AFSDKPurchasehiJ0_p_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKConnectorCommunication>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "setPurchaseDataSendingDelegateWithDelegate:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AFSDKPurchaseDataSendingDelegate", + "children": [ + { + "kind": "Function", + "name": "sendDecryptReceiptRequest", + "printedName": "sendDecryptReceiptRequest(environment:receipt:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP25sendDecryptReceiptRequest11environment7receipt17completionHandlerySS_SSySDys11AnyHashableVypGSg_s5Error_pSgtctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendARSRequest", + "printedName": "sendARSRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendARSRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP14sendARSRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendARSRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendVIAPRequest", + "printedName": "sendVIAPRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendVIAPRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP15sendVIAPRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendVIAPRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendCachedPurchaseConnectorEvents", + "printedName": "sendCachedPurchaseConnectorEvents(completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "() -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP33sendCachedPurchaseConnectorEvents17completionHandleryyyc_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface new file mode 100644 index 000000000..d917e48e2 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target arm64-apple-ios12.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftdoc b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftdoc new file mode 100644 index 000000000..b8c429029 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftdoc differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftinterface new file mode 100644 index 000000000..d917e48e2 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target arm64-apple-ios12.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.abi.json b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.abi.json new file mode 100644 index 000000000..4d7c1be56 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.abi.json @@ -0,0 +1,458 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "TopLevel", + "printedName": "TopLevel", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKConnectorCommunication", + "printedName": "AFSDKConnectorCommunication", + "children": [ + { + "kind": "Function", + "name": "setPurchaseDataSendingDelegate", + "printedName": "setPurchaseDataSendingDelegate(delegate:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AppsFlyerLib.AFSDKPurchaseDataSendingDelegate", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate" + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication(im)setPurchaseDataSendingDelegateWithDelegate:", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP30setPurchaseDataSendingDelegate8delegateyAA013AFSDKPurchasehiJ0_p_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKConnectorCommunication>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "setPurchaseDataSendingDelegateWithDelegate:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AFSDKPurchaseDataSendingDelegate", + "children": [ + { + "kind": "Function", + "name": "sendDecryptReceiptRequest", + "printedName": "sendDecryptReceiptRequest(environment:receipt:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP25sendDecryptReceiptRequest11environment7receipt17completionHandlerySS_SSySDys11AnyHashableVypGSg_s5Error_pSgtctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendARSRequest", + "printedName": "sendARSRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendARSRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP14sendARSRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendARSRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendVIAPRequest", + "printedName": "sendVIAPRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendVIAPRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP15sendVIAPRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendVIAPRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendCachedPurchaseConnectorEvents", + "printedName": "sendCachedPurchaseConnectorEvents(completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "() -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP33sendCachedPurchaseConnectorEvents17completionHandleryyyc_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface new file mode 100644 index 000000000..83c07c864 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target x86_64-apple-ios12.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftdoc b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftdoc new file mode 100644 index 000000000..3b8343425 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftdoc differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftinterface new file mode 100644 index 000000000..83c07c864 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target x86_64-apple-ios12.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/module.modulemap b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/module.modulemap new file mode 100644 index 000000000..abf5a4827 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/module.modulemap @@ -0,0 +1,11 @@ +framework module AppsFlyerLib { + umbrella header "AppsFlyerLib.h" + + export * + module * { export * } +} + +module AppsFlyerLib.Swift { + header "AppsFlyerLib-Swift.h" + requires objc +} diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/PrivacyInfo.xcprivacy b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..5da2889f7 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/PrivacyInfo.xcprivacy @@ -0,0 +1,73 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyTrackingDomains + + att-attr.whappsflyer.com + att-attr.appsflyer-cn.com + att-attr.hevents.appsflyer-cn.com + att-launches.whappsflyer.com + att-launches.appsflyer-cn.com + att-launches.hevents.appsflyer-cn.com + att-conversions.hevents.appsflyer-cn.com + att-conversions.appsflyer-cn.com + att-conversions.whappsflyer.com + att-dlsdk.hevents.appsflyer-cn.com + att-dlsdk.appsflyer-cn.com + att-dlsdk.whappsflyer.com + att-dlsdk.appsflyersdk.com + att-conversions.appsflyersdk.com + att-launches.appsflyersdk.com + att-attr.appsflyersdk.com + + NSPrivacyTracking + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + NSPrivacyAccessedAPITypeReasons + + C617.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + + + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeDirectory b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeDirectory new file mode 100644 index 000000000..dfc8d7a94 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeDirectory differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements new file mode 100644 index 000000000..dbf9d6144 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements-1 b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements-1 new file mode 100644 index 000000000..80bb8aa64 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements-1 differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeResources b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeResources new file mode 100644 index 000000000..195c989d7 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeResources @@ -0,0 +1,447 @@ + + + + + files + + Headers/AFAdRevenueData.h + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + Headers/AFSDKPurchaseDetails.h + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + Headers/AFSDKValidateAndLogResult.h + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + Headers/AppsFlyerConsent.h + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + Headers/AppsFlyerCrossPromotionHelper.h + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + Headers/AppsFlyerDeepLink.h + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + Headers/AppsFlyerDeepLinkResult.h + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + Headers/AppsFlyerLib-Swift.h + + FOoS78OxL9Z3oz6vB1wWn0PArv0= + + Headers/AppsFlyerLib.h + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + Headers/AppsFlyerLinkGenerator.h + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + Headers/AppsFlyerShareInviteHelper.h + + LysKB9CL90x+MWXOuuRPixmiVJo= + + Info.plist + + R1HkFJpIbi3chDScxpiSYpL4nMw= + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface + + i1iM4UdBHXU2lmDnJ/Czh5pfamI= + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + ex2JxlYuxQFqky3dqJcNjo7d+qo= + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftinterface + + i1iM4UdBHXU2lmDnJ/Czh5pfamI= + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftmodule + + TfS7gFh/R+l98E5e3VBcTi9V0Bw= + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface + + wn2IXmAG5n5kmUW9IvrqLMpExQs= + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + Jof2V2SbxYOsGiZHkRxK/hYz/Co= + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftinterface + + wn2IXmAG5n5kmUW9IvrqLMpExQs= + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftmodule + + shPGS7aB96GLhioV1kHCBMgFXus= + + Modules/module.modulemap + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + PrivacyInfo.xcprivacy + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + + files2 + + Headers/AFAdRevenueData.h + + hash + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + hash2 + + yrAHizSHbgcNNFLOfLoHWfaZGyJOlAoDsri7G5c7ZiA= + + + Headers/AFSDKPurchaseDetails.h + + hash + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + hash2 + + UNK4h738nezlAQq+5weKs6WQUIVzBoBZIFYlfSGbhRA= + + + Headers/AFSDKValidateAndLogResult.h + + hash + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + hash2 + + 8B0SIW1N+hhdxvGum5eyxLMW5e/T797WSXOd2i+irv4= + + + Headers/AppsFlyerConsent.h + + hash + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + hash2 + + VIYU7EyY+VmJF4vKtTem3hkI/fjGfSK7lYs2zq/9D4E= + + + Headers/AppsFlyerCrossPromotionHelper.h + + hash + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + hash2 + + YgrwrWx/ZFYjXh2t5ZHY6S0EZTroYfe5Nprl3alq+Ho= + + + Headers/AppsFlyerDeepLink.h + + hash + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + hash2 + + Z5nW/ynpNNV+krqJXqy1Gb+gdnruPFWutZYYX7hSt3I= + + + Headers/AppsFlyerDeepLinkResult.h + + hash + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + hash2 + + QsQGXKix5206DUBBUdDxfg5ykma/3V9MoHOxbz8NaOs= + + + Headers/AppsFlyerLib-Swift.h + + hash + + FOoS78OxL9Z3oz6vB1wWn0PArv0= + + hash2 + + s3bkkJu3kOu0GV4agwv8JVspCFp8hE12f3253m+G6T4= + + + Headers/AppsFlyerLib.h + + hash + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + hash2 + + SQsJwXrqFChMPzjRNaG0wtOfEV17jrTEH4mn18wQQDY= + + + Headers/AppsFlyerLinkGenerator.h + + hash + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + hash2 + + LskctIiGg0ZYNtNTDDkoSuDrB3xS58UmllXbrJDHg48= + + + Headers/AppsFlyerShareInviteHelper.h + + hash + + LysKB9CL90x+MWXOuuRPixmiVJo= + + hash2 + + PPGAG29u7O6ly0pq4HZsZxW1zcqL/viEKKBfZ6qzfdE= + + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface + + hash + + i1iM4UdBHXU2lmDnJ/Czh5pfamI= + + hash2 + + 4MD9uoatCkuOKH9AlR6foTTNYj+7TNc6quxamFMRTSo= + + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + hash + + ex2JxlYuxQFqky3dqJcNjo7d+qo= + + hash2 + + Gz+v10EwWzXwNQ+WpHCUSx6bcILFZ323RxNGAcPJ2cU= + + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftinterface + + hash + + i1iM4UdBHXU2lmDnJ/Czh5pfamI= + + hash2 + + 4MD9uoatCkuOKH9AlR6foTTNYj+7TNc6quxamFMRTSo= + + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-ios-simulator.swiftmodule + + hash + + TfS7gFh/R+l98E5e3VBcTi9V0Bw= + + hash2 + + lz2UIsN1MWeodrXz8Y1oXulkIwwSZSM2u1HgBjg0+cQ= + + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface + + hash + + wn2IXmAG5n5kmUW9IvrqLMpExQs= + + hash2 + + i7WPdwrs5dF/nuOzDt6Sb0RI/sYkAfzjItW5R21605M= + + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + hash + + Jof2V2SbxYOsGiZHkRxK/hYz/Co= + + hash2 + + j2GPaSZglQqvyzdUdahvuxDe8T3fLkm8aLul3md381s= + + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftinterface + + hash + + wn2IXmAG5n5kmUW9IvrqLMpExQs= + + hash2 + + i7WPdwrs5dF/nuOzDt6Sb0RI/sYkAfzjItW5R21605M= + + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-ios-simulator.swiftmodule + + hash + + shPGS7aB96GLhioV1kHCBMgFXus= + + hash2 + + zMBGGO3w1ky/RjqHqFjq8qM8oEHUL5FcW4miZv34ROU= + + + Modules/module.modulemap + + hash + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + hash2 + + uZJ3EIEDqGw+lkbzR0fNsIrgzthOwNUTrpyMAJQZFc8= + + + PrivacyInfo.xcprivacy + + hash + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + hash2 + + 6xKWyp82bfdpIC+qSEbhTjD40SP5BWjupWVxQg5FQkY= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeSignature b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/ios-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeSignature new file mode 100644 index 000000000..e69de29bb diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/AppsFlyerLib b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/AppsFlyerLib new file mode 120000 index 000000000..fd96586f8 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/AppsFlyerLib @@ -0,0 +1 @@ +Versions/Current/AppsFlyerLib \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Headers b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Headers new file mode 120000 index 000000000..a177d2a6b --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Modules b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Modules new file mode 120000 index 000000000..5736f3186 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Modules @@ -0,0 +1 @@ +Versions/Current/Modules \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Resources b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Resources new file mode 120000 index 000000000..953ee36f3 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/AppsFlyerLib b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/AppsFlyerLib new file mode 100644 index 000000000..9690db8bf Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/AppsFlyerLib differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFAdRevenueData.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFAdRevenueData.h new file mode 100644 index 000000000..2025468a3 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFAdRevenueData.h @@ -0,0 +1,68 @@ +// +// AFAdRevenueData.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 26/06/2024. +// + +typedef NS_CLOSED_ENUM(NSUInteger, AppsFlyerAdRevenueMediationNetworkType) { + AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob = 1, + AppsFlyerAdRevenueMediationNetworkTypeIronSource = 2, + AppsFlyerAdRevenueMediationNetworkTypeApplovinMax= 3, + AppsFlyerAdRevenueMediationNetworkTypeFyber = 4, + AppsFlyerAdRevenueMediationNetworkTypeAppodeal = 5, + AppsFlyerAdRevenueMediationNetworkTypeAdmost = 6, + AppsFlyerAdRevenueMediationNetworkTypeTopon = 7, + AppsFlyerAdRevenueMediationNetworkTypeTradplus = 8, + AppsFlyerAdRevenueMediationNetworkTypeYandex = 9, + AppsFlyerAdRevenueMediationNetworkTypeChartBoost = 10, + AppsFlyerAdRevenueMediationNetworkTypeUnity = 11, + AppsFlyerAdRevenueMediationNetworkTypeToponPte = 12, + AppsFlyerAdRevenueMediationNetworkTypeCustom = 13, + AppsFlyerAdRevenueMediationNetworkTypeDirectMonetization = 14 +} NS_SWIFT_NAME(MediationNetworkType); + +#define kAppsFlyerAdRevenueMonetizationNetwork @"monetization_network" +#define kAppsFlyerAdRevenueMediationNetwork @"mediation_network" +#define kAppsFlyerAdRevenueEventRevenue @"event_revenue" +#define kAppsFlyerAdRevenueEventRevenueCurrency @"event_revenue_currency" +#define kAppsFlyerAdRevenueCustomParameters @"custom_parameters" +#define kAFADRWrapperTypeGeneric @"adrevenue_sdk" + +//Pre-defined keys for non-mandatory dictionary + +//Code ISO 3166-1 format +#define kAppsFlyerAdRevenueCountry @"country" + +//ID of the ad unit for the impression +#define kAppsFlyerAdRevenueAdUnit @"ad_unit" + +//Format of the ad +#define kAppsFlyerAdRevenueAdType @"ad_type" + +//ID of the ad placement for the impression +#define kAppsFlyerAdRevenuePlacement @"placement" + + +@interface AFAdRevenueData : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (strong, nonnull, nonatomic) NSString *monetizationNetwork; +@property AppsFlyerAdRevenueMediationNetworkType mediationNetwork; +@property (strong, nonnull, nonatomic) NSString *currencyIso4217Code; +@property (strong, nonnull, nonatomic) NSNumber *eventRevenue; + +/** +* @param monetizationNetwork network which monetized the impression (@"facebook") +* @param mediationNetwork mediation source that mediated the monetization network for the impression (AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob) +* @param currencyIso4217Code reported impression’s revenue currency ISO 4217 format (@"USD") +* @param eventRevenue reported impression’s revenue (@(0.001994303)) +*/ +- (instancetype _Nonnull )initWithMonetizationNetwork:(NSString *_Nonnull)monetizationNetwork + mediationNetwork:(AppsFlyerAdRevenueMediationNetworkType)mediationNetwork + currencyIso4217Code:(NSString *_Nonnull)currencyIso4217Code + eventRevenue:(NSNumber *_Nonnull)eventRevenue; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFSDKPurchaseDetails.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFSDKPurchaseDetails.h new file mode 100644 index 000000000..89af8a702 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFSDKPurchaseDetails.h @@ -0,0 +1,23 @@ +// +// AFSDKPurchaseDetails.h +// AppsFlyerLib +// +// Created by Moris Gateno on 13/03/2024. +// + +@interface AFSDKPurchaseDetails : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (strong, nullable, nonatomic) NSString *productId; +@property (strong, nullable, nonatomic) NSString *price; +@property (strong, nullable, nonatomic) NSString *currency; +@property (strong, nullable, nonatomic) NSString *transactionId; + +- (instancetype _Nonnull )initWithProductId:(NSString *_Nullable)productId + price:(NSString *_Nullable)price + currency:(NSString *_Nullable)currency + transactionId:(NSString *_Nullable)transactionId; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFSDKValidateAndLogResult.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFSDKValidateAndLogResult.h new file mode 100644 index 000000000..94ef0f4c2 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AFSDKValidateAndLogResult.h @@ -0,0 +1,35 @@ +// +// AFSDKValidateAndLogResult.h +// AppsFlyerLib +// +// Created by Moris Gateno on 13/03/2024. +// + + +typedef NS_CLOSED_ENUM(NSUInteger, AFSDKValidateAndLogStatus) { + AFSDKValidateAndLogStatusSuccess, + AFSDKValidateAndLogStatusFailure, + AFSDKValidateAndLogStatusError +} NS_SWIFT_NAME(ValidateAndLogStatus); + +NS_SWIFT_NAME(ValidateAndLogResult) +@interface AFSDKValidateAndLogResult : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +- (instancetype _Nonnull )initWithStatus:(AFSDKValidateAndLogStatus)status + result:(NSDictionary *_Nullable)result + errorData:(NSDictionary *_Nullable)errorData + error:(NSError *_Nullable)error; + +@property(readonly) AFSDKValidateAndLogStatus status; +// Success case +@property(readonly, nullable) NSDictionary *result; +// Server 200 with validation failure +@property(readonly, nullable) NSDictionary *errorData; +// for the error case +@property(readonly, nullable) NSError *error; + +@end + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerConsent.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerConsent.h new file mode 100644 index 000000000..564912a92 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerConsent.h @@ -0,0 +1,26 @@ +// +// AppsFlyerConsent.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 14/01/2024. +// +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AppsFlyerConsent : NSObject + +@property (nonatomic, readonly, assign) BOOL isUserSubjectToGDPR; +@property (nonatomic, readonly, assign) BOOL hasConsentForDataUsage; +@property (nonatomic, readonly, assign) BOOL hasConsentForAdsPersonalization; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (instancetype)initForGDPRUserWithHasConsentForDataUsage:(BOOL)hasConsentForDataUsage + hasConsentForAdsPersonalization:(BOOL)hasConsentForAdsPersonalization NS_DESIGNATED_INITIALIZER; +- (instancetype)initNonGDPRUser NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerCrossPromotionHelper.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerCrossPromotionHelper.h new file mode 100644 index 000000000..483f8f863 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerCrossPromotionHelper.h @@ -0,0 +1,49 @@ +// +// CrossPromotionHelper.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + AppsFlyer allows you to log and attribute installs originating + from cross promotion campaigns of your existing apps. + Afterwards, you can optimize on your cross-promotion traffic to get even better results. + */ +@interface AppsFlyerCrossPromotionHelper : NSObject + +/** + To log an impression use the following API call. + Make sure to use the promoted App ID as it appears within the AppsFlyer dashboard. + + @param appID Promoted App ID + @param campaign A campaign name + @param parameters Additional params like `@{@"af_sub1": @"val", @"custom_param": @"val2" }` +*/ ++ (void)logCrossPromoteImpression:(nonnull NSString *)appID + campaign:(nullable NSString *)campaign + parameters:(nullable NSDictionary *)parameters; + +/** + iOS allows you to utilize the StoreKit component to open + the App Store while remaining in the context of your app. + More details at https://support.appsflyer.com/hc/en-us/articles/115004481946-Cross-Promotion-Tracking#tracking-cross-promotion-impressions + + @param appID Promoted App ID + @param campaign A campaign name + @param parameters Additional params like `@{@"af_sub1": @"val", @"custom_param": @"val2" }` + @param openStoreBlock Contains promoted `clickURL` + */ ++ (void)logAndOpenStore:(nonnull NSString *)appID + campaign:(nullable NSString *)campaign + parameters:(nullable NSDictionary *)parameters + openStore:(void (^)(NSURLSession *urlSession, NSURL *clickURL))openStoreBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLink.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLink.h new file mode 100644 index 000000000..f099aceb9 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLink.h @@ -0,0 +1,36 @@ +// +// AFSDKDeeplink.h +// AppsFlyerLib +// +// Created by Andrii Hahan on 20.08.2020. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(DeepLink) +@interface AppsFlyerDeepLink : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (readonly, nonnull) NSDictionary *clickEvent; +@property (readonly, nullable) NSString *deeplinkValue; +@property (readonly, nullable) NSString *matchType; +@property (readonly, nullable) NSString *clickHTTPReferrer; +@property (readonly, nullable) NSString *mediaSource; +@property (readonly, nullable) NSString *campaign; +@property (readonly, nullable) NSString *campaignId; +@property (readonly, nullable) NSString *afSub1; +@property (readonly, nullable) NSString *afSub2; +@property (readonly, nullable) NSString *afSub3; +@property (readonly, nullable) NSString *afSub4; +@property (readonly, nullable) NSString *afSub5; +@property (readonly) BOOL isDeferred; + +- (NSString *)toString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLinkResult.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLinkResult.h new file mode 100644 index 000000000..50d41d71e --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerDeepLinkResult.h @@ -0,0 +1,29 @@ +// +// AFSDKDeeplinkResult.h +// AppsFlyerLib +// +// Created by Andrii Hahan on 20.08.2020. +// + +#import + +@class AppsFlyerDeepLink; + +typedef NS_CLOSED_ENUM(NSUInteger, AFSDKDeepLinkResultStatus) { + AFSDKDeepLinkResultStatusNotFound, + AFSDKDeepLinkResultStatusFound, + AFSDKDeepLinkResultStatusFailure, +} NS_SWIFT_NAME(DeepLinkResultStatus); + +NS_SWIFT_NAME(DeepLinkResult) +@interface AppsFlyerDeepLinkResult : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property(readonly) AFSDKDeepLinkResultStatus status; + +@property(readonly, nullable) AppsFlyerDeepLink *deepLink; +@property(readonly, nullable) NSError *error; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib-Swift.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib-Swift.h new file mode 100644 index 000000000..4155b266d --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib-Swift.h @@ -0,0 +1,618 @@ +#if 0 +#elif defined(__arm64__) && __arm64__ +// Generated by Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +#ifndef APPSFLYERLIB_SWIFT_H +#define APPSFLYERLIB_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="AppsFlyerLib",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#elif defined(__x86_64__) && __x86_64__ +// Generated by Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +#ifndef APPSFLYERLIB_SWIFT_H +#define APPSFLYERLIB_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="AppsFlyerLib",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#else +#error unsupported Swift architecture +#endif diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib.h new file mode 100644 index 000000000..182fe6e91 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLib.h @@ -0,0 +1,743 @@ +// +// AppsFlyerLib.h +// AppsFlyerLib +// +// AppsFlyer iOS SDK 6.15.1 (211) +// Copyright (c) 2012-2023 AppsFlyer Ltd. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +// In app event names constants +#define AFEventLevelAchieved @"af_level_achieved" +#define AFEventAddPaymentInfo @"af_add_payment_info" +#define AFEventAddToCart @"af_add_to_cart" +#define AFEventAddToWishlist @"af_add_to_wishlist" +#define AFEventCompleteRegistration @"af_complete_registration" +#define AFEventTutorial_completion @"af_tutorial_completion" +#define AFEventInitiatedCheckout @"af_initiated_checkout" +#define AFEventPurchase @"af_purchase" +#define AFEventRate @"af_rate" +#define AFEventSearch @"af_search" +#define AFEventSpentCredits @"af_spent_credits" +#define AFEventAchievementUnlocked @"af_achievement_unlocked" +#define AFEventContentView @"af_content_view" +#define AFEventListView @"af_list_view" +#define AFEventTravelBooking @"af_travel_booking" +#define AFEventShare @"af_share" +#define AFEventInvite @"af_invite" +#define AFEventLogin @"af_login" +#define AFEventReEngage @"af_re_engage" +#define AFEventUpdate @"af_update" +#define AFEventOpenedFromPushNotification @"af_opened_from_push_notification" +#define AFEventLocation @"af_location_coordinates" +#define AFEventCustomerSegment @"af_customer_segment" + +#define AFEventSubscribe @"af_subscribe" +#define AFEventStartTrial @"af_start_trial" +#define AFEventAdClick @"af_ad_click" +#define AFEventAdView @"af_ad_view" + +// In app event parameter names +#define AFEventParamContent @"af_content" +#define AFEventParamAchievementId @"af_achievement_id" +#define AFEventParamLevel @"af_level" +#define AFEventParamScore @"af_score" +#define AFEventParamSuccess @"af_success" +#define AFEventParamPrice @"af_price" +#define AFEventParamContentType @"af_content_type" +#define AFEventParamContentId @"af_content_id" +#define AFEventParamContentList @"af_content_list" +#define AFEventParamCurrency @"af_currency" +#define AFEventParamQuantity @"af_quantity" +#define AFEventParamRegistrationMethod @"af_registration_method" +#define AFEventParamPaymentInfoAvailable @"af_payment_info_available" +#define AFEventParamMaxRatingValue @"af_max_rating_value" +#define AFEventParamRatingValue @"af_rating_value" +#define AFEventParamSearchString @"af_search_string" +#define AFEventParamDateA @"af_date_a" +#define AFEventParamDateB @"af_date_b" +#define AFEventParamDestinationA @"af_destination_a" +#define AFEventParamDestinationB @"af_destination_b" +#define AFEventParamDescription @"af_description" +#define AFEventParamClass @"af_class" +#define AFEventParamEventStart @"af_event_start" +#define AFEventParamEventEnd @"af_event_end" +#define AFEventParamLat @"af_lat" +#define AFEventParamLong @"af_long" +#define AFEventParamCustomerUserId @"af_customer_user_id" +#define AFEventParamValidated @"af_validated" +#define AFEventParamRevenue @"af_revenue" +#define AFEventProjectedParamRevenue @"af_projected_revenue" +#define AFEventParamReceiptId @"af_receipt_id" +#define AFEventParamTutorialId @"af_tutorial_id" +#define AFEventParamVirtualCurrencyName @"af_virtual_currency_name" +#define AFEventParamDeepLink @"af_deep_link" +#define AFEventParamOldVersion @"af_old_version" +#define AFEventParamNewVersion @"af_new_version" +#define AFEventParamReviewText @"af_review_text" +#define AFEventParamCouponCode @"af_coupon_code" +#define AFEventParamOrderId @"af_order_id" +#define AFEventParam1 @"af_param_1" +#define AFEventParam2 @"af_param_2" +#define AFEventParam3 @"af_param_3" +#define AFEventParam4 @"af_param_4" +#define AFEventParam5 @"af_param_5" +#define AFEventParam6 @"af_param_6" +#define AFEventParam7 @"af_param_7" +#define AFEventParam8 @"af_param_8" +#define AFEventParam9 @"af_param_9" +#define AFEventParam10 @"af_param_10" +#define AFEventParamTouch @"af_touch_obj" + +#define AFEventParamDepartingDepartureDate @"af_departing_departure_date" +#define AFEventParamReturningDepartureDate @"af_returning_departure_date" +#define AFEventParamDestinationList @"af_destination_list" //array of string +#define AFEventParamCity @"af_city" +#define AFEventParamRegion @"af_region" +#define AFEventParamCountry @"af_country" + + +#define AFEventParamDepartingArrivalDate @"af_departing_arrival_date" +#define AFEventParamReturningArrivalDate @"af_returning_arrival_date" +#define AFEventParamSuggestedDestinations @"af_suggested_destinations" //array of string +#define AFEventParamTravelStart @"af_travel_start" +#define AFEventParamTravelEnd @"af_travel_end" +#define AFEventParamNumAdults @"af_num_adults" +#define AFEventParamNumChildren @"af_num_children" +#define AFEventParamNumInfants @"af_num_infants" +#define AFEventParamSuggestedHotels @"af_suggested_hotels" //array of string + +#define AFEventParamUserScore @"af_user_score" +#define AFEventParamHotelScore @"af_hotel_score" +#define AFEventParamPurchaseCurrency @"af_purchase_currency" + +#define AFEventParamPreferredStarRatings @"af_preferred_star_ratings" //array of int (basically a tuple (min,max) but we'll use array of int and instruct the developer to use two values) + +#define AFEventParamPreferredPriceRange @"af_preferred_price_range" //array of int (basically a tuple (min,max) but we'll use array of int and instruct the developer to use two values) +#define AFEventParamPreferredNeighborhoods @"af_preferred_neighborhoods" //array of string +#define AFEventParamPreferredNumStops @"af_preferred_num_stops" + +/// Mail hashing type +typedef enum { + /// None + EmailCryptTypeNone = 0, + /// SHA256 + EmailCryptTypeSHA256 = 3 +} EmailCryptType; + +typedef NS_CLOSED_ENUM(NSInteger, AFSDKPlugin) { + AFSDKPluginIOSNative, + AFSDKPluginUnity, + AFSDKPluginFlutter, + AFSDKPluginReactNative, + AFSDKPluginAdobeAir, + AFSDKPluginAdobeMobile, + AFSDKPluginCocos2dx, + AFSDKPluginCordova, + AFSDKPluginMparticle, + AFSDKPluginNativeScript, + AFSDKPluginExpo, + AFSDKPluginUnreal, + AFSDKPluginXamarin, + AFSDKPluginCapacitor, + AFSDKPluginSegment, + AFSDKPluginAdobeSwiftAEP +} NS_SWIFT_NAME(Plugin); + + +NS_SWIFT_NAME(DeepLinkDelegate) +@protocol AppsFlyerDeepLinkDelegate + +@optional +- (void)didResolveDeepLink:(AppsFlyerDeepLinkResult *_Nonnull)result; + +@end + +/** + Conform and subscribe to this protocol to allow getting data about conversion and + install attribution + */ +@protocol AppsFlyerLibDelegate + +/** + `conversionInfo` contains information about install. + Organic/non-organic, etc. + @param conversionInfo May contain null values for some keys. Please handle this case. + */ +- (void)onConversionDataSuccess:(NSDictionary *)conversionInfo; + +/** + Any errors that occurred during the conversion request. + */ +- (void)onConversionDataFail:(NSError *)error; + +@optional + +/** + `attributionData` contains information about OneLink, deeplink. + */ +- (void)onAppOpenAttribution:(NSDictionary *)attributionData; + +/** + Any errors that occurred during the attribution request. + */ +- (void)onAppOpenAttributionFailure:(NSError *)error; + +/** + @abstract Sets the HTTP header fields of the ESP resolving to the given + dictionary. + @discussion This method replaces all header fields that may have + existed before this method ESP resolving call. + To keep default SDK behavior - return nil; + */ +- (NSDictionary * _Nullable)allHTTPHeaderFieldsForResolveDeepLinkURL:(NSURL *)URL; + +@end + +/** + You can log installs, app updates, sessions and additional in-app events + (including in-app purchases, game levels, etc.) + to evaluate ROI and user engagement. + The iOS SDK is compatible with all iOS/tvOS devices with iOS version 7 and above. + + @see [SDK Integration Validator](https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS) + for more information. + + */ +@interface AppsFlyerLib : NSObject + +/** + Gets the singleton instance of the AppsFlyerLib class, creating it if + necessary. + + @return The singleton instance of AppsFlyerLib. + */ ++ (AppsFlyerLib *)shared; + + +- (void)setUpInteroperabilityObject:(id)object; + +/** + In case you use your own user ID in your app, you can set this property to that ID. + Enables you to cross-reference your own unique ID with AppsFlyer’s unique ID and the other devices’ IDs + */ +@property(nonatomic, strong, nullable) NSString * customerUserID; + +/** + In case you use custom data and you want to receive it in the raw reports. + + @see [Setting additional custom data](https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS#setting-additional-custom-data) for more information. + */ +@property(nonatomic, strong, nullable, setter = setAdditionalData:) NSDictionary * customData; + +/** + Use this property to set your AppsFlyer's dev key + */ +@property(nonatomic, strong) NSString * appsFlyerDevKey; + +/** + Use this property to set your app's Apple ID(taken from the app's page on iTunes Connect) + */ +@property(nonatomic, strong) NSString * appleAppID; + +#ifndef AFSDK_NO_IDFA +/** + AppsFlyer SDK collect Apple's `advertisingIdentifier` if the `AdSupport.framework` included in the SDK. + You can disable this behavior by setting the following property to YES +*/ +@property(nonatomic) BOOL disableAdvertisingIdentifier; + +@property(nonatomic, strong, readonly) NSString *advertisingIdentifier; + +/** + Waits for request user authorization to access app-related data + */ +- (void)waitForATTUserAuthorizationWithTimeoutInterval:(NSTimeInterval)timeoutInterval +NS_SWIFT_NAME(waitForATTUserAuthorization(timeoutInterval:)); + +#endif + +@property(nonatomic) BOOL disableSKAdNetwork; + +/** + In case of in app purchase events, you can set the currency code your user has purchased with. + The currency code is a 3 letter code according to ISO standards + + Objective-C: + +
+ [[AppsFlyerLib shared] setCurrencyCode:@"USD"];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().currencyCode = "USD"
+ 
+ */ +@property(nonatomic, strong, nullable) NSString *currencyCode; + +/** + Prints SDK messages to the console log. This property should only be used in `DEBUG` mode. + The default value is `NO` + */ +@property(nonatomic) BOOL isDebug; + +/** + Set this flag to `YES`, to collect the current device name(e.g. "My iPhone"). Default value is `NO` + */ +@property(nonatomic) BOOL shouldCollectDeviceName; + +/** + Set your `OneLink ID` from OneLink configuration. Used in User Invites to generate a OneLink. + */ +@property(nonatomic, strong, nullable, setter = setAppInviteOneLink:) NSString * appInviteOneLinkID; + +/** + Opt-out logging for specific user + */ +@property(atomic) BOOL anonymizeUser; + +/** + Opt-out for Apple Search Ads attributions + */ +@property(atomic) BOOL disableCollectASA; + +/** + Disable Apple Ads Attribution API +[AAAtribution attributionTokenWithError:] + */ +@property(nonatomic) BOOL disableAppleAdsAttribution; + +/** + AppsFlyer delegate. See `AppsFlyerLibDelegate` + */ +@property(weak, nonatomic) id delegate; + +@property(weak, nonatomic) id deepLinkDelegate; + +/** + In app purchase receipt validation Apple environment(production or sandbox). The default value is NO + */ +@property(nonatomic) BOOL useReceiptValidationSandbox; + +/** + Set this flag to test uninstall on Apple environment(production or sandbox). The default value is NO + */ +@property(nonatomic) BOOL useUninstallSandbox; + +/** + For advertisers who wrap OneLink within another Universal Link. + An advertiser will be able to deeplink from a OneLink wrapped within another Universal Link and also log this retargeting conversion. + + Objective-C: + +
+ [[AppsFlyerLib shared] setResolveDeepLinkURLs:@[@"domain.com", @"subdomain.domain.com"]];
+ 
+ */ +@property(nonatomic, nullable, copy) NSArray *resolveDeepLinkURLs; + +/** + For advertisers who use vanity OneLinks. + + Objective-C: + +
+ [[AppsFlyerLib shared] oneLinkCustomDomains:@[@"domain.com", @"subdomain.domain.com"]];
+ 
+ */ +@property(nonatomic, nullable, copy) NSArray *oneLinkCustomDomains; + +/* + * Set phone number for each `start` event. `phoneNumber` will be sent as SHA256 string + */ +@property(nonatomic, nullable, copy) NSString *phoneNumber; + +- (NSString *)phoneNumber UNAVAILABLE_ATTRIBUTE; + +/** + To disable app's vendor identifier(IDFV), set disableIDFVCollection to true + */ +@property(nonatomic) BOOL disableIDFVCollection; + +/** + Set the language of the device. The data will be displayed in Raw Data Reports + Objective-C: + +
+ [[AppsFlyerLib shared] setCurrentDeviceLanguage:@"EN"]
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().currentDeviceLanguage("EN")
+ 
+ */ +@property(nonatomic, nullable, copy) NSString *currentDeviceLanguage; + +/** + Internal API. Please don't use. + */ +- (void)setPluginInfoWith:(AFSDKPlugin)plugin + pluginVersion:(NSString *)version + additionalParams:(NSDictionary * _Nullable)additionalParams +NS_SWIFT_NAME(setPluginInfo(plugin:version:additionalParams:)); + +/** + Enable the collection of Facebook Deferred AppLinks + Requires Facebook SDK and Facebook app on target/client device. + This API must be invoked prior to initializing the AppsFlyer SDK in order to function properly. + + Objective-C: + +
+ [[AppsFlyerLib shared] enableFacebookDeferredApplinksWithClass:[FBSDKAppLinkUtility class]]
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().enableFacebookDeferredApplinks(with: FBSDKAppLinkUtility.self)
+ 
+ + @param facebookAppLinkUtilityClass requeries method call `[FBSDKAppLinkUtility class]` as param. + */ +- (void)enableFacebookDeferredApplinksWithClass:(Class _Nullable)facebookAppLinkUtilityClass; + +/** + Use this to send the user's emails + + @param userEmails The list of strings that hold mails + @param type Hash algoritm + */ +- (void)setUserEmails:(NSArray * _Nullable)userEmails withCryptType:(EmailCryptType)type; + +/** + Start SDK session + Add the following method at the `applicationDidBecomeActive` in AppDelegate class + */ +- (void)start; + +- (void)startWithCompletionHandler:(void (^ _Nullable)(NSDictionary * _Nullable dictionary, NSError * _Nullable error))completionHandler; + +/** + Use this method to log an events with multiple values. See AppsFlyer's documentation for details. + + Objective-C: + +
+ [[AppsFlyerLib shared] logEvent:AFEventPurchase
+        withValues: @{AFEventParamRevenue  : @200,
+                      AFEventParamCurrency : @"USD",
+                      AFEventParamQuantity : @2,
+                      AFEventParamContentId: @"092",
+                      AFEventParamReceiptId: @"9277"}];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().logEvent(AFEventPurchase,
+        withValues: [AFEventParamRevenue  : "1200",
+                     AFEventParamContent  : "shoes",
+                     AFEventParamContentId: "123"])
+ 
+ + @param eventName Contains name of event that could be provided from predefined constants in `AppsFlyerLib.h` + @param values Contains dictionary of values for handling by backend + */ +- (void)logEvent:(NSString *)eventName withValues:(NSDictionary * _Nullable)values; + +- (void)logEventWithEventName:(NSString *)eventName + eventValues:(NSDictionary * _Nullable)eventValues + completionHandler:(void (^ _Nullable)(NSDictionary * _Nullable dictionary, NSError * _Nullable error))completionHandler +NS_SWIFT_NAME(logEvent(name:values:completionHandler:)); + +/** + To log and validate in app purchases you can call this method from the completeTransaction: method on + your `SKPaymentTransactionObserver`. + + @param productIdentifier The product identifier + @param price The product price + @param currency The product currency + @param transactionId The purchase transaction Id + @param params The additional param, which you want to receive it in the raw reports + @param successBlock The success callback + @param failedBlock The failure callback + */ +- (void)validateAndLogInAppPurchase:(NSString * _Nullable)productIdentifier + price:(NSString * _Nullable)price + currency:(NSString * _Nullable)currency + transactionId:(NSString * _Nullable)transactionId + additionalParameters:(NSDictionary * _Nullable)params + success:(void (^ _Nullable)(NSDictionary * response))successBlock + failure:(void (^ _Nullable)(NSError * _Nullable error, id _Nullable reponse))failedBlock NS_AVAILABLE(10_7, 7_0); + +typedef void (^AFSDKValidateAndLogCompletion)(AFSDKValidateAndLogResult * _Nullable result); + +/** + To log and validate in app purchases you can call this method from the completeTransaction: method on + your `SKPaymentTransactionObserver`. + + @param details The product details + @param extraEventValues The additional param, which you want to receive it in the raw reports + @param completionHandler The callback + */ +- (void)validateAndLogInAppPurchase:(AFSDKPurchaseDetails *)details + extraEventValues:(NSDictionary * _Nullable)extraEventValues + completionHandler:(AFSDKValidateAndLogCompletion)completionHandler NS_AVAILABLE(10_7, 7_0); + +/** + An API to provide the data from the impression payload to AdRevenue. + + @param adRevenueData object used to hold all mandatory parameters of AdRevenue event. + @param additionalParameters non-mandatory dictionary which can include pre-defined keys (kAppsFlyerAdRevenueCountry, etc) + */ +- (void)logAdRevenue:(AFAdRevenueData *)adRevenueData additionalParameters:(NSDictionary * _Nullable)additionalParameters; + +/** + To log location for geo-fencing. Does the same as code below. + +
+ AppsFlyerLib.shared().logEvent(AFEventLocation, withValues: [AFEventParamLong:longitude, AFEventParamLat:latitude])
+ 
+ + @param longitude The location longitude + @param latitude The location latitude + */ +- (void)logLocation:(double)longitude latitude:(double)latitude NS_SWIFT_NAME(logLocation(longitude:latitude:)); + +/** + This method returns AppsFlyer's internal id(unique for your app) + + @return Internal AppsFlyer Id + */ +- (NSString *)getAppsFlyerUID; + +/** + In case you want to log deep linking. Does the same as `-handleOpenURL:sourceApplication:withAnnotation`. + + @warning Preferred to use `-handleOpenURL:sourceApplication:withAnnotation`. + + @param url The URL that was passed to your AppDelegate. + @param sourceApplication The sourceApplication that passed to your AppDelegate. + */ +- (void)handleOpenURL:(NSURL * _Nullable)url sourceApplication:(NSString * _Nullable)sourceApplication API_UNAVAILABLE(macos); + +/** + In case you want to log deep linking. + Call this method from inside your AppDelegate `-application:openURL:sourceApplication:annotation:` + + @param url The URL that was passed to your AppDelegate. + @param sourceApplication The sourceApplication that passed to your AppDelegate. + @param annotation The annotation that passed to your app delegate. + */ +- (void)handleOpenURL:(NSURL * _Nullable)url + sourceApplication:(NSString * _Nullable)sourceApplication + withAnnotation:(id _Nullable)annotation API_UNAVAILABLE(macos); + +/** + Call this method from inside of your AppDelegate `-application:openURL:options:` method. + This method is functionally the same as calling the AppsFlyer method + `-handleOpenURL:sourceApplication:withAnnotation`. + + @param url The URL that was passed to your app delegate + @param options The options dictionary that was passed to your AppDelegate. + */ +- (void)handleOpenUrl:(NSURL * _Nullable)url options:(NSDictionary * _Nullable)options API_UNAVAILABLE(macos); + +/** + Allow AppsFlyer to handle restoration from an NSUserActivity. + Use this method to log deep links with OneLink. + + @param userActivity The NSUserActivity that caused the app to be opened. + */ +- (BOOL)continueUserActivity:(NSUserActivity * _Nullable)userActivity + restorationHandler:(void (^ _Nullable)(NSArray * _Nullable))restorationHandler NS_AVAILABLE_IOS(9_0) API_UNAVAILABLE(macos); + +/** + Enable AppsFlyer to handle a push notification. + + @see [Learn more here](https://support.appsflyer.com/hc/en-us/articles/207364076-Measuring-Push-Notification-Re-Engagement-Campaigns) + + @warning To make it work - set data, related to AppsFlyer under key @"af". + + @param pushPayload The `userInfo` from received remote notification. One of root keys should be @"af". + */ +- (void)handlePushNotification:(NSDictionary * _Nullable)pushPayload; + + +/** + Register uninstall - you should register for remote notification and provide AppsFlyer the push device token. + + @param deviceToken The `deviceToken` from `-application:didRegisterForRemoteNotificationsWithDeviceToken:` + */ +- (void)registerUninstall:(NSData * _Nullable)deviceToken; + +/** + Get SDK version. + + @return The AppsFlyer SDK version info. + */ +- (NSString *)getSDKVersion; + +/** + This is for internal use. + */ +- (void)remoteDebuggingCallWithData:(NSString *)data; + +/** + This is for internal use. + */ +- (void)remoteDebuggingCallV2WithData:(NSString *)dataAsString; + +/** + Used to force the trigger `onAppOpenAttribution` delegate. + Notice, re-engagement, session and launch won't be counted. + Only for OneLink/UniversalLink/Deeplink resolving. + + @param URL The param to resolve into -[AppsFlyerLibDelegate onAppOpenAttribution:] + */ +- (void)performOnAppAttributionWithURL:(NSURL * _Nullable)URL; + +/** + @brief This property accepts a string value representing the host name for all endpoints. + Can be used to Zero rate your application’s data usage. Contact your CSM for more information. + + @warning To use `default` SDK endpoint – set value to `nil`. + + Objective-C: + +
+ [[AppsFlyerLib shared] setHost:@"example.com"];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().host = "example.com"
+ 
+ */ +@property(nonatomic, strong, readonly) NSString *host; + +/** + * This function set the host name and prefix host name for all the endpoints + **/ +- (void)setHost:(NSString *)host withHostPrefix:(NSString *)hostPrefix; + +/** + * This property accepts a string value representing the prefix host name for all endpoints. + * for example "test" prefix with default host name will have the address "host.appsflyer.com" + */ +@property(nonatomic, strong, readonly) NSString *hostPrefix; + +/** + This property is responsible for timeout between sessions in seconds. + Default value is 5 seconds. + */ +@property(atomic) NSUInteger minTimeBetweenSessions; + +/** + API to shut down all SDK activities. + + @warning This will disable all requests from AppsFlyer SDK. + */ +@property(atomic) BOOL isStopped; + +/** + API to set manually Facebook deferred app link + */ +@property(nonatomic, nullable, copy) NSURL *facebookDeferredAppLink; + +/** + Block an events from being shared with ad networks and other 3rd party integrations + Must only include letters/digits or underscore, maximum length: 45 + */ +@property(nonatomic, nullable, copy) NSArray *sharingFilter DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); + +@property(nonatomic) NSUInteger deepLinkTimeout; + +/** + Block an events from being shared with any partner + This method overwrite -[AppsFlyerLib setSharingFilter:] + */ +- (void)setSharingFilterForAllPartners DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); + +/** + Block an events from being shared with ad networks and other 3rd party integrations + Must only include letters/digits or underscore, maximum length: 45 + + The sharing filter is cleared in case if `nil` or empty array passed as a parameter. + "all" keyword sets sharing filter for ALL partners, it is case insencitive and has highest priority + if passed along with another values. For example, if ["all", "examplePartner1_int", "examplePartner2_int" ] passed, + the sharing filter will be set for ALL partners. + */ +- (void)setSharingFilterForPartners:(NSArray * _Nullable)sharingFilter; + + +/** + Sets or updates the user consent data related to GDPR and DMA regulations for advertising and data usage + purposes within the application. This method must be invoked with the user's current consent status each + time the app starts or whenever there is a change in the user's consent preferences. + + Note that this method does not persist the consent data across app sessions; it only applies for the + duration of the current app session. If you wish to stop providing the consent data, you should + cease calling this method. + + @param consent an instance of AppsFlyerConsent that encapsulates the user's consent information. + */ +- (void)setConsentData:(AppsFlyerConsent *)consent; + +/** + Enable the SDK to collect and send TCF data + + @param shouldCollectConsentData indicates if the TCF data collection is enabled. + */ +- (void)enableTCFDataCollection:(BOOL)shouldCollectConsentData; + +/** + Validate if URL contains certain string and append quiery + parameters to deeplink URL. In case if URL does not contain user-defined string, + parameters are not appended to the url. + + @param containsString string to check in URL. + @param parameters NSDictionary, which containins parameters to append to the deeplink url after it passed validation. + */ +- (void)appendParametersToDeepLinkingURLWithString:(NSString *)containsString + parameters:(NSDictionary *)parameters +NS_SWIFT_NAME(appendParametersToDeeplinkURL(contains:parameters:)); + +/** + Adds array of keys, which are used to compose key path + to resolve deeplink from push notification payload `userInfo`. + + @param deepLinkPath an array of strings which contains keys to search for deeplink in payload. + */ +- (void)addPushNotificationDeepLinkPath:(NSArray *)deepLinkPath; + +/** + * Allows sending custom data for partner integration purposes. + * + * @param partnerId ID of the partner (usually has "_int" suffix) + * @param partnerInfo customer data, depends on the integration nature with specific partner + */ + +- (void)setPartnerDataWithPartnerId:(NSString * _Nullable)partnerId partnerInfo:(NSDictionary * _Nullable)partnerInfo +NS_SWIFT_NAME(setPartnerData(partnerId:partnerInfo:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLinkGenerator.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLinkGenerator.h new file mode 100644 index 000000000..d3ec8f4e4 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerLinkGenerator.h @@ -0,0 +1,52 @@ +// +// LinkGenerator.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Payload container for the `generateInviteUrlWithLinkGenerator:completionHandler:` from `AppsFlyerShareInviteHelper` + */ +@interface AppsFlyerLinkGenerator : NSObject + +/// Instance initialization is not allowed. Use generated instance +/// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` +- (instancetype)init NS_UNAVAILABLE; +/// Instance initialization is not allowed. Use generated instance +/// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` ++ (instancetype)new NS_UNAVAILABLE; + +@property(nonatomic, nullable, copy) NSString *brandDomain; + +/// The channel through which the invite was sent (e.g. Facebook/Gmail/etc.). Usage: Recommended +- (void)setChannel :(nonnull NSString *)channel; +/// ReferrerCustomerId setter +- (void)setReferrerCustomerId:(nonnull NSString *)referrerCustomerId; +/// A campaign name. Usage: Optional +- (void)setCampaign :(nonnull NSString *)campaign; +/// ReferrerUID setter +- (void)setReferrerUID :(nonnull NSString *)referrerUID; +/// Referrer name +- (void)setReferrerName :(nonnull NSString *)referrerName; +/// The URL to referrer user avatar. Usage: Optional +- (void)setReferrerImageURL :(nonnull NSString *)referrerImageURL; +/// AppleAppID +- (void)setAppleAppID :(nonnull NSString *)appleAppID; +/// Deeplink path +- (void)setDeeplinkPath :(nonnull NSString *)deeplinkPath; +/// Base deeplink path +- (void)setBaseDeeplink :(nonnull NSString *)baseDeeplink; +/// A single key value custom parameter. Usage: Optional +- (void)addParameterValue :(nonnull NSString *)value forKey:(NSString *)key; +/// Multiple key value custom parameters. Usage: Optional +- (void)addParameters :(nonnull NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerShareInviteHelper.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerShareInviteHelper.h new file mode 100644 index 000000000..f55dbf9d0 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Headers/AppsFlyerShareInviteHelper.h @@ -0,0 +1,35 @@ +// +// ShareInviteHelper.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import +#import + +/** + AppsFlyerShareInviteHelper + */ +@interface AppsFlyerShareInviteHelper : NSObject + +NS_ASSUME_NONNULL_BEGIN + +/** + * The AppsFlyerShareInviteHelper class builds the invite URL according to various setter methods + * which allow passing on additional information on the click. + * This information is available through `onConversionDataReceived:` when the user accepts the invite and installs the app. + * In addition, campaign and channel parameters are visible within the AppsFlyer Dashboard. + */ ++ (void)generateInviteUrlWithLinkGenerator:(AppsFlyerLinkGenerator *(^)(AppsFlyerLinkGenerator *generator))generatorCreator completionHandler:(void (^)(NSURL *_Nullable url))completionHandler; + +/** + * It is recommended to generate an in-app event after the invite is sent to log the invites from the senders' perspective. + * This enables you to find the users that tend most to invite friends, and the media sources that get you these users. + */ ++ (void)logInvite:(nullable NSString *)channel parameters:(nullable NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.abi.json b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.abi.json new file mode 100644 index 000000000..4d7c1be56 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.abi.json @@ -0,0 +1,458 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "TopLevel", + "printedName": "TopLevel", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKConnectorCommunication", + "printedName": "AFSDKConnectorCommunication", + "children": [ + { + "kind": "Function", + "name": "setPurchaseDataSendingDelegate", + "printedName": "setPurchaseDataSendingDelegate(delegate:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AppsFlyerLib.AFSDKPurchaseDataSendingDelegate", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate" + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication(im)setPurchaseDataSendingDelegateWithDelegate:", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP30setPurchaseDataSendingDelegate8delegateyAA013AFSDKPurchasehiJ0_p_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKConnectorCommunication>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "setPurchaseDataSendingDelegateWithDelegate:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AFSDKPurchaseDataSendingDelegate", + "children": [ + { + "kind": "Function", + "name": "sendDecryptReceiptRequest", + "printedName": "sendDecryptReceiptRequest(environment:receipt:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP25sendDecryptReceiptRequest11environment7receipt17completionHandlerySS_SSySDys11AnyHashableVypGSg_s5Error_pSgtctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendARSRequest", + "printedName": "sendARSRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendARSRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP14sendARSRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendARSRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendVIAPRequest", + "printedName": "sendVIAPRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendVIAPRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP15sendVIAPRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendVIAPRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendCachedPurchaseConnectorEvents", + "printedName": "sendCachedPurchaseConnectorEvents(completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "() -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP33sendCachedPurchaseConnectorEvents17completionHandleryyyc_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.private.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.private.swiftinterface new file mode 100644 index 000000000..a76b4073f --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.private.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target arm64-apple-macos10.14.6 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.swiftdoc b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.swiftdoc new file mode 100644 index 000000000..dda2f05da Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.swiftdoc differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.swiftinterface new file mode 100644 index 000000000..a76b4073f --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/arm64-apple-macos.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target arm64-apple-macos10.14.6 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.abi.json b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.abi.json new file mode 100644 index 000000000..4d7c1be56 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.abi.json @@ -0,0 +1,458 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "TopLevel", + "printedName": "TopLevel", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKConnectorCommunication", + "printedName": "AFSDKConnectorCommunication", + "children": [ + { + "kind": "Function", + "name": "setPurchaseDataSendingDelegate", + "printedName": "setPurchaseDataSendingDelegate(delegate:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AppsFlyerLib.AFSDKPurchaseDataSendingDelegate", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate" + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication(im)setPurchaseDataSendingDelegateWithDelegate:", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP30setPurchaseDataSendingDelegate8delegateyAA013AFSDKPurchasehiJ0_p_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKConnectorCommunication>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "setPurchaseDataSendingDelegateWithDelegate:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AFSDKPurchaseDataSendingDelegate", + "children": [ + { + "kind": "Function", + "name": "sendDecryptReceiptRequest", + "printedName": "sendDecryptReceiptRequest(environment:receipt:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP25sendDecryptReceiptRequest11environment7receipt17completionHandlerySS_SSySDys11AnyHashableVypGSg_s5Error_pSgtctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendARSRequest", + "printedName": "sendARSRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendARSRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP14sendARSRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendARSRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendVIAPRequest", + "printedName": "sendVIAPRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendVIAPRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP15sendVIAPRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendVIAPRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendCachedPurchaseConnectorEvents", + "printedName": "sendCachedPurchaseConnectorEvents(completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "() -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP33sendCachedPurchaseConnectorEvents17completionHandleryyyc_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.private.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.private.swiftinterface new file mode 100644 index 000000000..afedd07ca --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.private.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target x86_64-apple-macos10.14.6 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.swiftdoc b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.swiftdoc new file mode 100644 index 000000000..cf51e707f Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.swiftdoc differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.swiftinterface new file mode 100644 index 000000000..afedd07ca --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-macos.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target x86_64-apple-macos10.14.6 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/module.modulemap b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/module.modulemap new file mode 100644 index 000000000..abf5a4827 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Modules/module.modulemap @@ -0,0 +1,11 @@ +framework module AppsFlyerLib { + umbrella header "AppsFlyerLib.h" + + export * + module * { export * } +} + +module AppsFlyerLib.Swift { + header "AppsFlyerLib-Swift.h" + requires objc +} diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Resources/Info.plist b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Resources/Info.plist new file mode 100644 index 000000000..d0dd0fe49 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Resources/Info.plist @@ -0,0 +1,48 @@ + + + + + BuildMachineOSBuild + 23C71 + CFBundleDevelopmentRegion + en + CFBundleExecutable + AppsFlyerLib + CFBundleIdentifier + com.appsflyer.sdk.lib + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + AppsFlyerLib + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 6.15.1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 21A326 + DTPlatformName + macosx + DTPlatformVersion + 14.0 + DTSDKBuild + 23A334 + DTSDKName + macosx14.0 + DTXcode + 1501 + DTXcodeBuild + 15A507 + LSMinimumSystemVersion + 10.14.6 + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Resources/PrivacyInfo.xcprivacy b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..5da2889f7 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/A/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,73 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyTrackingDomains + + att-attr.whappsflyer.com + att-attr.appsflyer-cn.com + att-attr.hevents.appsflyer-cn.com + att-launches.whappsflyer.com + att-launches.appsflyer-cn.com + att-launches.hevents.appsflyer-cn.com + att-conversions.hevents.appsflyer-cn.com + att-conversions.appsflyer-cn.com + att-conversions.whappsflyer.com + att-dlsdk.hevents.appsflyer-cn.com + att-dlsdk.appsflyer-cn.com + att-dlsdk.whappsflyer.com + att-dlsdk.appsflyersdk.com + att-conversions.appsflyersdk.com + att-launches.appsflyersdk.com + att-attr.appsflyersdk.com + + NSPrivacyTracking + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + NSPrivacyAccessedAPITypeReasons + + C617.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + + + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/Current b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/Current new file mode 120000 index 000000000..8c7e5a667 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/macos-arm64_x86_64/AppsFlyerLib.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/AppsFlyerLib b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/AppsFlyerLib new file mode 100644 index 000000000..ce4635089 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/AppsFlyerLib differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AFAdRevenueData.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AFAdRevenueData.h new file mode 100644 index 000000000..2025468a3 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AFAdRevenueData.h @@ -0,0 +1,68 @@ +// +// AFAdRevenueData.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 26/06/2024. +// + +typedef NS_CLOSED_ENUM(NSUInteger, AppsFlyerAdRevenueMediationNetworkType) { + AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob = 1, + AppsFlyerAdRevenueMediationNetworkTypeIronSource = 2, + AppsFlyerAdRevenueMediationNetworkTypeApplovinMax= 3, + AppsFlyerAdRevenueMediationNetworkTypeFyber = 4, + AppsFlyerAdRevenueMediationNetworkTypeAppodeal = 5, + AppsFlyerAdRevenueMediationNetworkTypeAdmost = 6, + AppsFlyerAdRevenueMediationNetworkTypeTopon = 7, + AppsFlyerAdRevenueMediationNetworkTypeTradplus = 8, + AppsFlyerAdRevenueMediationNetworkTypeYandex = 9, + AppsFlyerAdRevenueMediationNetworkTypeChartBoost = 10, + AppsFlyerAdRevenueMediationNetworkTypeUnity = 11, + AppsFlyerAdRevenueMediationNetworkTypeToponPte = 12, + AppsFlyerAdRevenueMediationNetworkTypeCustom = 13, + AppsFlyerAdRevenueMediationNetworkTypeDirectMonetization = 14 +} NS_SWIFT_NAME(MediationNetworkType); + +#define kAppsFlyerAdRevenueMonetizationNetwork @"monetization_network" +#define kAppsFlyerAdRevenueMediationNetwork @"mediation_network" +#define kAppsFlyerAdRevenueEventRevenue @"event_revenue" +#define kAppsFlyerAdRevenueEventRevenueCurrency @"event_revenue_currency" +#define kAppsFlyerAdRevenueCustomParameters @"custom_parameters" +#define kAFADRWrapperTypeGeneric @"adrevenue_sdk" + +//Pre-defined keys for non-mandatory dictionary + +//Code ISO 3166-1 format +#define kAppsFlyerAdRevenueCountry @"country" + +//ID of the ad unit for the impression +#define kAppsFlyerAdRevenueAdUnit @"ad_unit" + +//Format of the ad +#define kAppsFlyerAdRevenueAdType @"ad_type" + +//ID of the ad placement for the impression +#define kAppsFlyerAdRevenuePlacement @"placement" + + +@interface AFAdRevenueData : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (strong, nonnull, nonatomic) NSString *monetizationNetwork; +@property AppsFlyerAdRevenueMediationNetworkType mediationNetwork; +@property (strong, nonnull, nonatomic) NSString *currencyIso4217Code; +@property (strong, nonnull, nonatomic) NSNumber *eventRevenue; + +/** +* @param monetizationNetwork network which monetized the impression (@"facebook") +* @param mediationNetwork mediation source that mediated the monetization network for the impression (AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob) +* @param currencyIso4217Code reported impression’s revenue currency ISO 4217 format (@"USD") +* @param eventRevenue reported impression’s revenue (@(0.001994303)) +*/ +- (instancetype _Nonnull )initWithMonetizationNetwork:(NSString *_Nonnull)monetizationNetwork + mediationNetwork:(AppsFlyerAdRevenueMediationNetworkType)mediationNetwork + currencyIso4217Code:(NSString *_Nonnull)currencyIso4217Code + eventRevenue:(NSNumber *_Nonnull)eventRevenue; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h new file mode 100644 index 000000000..89af8a702 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h @@ -0,0 +1,23 @@ +// +// AFSDKPurchaseDetails.h +// AppsFlyerLib +// +// Created by Moris Gateno on 13/03/2024. +// + +@interface AFSDKPurchaseDetails : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (strong, nullable, nonatomic) NSString *productId; +@property (strong, nullable, nonatomic) NSString *price; +@property (strong, nullable, nonatomic) NSString *currency; +@property (strong, nullable, nonatomic) NSString *transactionId; + +- (instancetype _Nonnull )initWithProductId:(NSString *_Nullable)productId + price:(NSString *_Nullable)price + currency:(NSString *_Nullable)currency + transactionId:(NSString *_Nullable)transactionId; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h new file mode 100644 index 000000000..94ef0f4c2 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h @@ -0,0 +1,35 @@ +// +// AFSDKValidateAndLogResult.h +// AppsFlyerLib +// +// Created by Moris Gateno on 13/03/2024. +// + + +typedef NS_CLOSED_ENUM(NSUInteger, AFSDKValidateAndLogStatus) { + AFSDKValidateAndLogStatusSuccess, + AFSDKValidateAndLogStatusFailure, + AFSDKValidateAndLogStatusError +} NS_SWIFT_NAME(ValidateAndLogStatus); + +NS_SWIFT_NAME(ValidateAndLogResult) +@interface AFSDKValidateAndLogResult : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +- (instancetype _Nonnull )initWithStatus:(AFSDKValidateAndLogStatus)status + result:(NSDictionary *_Nullable)result + errorData:(NSDictionary *_Nullable)errorData + error:(NSError *_Nullable)error; + +@property(readonly) AFSDKValidateAndLogStatus status; +// Success case +@property(readonly, nullable) NSDictionary *result; +// Server 200 with validation failure +@property(readonly, nullable) NSDictionary *errorData; +// for the error case +@property(readonly, nullable) NSError *error; + +@end + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h new file mode 100644 index 000000000..564912a92 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h @@ -0,0 +1,26 @@ +// +// AppsFlyerConsent.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 14/01/2024. +// +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AppsFlyerConsent : NSObject + +@property (nonatomic, readonly, assign) BOOL isUserSubjectToGDPR; +@property (nonatomic, readonly, assign) BOOL hasConsentForDataUsage; +@property (nonatomic, readonly, assign) BOOL hasConsentForAdsPersonalization; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (instancetype)initForGDPRUserWithHasConsentForDataUsage:(BOOL)hasConsentForDataUsage + hasConsentForAdsPersonalization:(BOOL)hasConsentForAdsPersonalization NS_DESIGNATED_INITIALIZER; +- (instancetype)initNonGDPRUser NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h new file mode 100644 index 000000000..483f8f863 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h @@ -0,0 +1,49 @@ +// +// CrossPromotionHelper.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + AppsFlyer allows you to log and attribute installs originating + from cross promotion campaigns of your existing apps. + Afterwards, you can optimize on your cross-promotion traffic to get even better results. + */ +@interface AppsFlyerCrossPromotionHelper : NSObject + +/** + To log an impression use the following API call. + Make sure to use the promoted App ID as it appears within the AppsFlyer dashboard. + + @param appID Promoted App ID + @param campaign A campaign name + @param parameters Additional params like `@{@"af_sub1": @"val", @"custom_param": @"val2" }` +*/ ++ (void)logCrossPromoteImpression:(nonnull NSString *)appID + campaign:(nullable NSString *)campaign + parameters:(nullable NSDictionary *)parameters; + +/** + iOS allows you to utilize the StoreKit component to open + the App Store while remaining in the context of your app. + More details at https://support.appsflyer.com/hc/en-us/articles/115004481946-Cross-Promotion-Tracking#tracking-cross-promotion-impressions + + @param appID Promoted App ID + @param campaign A campaign name + @param parameters Additional params like `@{@"af_sub1": @"val", @"custom_param": @"val2" }` + @param openStoreBlock Contains promoted `clickURL` + */ ++ (void)logAndOpenStore:(nonnull NSString *)appID + campaign:(nullable NSString *)campaign + parameters:(nullable NSDictionary *)parameters + openStore:(void (^)(NSURLSession *urlSession, NSURL *clickURL))openStoreBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h new file mode 100644 index 000000000..f099aceb9 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h @@ -0,0 +1,36 @@ +// +// AFSDKDeeplink.h +// AppsFlyerLib +// +// Created by Andrii Hahan on 20.08.2020. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(DeepLink) +@interface AppsFlyerDeepLink : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (readonly, nonnull) NSDictionary *clickEvent; +@property (readonly, nullable) NSString *deeplinkValue; +@property (readonly, nullable) NSString *matchType; +@property (readonly, nullable) NSString *clickHTTPReferrer; +@property (readonly, nullable) NSString *mediaSource; +@property (readonly, nullable) NSString *campaign; +@property (readonly, nullable) NSString *campaignId; +@property (readonly, nullable) NSString *afSub1; +@property (readonly, nullable) NSString *afSub2; +@property (readonly, nullable) NSString *afSub3; +@property (readonly, nullable) NSString *afSub4; +@property (readonly, nullable) NSString *afSub5; +@property (readonly) BOOL isDeferred; + +- (NSString *)toString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h new file mode 100644 index 000000000..50d41d71e --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h @@ -0,0 +1,29 @@ +// +// AFSDKDeeplinkResult.h +// AppsFlyerLib +// +// Created by Andrii Hahan on 20.08.2020. +// + +#import + +@class AppsFlyerDeepLink; + +typedef NS_CLOSED_ENUM(NSUInteger, AFSDKDeepLinkResultStatus) { + AFSDKDeepLinkResultStatusNotFound, + AFSDKDeepLinkResultStatusFound, + AFSDKDeepLinkResultStatusFailure, +} NS_SWIFT_NAME(DeepLinkResultStatus); + +NS_SWIFT_NAME(DeepLinkResult) +@interface AppsFlyerDeepLinkResult : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property(readonly) AFSDKDeepLinkResultStatus status; + +@property(readonly, nullable) AppsFlyerDeepLink *deepLink; +@property(readonly, nullable) NSError *error; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h new file mode 100644 index 000000000..1f1b87e0b --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h @@ -0,0 +1,311 @@ +#if 0 +#elif defined(__arm64__) && __arm64__ +// Generated by Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +#ifndef APPSFLYERLIB_SWIFT_H +#define APPSFLYERLIB_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="AppsFlyerLib",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#else +#error unsupported Swift architecture +#endif diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib.h new file mode 100644 index 000000000..182fe6e91 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLib.h @@ -0,0 +1,743 @@ +// +// AppsFlyerLib.h +// AppsFlyerLib +// +// AppsFlyer iOS SDK 6.15.1 (211) +// Copyright (c) 2012-2023 AppsFlyer Ltd. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +// In app event names constants +#define AFEventLevelAchieved @"af_level_achieved" +#define AFEventAddPaymentInfo @"af_add_payment_info" +#define AFEventAddToCart @"af_add_to_cart" +#define AFEventAddToWishlist @"af_add_to_wishlist" +#define AFEventCompleteRegistration @"af_complete_registration" +#define AFEventTutorial_completion @"af_tutorial_completion" +#define AFEventInitiatedCheckout @"af_initiated_checkout" +#define AFEventPurchase @"af_purchase" +#define AFEventRate @"af_rate" +#define AFEventSearch @"af_search" +#define AFEventSpentCredits @"af_spent_credits" +#define AFEventAchievementUnlocked @"af_achievement_unlocked" +#define AFEventContentView @"af_content_view" +#define AFEventListView @"af_list_view" +#define AFEventTravelBooking @"af_travel_booking" +#define AFEventShare @"af_share" +#define AFEventInvite @"af_invite" +#define AFEventLogin @"af_login" +#define AFEventReEngage @"af_re_engage" +#define AFEventUpdate @"af_update" +#define AFEventOpenedFromPushNotification @"af_opened_from_push_notification" +#define AFEventLocation @"af_location_coordinates" +#define AFEventCustomerSegment @"af_customer_segment" + +#define AFEventSubscribe @"af_subscribe" +#define AFEventStartTrial @"af_start_trial" +#define AFEventAdClick @"af_ad_click" +#define AFEventAdView @"af_ad_view" + +// In app event parameter names +#define AFEventParamContent @"af_content" +#define AFEventParamAchievementId @"af_achievement_id" +#define AFEventParamLevel @"af_level" +#define AFEventParamScore @"af_score" +#define AFEventParamSuccess @"af_success" +#define AFEventParamPrice @"af_price" +#define AFEventParamContentType @"af_content_type" +#define AFEventParamContentId @"af_content_id" +#define AFEventParamContentList @"af_content_list" +#define AFEventParamCurrency @"af_currency" +#define AFEventParamQuantity @"af_quantity" +#define AFEventParamRegistrationMethod @"af_registration_method" +#define AFEventParamPaymentInfoAvailable @"af_payment_info_available" +#define AFEventParamMaxRatingValue @"af_max_rating_value" +#define AFEventParamRatingValue @"af_rating_value" +#define AFEventParamSearchString @"af_search_string" +#define AFEventParamDateA @"af_date_a" +#define AFEventParamDateB @"af_date_b" +#define AFEventParamDestinationA @"af_destination_a" +#define AFEventParamDestinationB @"af_destination_b" +#define AFEventParamDescription @"af_description" +#define AFEventParamClass @"af_class" +#define AFEventParamEventStart @"af_event_start" +#define AFEventParamEventEnd @"af_event_end" +#define AFEventParamLat @"af_lat" +#define AFEventParamLong @"af_long" +#define AFEventParamCustomerUserId @"af_customer_user_id" +#define AFEventParamValidated @"af_validated" +#define AFEventParamRevenue @"af_revenue" +#define AFEventProjectedParamRevenue @"af_projected_revenue" +#define AFEventParamReceiptId @"af_receipt_id" +#define AFEventParamTutorialId @"af_tutorial_id" +#define AFEventParamVirtualCurrencyName @"af_virtual_currency_name" +#define AFEventParamDeepLink @"af_deep_link" +#define AFEventParamOldVersion @"af_old_version" +#define AFEventParamNewVersion @"af_new_version" +#define AFEventParamReviewText @"af_review_text" +#define AFEventParamCouponCode @"af_coupon_code" +#define AFEventParamOrderId @"af_order_id" +#define AFEventParam1 @"af_param_1" +#define AFEventParam2 @"af_param_2" +#define AFEventParam3 @"af_param_3" +#define AFEventParam4 @"af_param_4" +#define AFEventParam5 @"af_param_5" +#define AFEventParam6 @"af_param_6" +#define AFEventParam7 @"af_param_7" +#define AFEventParam8 @"af_param_8" +#define AFEventParam9 @"af_param_9" +#define AFEventParam10 @"af_param_10" +#define AFEventParamTouch @"af_touch_obj" + +#define AFEventParamDepartingDepartureDate @"af_departing_departure_date" +#define AFEventParamReturningDepartureDate @"af_returning_departure_date" +#define AFEventParamDestinationList @"af_destination_list" //array of string +#define AFEventParamCity @"af_city" +#define AFEventParamRegion @"af_region" +#define AFEventParamCountry @"af_country" + + +#define AFEventParamDepartingArrivalDate @"af_departing_arrival_date" +#define AFEventParamReturningArrivalDate @"af_returning_arrival_date" +#define AFEventParamSuggestedDestinations @"af_suggested_destinations" //array of string +#define AFEventParamTravelStart @"af_travel_start" +#define AFEventParamTravelEnd @"af_travel_end" +#define AFEventParamNumAdults @"af_num_adults" +#define AFEventParamNumChildren @"af_num_children" +#define AFEventParamNumInfants @"af_num_infants" +#define AFEventParamSuggestedHotels @"af_suggested_hotels" //array of string + +#define AFEventParamUserScore @"af_user_score" +#define AFEventParamHotelScore @"af_hotel_score" +#define AFEventParamPurchaseCurrency @"af_purchase_currency" + +#define AFEventParamPreferredStarRatings @"af_preferred_star_ratings" //array of int (basically a tuple (min,max) but we'll use array of int and instruct the developer to use two values) + +#define AFEventParamPreferredPriceRange @"af_preferred_price_range" //array of int (basically a tuple (min,max) but we'll use array of int and instruct the developer to use two values) +#define AFEventParamPreferredNeighborhoods @"af_preferred_neighborhoods" //array of string +#define AFEventParamPreferredNumStops @"af_preferred_num_stops" + +/// Mail hashing type +typedef enum { + /// None + EmailCryptTypeNone = 0, + /// SHA256 + EmailCryptTypeSHA256 = 3 +} EmailCryptType; + +typedef NS_CLOSED_ENUM(NSInteger, AFSDKPlugin) { + AFSDKPluginIOSNative, + AFSDKPluginUnity, + AFSDKPluginFlutter, + AFSDKPluginReactNative, + AFSDKPluginAdobeAir, + AFSDKPluginAdobeMobile, + AFSDKPluginCocos2dx, + AFSDKPluginCordova, + AFSDKPluginMparticle, + AFSDKPluginNativeScript, + AFSDKPluginExpo, + AFSDKPluginUnreal, + AFSDKPluginXamarin, + AFSDKPluginCapacitor, + AFSDKPluginSegment, + AFSDKPluginAdobeSwiftAEP +} NS_SWIFT_NAME(Plugin); + + +NS_SWIFT_NAME(DeepLinkDelegate) +@protocol AppsFlyerDeepLinkDelegate + +@optional +- (void)didResolveDeepLink:(AppsFlyerDeepLinkResult *_Nonnull)result; + +@end + +/** + Conform and subscribe to this protocol to allow getting data about conversion and + install attribution + */ +@protocol AppsFlyerLibDelegate + +/** + `conversionInfo` contains information about install. + Organic/non-organic, etc. + @param conversionInfo May contain null values for some keys. Please handle this case. + */ +- (void)onConversionDataSuccess:(NSDictionary *)conversionInfo; + +/** + Any errors that occurred during the conversion request. + */ +- (void)onConversionDataFail:(NSError *)error; + +@optional + +/** + `attributionData` contains information about OneLink, deeplink. + */ +- (void)onAppOpenAttribution:(NSDictionary *)attributionData; + +/** + Any errors that occurred during the attribution request. + */ +- (void)onAppOpenAttributionFailure:(NSError *)error; + +/** + @abstract Sets the HTTP header fields of the ESP resolving to the given + dictionary. + @discussion This method replaces all header fields that may have + existed before this method ESP resolving call. + To keep default SDK behavior - return nil; + */ +- (NSDictionary * _Nullable)allHTTPHeaderFieldsForResolveDeepLinkURL:(NSURL *)URL; + +@end + +/** + You can log installs, app updates, sessions and additional in-app events + (including in-app purchases, game levels, etc.) + to evaluate ROI and user engagement. + The iOS SDK is compatible with all iOS/tvOS devices with iOS version 7 and above. + + @see [SDK Integration Validator](https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS) + for more information. + + */ +@interface AppsFlyerLib : NSObject + +/** + Gets the singleton instance of the AppsFlyerLib class, creating it if + necessary. + + @return The singleton instance of AppsFlyerLib. + */ ++ (AppsFlyerLib *)shared; + + +- (void)setUpInteroperabilityObject:(id)object; + +/** + In case you use your own user ID in your app, you can set this property to that ID. + Enables you to cross-reference your own unique ID with AppsFlyer’s unique ID and the other devices’ IDs + */ +@property(nonatomic, strong, nullable) NSString * customerUserID; + +/** + In case you use custom data and you want to receive it in the raw reports. + + @see [Setting additional custom data](https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS#setting-additional-custom-data) for more information. + */ +@property(nonatomic, strong, nullable, setter = setAdditionalData:) NSDictionary * customData; + +/** + Use this property to set your AppsFlyer's dev key + */ +@property(nonatomic, strong) NSString * appsFlyerDevKey; + +/** + Use this property to set your app's Apple ID(taken from the app's page on iTunes Connect) + */ +@property(nonatomic, strong) NSString * appleAppID; + +#ifndef AFSDK_NO_IDFA +/** + AppsFlyer SDK collect Apple's `advertisingIdentifier` if the `AdSupport.framework` included in the SDK. + You can disable this behavior by setting the following property to YES +*/ +@property(nonatomic) BOOL disableAdvertisingIdentifier; + +@property(nonatomic, strong, readonly) NSString *advertisingIdentifier; + +/** + Waits for request user authorization to access app-related data + */ +- (void)waitForATTUserAuthorizationWithTimeoutInterval:(NSTimeInterval)timeoutInterval +NS_SWIFT_NAME(waitForATTUserAuthorization(timeoutInterval:)); + +#endif + +@property(nonatomic) BOOL disableSKAdNetwork; + +/** + In case of in app purchase events, you can set the currency code your user has purchased with. + The currency code is a 3 letter code according to ISO standards + + Objective-C: + +
+ [[AppsFlyerLib shared] setCurrencyCode:@"USD"];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().currencyCode = "USD"
+ 
+ */ +@property(nonatomic, strong, nullable) NSString *currencyCode; + +/** + Prints SDK messages to the console log. This property should only be used in `DEBUG` mode. + The default value is `NO` + */ +@property(nonatomic) BOOL isDebug; + +/** + Set this flag to `YES`, to collect the current device name(e.g. "My iPhone"). Default value is `NO` + */ +@property(nonatomic) BOOL shouldCollectDeviceName; + +/** + Set your `OneLink ID` from OneLink configuration. Used in User Invites to generate a OneLink. + */ +@property(nonatomic, strong, nullable, setter = setAppInviteOneLink:) NSString * appInviteOneLinkID; + +/** + Opt-out logging for specific user + */ +@property(atomic) BOOL anonymizeUser; + +/** + Opt-out for Apple Search Ads attributions + */ +@property(atomic) BOOL disableCollectASA; + +/** + Disable Apple Ads Attribution API +[AAAtribution attributionTokenWithError:] + */ +@property(nonatomic) BOOL disableAppleAdsAttribution; + +/** + AppsFlyer delegate. See `AppsFlyerLibDelegate` + */ +@property(weak, nonatomic) id delegate; + +@property(weak, nonatomic) id deepLinkDelegate; + +/** + In app purchase receipt validation Apple environment(production or sandbox). The default value is NO + */ +@property(nonatomic) BOOL useReceiptValidationSandbox; + +/** + Set this flag to test uninstall on Apple environment(production or sandbox). The default value is NO + */ +@property(nonatomic) BOOL useUninstallSandbox; + +/** + For advertisers who wrap OneLink within another Universal Link. + An advertiser will be able to deeplink from a OneLink wrapped within another Universal Link and also log this retargeting conversion. + + Objective-C: + +
+ [[AppsFlyerLib shared] setResolveDeepLinkURLs:@[@"domain.com", @"subdomain.domain.com"]];
+ 
+ */ +@property(nonatomic, nullable, copy) NSArray *resolveDeepLinkURLs; + +/** + For advertisers who use vanity OneLinks. + + Objective-C: + +
+ [[AppsFlyerLib shared] oneLinkCustomDomains:@[@"domain.com", @"subdomain.domain.com"]];
+ 
+ */ +@property(nonatomic, nullable, copy) NSArray *oneLinkCustomDomains; + +/* + * Set phone number for each `start` event. `phoneNumber` will be sent as SHA256 string + */ +@property(nonatomic, nullable, copy) NSString *phoneNumber; + +- (NSString *)phoneNumber UNAVAILABLE_ATTRIBUTE; + +/** + To disable app's vendor identifier(IDFV), set disableIDFVCollection to true + */ +@property(nonatomic) BOOL disableIDFVCollection; + +/** + Set the language of the device. The data will be displayed in Raw Data Reports + Objective-C: + +
+ [[AppsFlyerLib shared] setCurrentDeviceLanguage:@"EN"]
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().currentDeviceLanguage("EN")
+ 
+ */ +@property(nonatomic, nullable, copy) NSString *currentDeviceLanguage; + +/** + Internal API. Please don't use. + */ +- (void)setPluginInfoWith:(AFSDKPlugin)plugin + pluginVersion:(NSString *)version + additionalParams:(NSDictionary * _Nullable)additionalParams +NS_SWIFT_NAME(setPluginInfo(plugin:version:additionalParams:)); + +/** + Enable the collection of Facebook Deferred AppLinks + Requires Facebook SDK and Facebook app on target/client device. + This API must be invoked prior to initializing the AppsFlyer SDK in order to function properly. + + Objective-C: + +
+ [[AppsFlyerLib shared] enableFacebookDeferredApplinksWithClass:[FBSDKAppLinkUtility class]]
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().enableFacebookDeferredApplinks(with: FBSDKAppLinkUtility.self)
+ 
+ + @param facebookAppLinkUtilityClass requeries method call `[FBSDKAppLinkUtility class]` as param. + */ +- (void)enableFacebookDeferredApplinksWithClass:(Class _Nullable)facebookAppLinkUtilityClass; + +/** + Use this to send the user's emails + + @param userEmails The list of strings that hold mails + @param type Hash algoritm + */ +- (void)setUserEmails:(NSArray * _Nullable)userEmails withCryptType:(EmailCryptType)type; + +/** + Start SDK session + Add the following method at the `applicationDidBecomeActive` in AppDelegate class + */ +- (void)start; + +- (void)startWithCompletionHandler:(void (^ _Nullable)(NSDictionary * _Nullable dictionary, NSError * _Nullable error))completionHandler; + +/** + Use this method to log an events with multiple values. See AppsFlyer's documentation for details. + + Objective-C: + +
+ [[AppsFlyerLib shared] logEvent:AFEventPurchase
+        withValues: @{AFEventParamRevenue  : @200,
+                      AFEventParamCurrency : @"USD",
+                      AFEventParamQuantity : @2,
+                      AFEventParamContentId: @"092",
+                      AFEventParamReceiptId: @"9277"}];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().logEvent(AFEventPurchase,
+        withValues: [AFEventParamRevenue  : "1200",
+                     AFEventParamContent  : "shoes",
+                     AFEventParamContentId: "123"])
+ 
+ + @param eventName Contains name of event that could be provided from predefined constants in `AppsFlyerLib.h` + @param values Contains dictionary of values for handling by backend + */ +- (void)logEvent:(NSString *)eventName withValues:(NSDictionary * _Nullable)values; + +- (void)logEventWithEventName:(NSString *)eventName + eventValues:(NSDictionary * _Nullable)eventValues + completionHandler:(void (^ _Nullable)(NSDictionary * _Nullable dictionary, NSError * _Nullable error))completionHandler +NS_SWIFT_NAME(logEvent(name:values:completionHandler:)); + +/** + To log and validate in app purchases you can call this method from the completeTransaction: method on + your `SKPaymentTransactionObserver`. + + @param productIdentifier The product identifier + @param price The product price + @param currency The product currency + @param transactionId The purchase transaction Id + @param params The additional param, which you want to receive it in the raw reports + @param successBlock The success callback + @param failedBlock The failure callback + */ +- (void)validateAndLogInAppPurchase:(NSString * _Nullable)productIdentifier + price:(NSString * _Nullable)price + currency:(NSString * _Nullable)currency + transactionId:(NSString * _Nullable)transactionId + additionalParameters:(NSDictionary * _Nullable)params + success:(void (^ _Nullable)(NSDictionary * response))successBlock + failure:(void (^ _Nullable)(NSError * _Nullable error, id _Nullable reponse))failedBlock NS_AVAILABLE(10_7, 7_0); + +typedef void (^AFSDKValidateAndLogCompletion)(AFSDKValidateAndLogResult * _Nullable result); + +/** + To log and validate in app purchases you can call this method from the completeTransaction: method on + your `SKPaymentTransactionObserver`. + + @param details The product details + @param extraEventValues The additional param, which you want to receive it in the raw reports + @param completionHandler The callback + */ +- (void)validateAndLogInAppPurchase:(AFSDKPurchaseDetails *)details + extraEventValues:(NSDictionary * _Nullable)extraEventValues + completionHandler:(AFSDKValidateAndLogCompletion)completionHandler NS_AVAILABLE(10_7, 7_0); + +/** + An API to provide the data from the impression payload to AdRevenue. + + @param adRevenueData object used to hold all mandatory parameters of AdRevenue event. + @param additionalParameters non-mandatory dictionary which can include pre-defined keys (kAppsFlyerAdRevenueCountry, etc) + */ +- (void)logAdRevenue:(AFAdRevenueData *)adRevenueData additionalParameters:(NSDictionary * _Nullable)additionalParameters; + +/** + To log location for geo-fencing. Does the same as code below. + +
+ AppsFlyerLib.shared().logEvent(AFEventLocation, withValues: [AFEventParamLong:longitude, AFEventParamLat:latitude])
+ 
+ + @param longitude The location longitude + @param latitude The location latitude + */ +- (void)logLocation:(double)longitude latitude:(double)latitude NS_SWIFT_NAME(logLocation(longitude:latitude:)); + +/** + This method returns AppsFlyer's internal id(unique for your app) + + @return Internal AppsFlyer Id + */ +- (NSString *)getAppsFlyerUID; + +/** + In case you want to log deep linking. Does the same as `-handleOpenURL:sourceApplication:withAnnotation`. + + @warning Preferred to use `-handleOpenURL:sourceApplication:withAnnotation`. + + @param url The URL that was passed to your AppDelegate. + @param sourceApplication The sourceApplication that passed to your AppDelegate. + */ +- (void)handleOpenURL:(NSURL * _Nullable)url sourceApplication:(NSString * _Nullable)sourceApplication API_UNAVAILABLE(macos); + +/** + In case you want to log deep linking. + Call this method from inside your AppDelegate `-application:openURL:sourceApplication:annotation:` + + @param url The URL that was passed to your AppDelegate. + @param sourceApplication The sourceApplication that passed to your AppDelegate. + @param annotation The annotation that passed to your app delegate. + */ +- (void)handleOpenURL:(NSURL * _Nullable)url + sourceApplication:(NSString * _Nullable)sourceApplication + withAnnotation:(id _Nullable)annotation API_UNAVAILABLE(macos); + +/** + Call this method from inside of your AppDelegate `-application:openURL:options:` method. + This method is functionally the same as calling the AppsFlyer method + `-handleOpenURL:sourceApplication:withAnnotation`. + + @param url The URL that was passed to your app delegate + @param options The options dictionary that was passed to your AppDelegate. + */ +- (void)handleOpenUrl:(NSURL * _Nullable)url options:(NSDictionary * _Nullable)options API_UNAVAILABLE(macos); + +/** + Allow AppsFlyer to handle restoration from an NSUserActivity. + Use this method to log deep links with OneLink. + + @param userActivity The NSUserActivity that caused the app to be opened. + */ +- (BOOL)continueUserActivity:(NSUserActivity * _Nullable)userActivity + restorationHandler:(void (^ _Nullable)(NSArray * _Nullable))restorationHandler NS_AVAILABLE_IOS(9_0) API_UNAVAILABLE(macos); + +/** + Enable AppsFlyer to handle a push notification. + + @see [Learn more here](https://support.appsflyer.com/hc/en-us/articles/207364076-Measuring-Push-Notification-Re-Engagement-Campaigns) + + @warning To make it work - set data, related to AppsFlyer under key @"af". + + @param pushPayload The `userInfo` from received remote notification. One of root keys should be @"af". + */ +- (void)handlePushNotification:(NSDictionary * _Nullable)pushPayload; + + +/** + Register uninstall - you should register for remote notification and provide AppsFlyer the push device token. + + @param deviceToken The `deviceToken` from `-application:didRegisterForRemoteNotificationsWithDeviceToken:` + */ +- (void)registerUninstall:(NSData * _Nullable)deviceToken; + +/** + Get SDK version. + + @return The AppsFlyer SDK version info. + */ +- (NSString *)getSDKVersion; + +/** + This is for internal use. + */ +- (void)remoteDebuggingCallWithData:(NSString *)data; + +/** + This is for internal use. + */ +- (void)remoteDebuggingCallV2WithData:(NSString *)dataAsString; + +/** + Used to force the trigger `onAppOpenAttribution` delegate. + Notice, re-engagement, session and launch won't be counted. + Only for OneLink/UniversalLink/Deeplink resolving. + + @param URL The param to resolve into -[AppsFlyerLibDelegate onAppOpenAttribution:] + */ +- (void)performOnAppAttributionWithURL:(NSURL * _Nullable)URL; + +/** + @brief This property accepts a string value representing the host name for all endpoints. + Can be used to Zero rate your application’s data usage. Contact your CSM for more information. + + @warning To use `default` SDK endpoint – set value to `nil`. + + Objective-C: + +
+ [[AppsFlyerLib shared] setHost:@"example.com"];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().host = "example.com"
+ 
+ */ +@property(nonatomic, strong, readonly) NSString *host; + +/** + * This function set the host name and prefix host name for all the endpoints + **/ +- (void)setHost:(NSString *)host withHostPrefix:(NSString *)hostPrefix; + +/** + * This property accepts a string value representing the prefix host name for all endpoints. + * for example "test" prefix with default host name will have the address "host.appsflyer.com" + */ +@property(nonatomic, strong, readonly) NSString *hostPrefix; + +/** + This property is responsible for timeout between sessions in seconds. + Default value is 5 seconds. + */ +@property(atomic) NSUInteger minTimeBetweenSessions; + +/** + API to shut down all SDK activities. + + @warning This will disable all requests from AppsFlyer SDK. + */ +@property(atomic) BOOL isStopped; + +/** + API to set manually Facebook deferred app link + */ +@property(nonatomic, nullable, copy) NSURL *facebookDeferredAppLink; + +/** + Block an events from being shared with ad networks and other 3rd party integrations + Must only include letters/digits or underscore, maximum length: 45 + */ +@property(nonatomic, nullable, copy) NSArray *sharingFilter DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); + +@property(nonatomic) NSUInteger deepLinkTimeout; + +/** + Block an events from being shared with any partner + This method overwrite -[AppsFlyerLib setSharingFilter:] + */ +- (void)setSharingFilterForAllPartners DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); + +/** + Block an events from being shared with ad networks and other 3rd party integrations + Must only include letters/digits or underscore, maximum length: 45 + + The sharing filter is cleared in case if `nil` or empty array passed as a parameter. + "all" keyword sets sharing filter for ALL partners, it is case insencitive and has highest priority + if passed along with another values. For example, if ["all", "examplePartner1_int", "examplePartner2_int" ] passed, + the sharing filter will be set for ALL partners. + */ +- (void)setSharingFilterForPartners:(NSArray * _Nullable)sharingFilter; + + +/** + Sets or updates the user consent data related to GDPR and DMA regulations for advertising and data usage + purposes within the application. This method must be invoked with the user's current consent status each + time the app starts or whenever there is a change in the user's consent preferences. + + Note that this method does not persist the consent data across app sessions; it only applies for the + duration of the current app session. If you wish to stop providing the consent data, you should + cease calling this method. + + @param consent an instance of AppsFlyerConsent that encapsulates the user's consent information. + */ +- (void)setConsentData:(AppsFlyerConsent *)consent; + +/** + Enable the SDK to collect and send TCF data + + @param shouldCollectConsentData indicates if the TCF data collection is enabled. + */ +- (void)enableTCFDataCollection:(BOOL)shouldCollectConsentData; + +/** + Validate if URL contains certain string and append quiery + parameters to deeplink URL. In case if URL does not contain user-defined string, + parameters are not appended to the url. + + @param containsString string to check in URL. + @param parameters NSDictionary, which containins parameters to append to the deeplink url after it passed validation. + */ +- (void)appendParametersToDeepLinkingURLWithString:(NSString *)containsString + parameters:(NSDictionary *)parameters +NS_SWIFT_NAME(appendParametersToDeeplinkURL(contains:parameters:)); + +/** + Adds array of keys, which are used to compose key path + to resolve deeplink from push notification payload `userInfo`. + + @param deepLinkPath an array of strings which contains keys to search for deeplink in payload. + */ +- (void)addPushNotificationDeepLinkPath:(NSArray *)deepLinkPath; + +/** + * Allows sending custom data for partner integration purposes. + * + * @param partnerId ID of the partner (usually has "_int" suffix) + * @param partnerInfo customer data, depends on the integration nature with specific partner + */ + +- (void)setPartnerDataWithPartnerId:(NSString * _Nullable)partnerId partnerInfo:(NSDictionary * _Nullable)partnerInfo +NS_SWIFT_NAME(setPartnerData(partnerId:partnerInfo:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h new file mode 100644 index 000000000..d3ec8f4e4 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h @@ -0,0 +1,52 @@ +// +// LinkGenerator.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Payload container for the `generateInviteUrlWithLinkGenerator:completionHandler:` from `AppsFlyerShareInviteHelper` + */ +@interface AppsFlyerLinkGenerator : NSObject + +/// Instance initialization is not allowed. Use generated instance +/// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` +- (instancetype)init NS_UNAVAILABLE; +/// Instance initialization is not allowed. Use generated instance +/// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` ++ (instancetype)new NS_UNAVAILABLE; + +@property(nonatomic, nullable, copy) NSString *brandDomain; + +/// The channel through which the invite was sent (e.g. Facebook/Gmail/etc.). Usage: Recommended +- (void)setChannel :(nonnull NSString *)channel; +/// ReferrerCustomerId setter +- (void)setReferrerCustomerId:(nonnull NSString *)referrerCustomerId; +/// A campaign name. Usage: Optional +- (void)setCampaign :(nonnull NSString *)campaign; +/// ReferrerUID setter +- (void)setReferrerUID :(nonnull NSString *)referrerUID; +/// Referrer name +- (void)setReferrerName :(nonnull NSString *)referrerName; +/// The URL to referrer user avatar. Usage: Optional +- (void)setReferrerImageURL :(nonnull NSString *)referrerImageURL; +/// AppleAppID +- (void)setAppleAppID :(nonnull NSString *)appleAppID; +/// Deeplink path +- (void)setDeeplinkPath :(nonnull NSString *)deeplinkPath; +/// Base deeplink path +- (void)setBaseDeeplink :(nonnull NSString *)baseDeeplink; +/// A single key value custom parameter. Usage: Optional +- (void)addParameterValue :(nonnull NSString *)value forKey:(NSString *)key; +/// Multiple key value custom parameters. Usage: Optional +- (void)addParameters :(nonnull NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h new file mode 100644 index 000000000..f55dbf9d0 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h @@ -0,0 +1,35 @@ +// +// ShareInviteHelper.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import +#import + +/** + AppsFlyerShareInviteHelper + */ +@interface AppsFlyerShareInviteHelper : NSObject + +NS_ASSUME_NONNULL_BEGIN + +/** + * The AppsFlyerShareInviteHelper class builds the invite URL according to various setter methods + * which allow passing on additional information on the click. + * This information is available through `onConversionDataReceived:` when the user accepts the invite and installs the app. + * In addition, campaign and channel parameters are visible within the AppsFlyer Dashboard. + */ ++ (void)generateInviteUrlWithLinkGenerator:(AppsFlyerLinkGenerator *(^)(AppsFlyerLinkGenerator *generator))generatorCreator completionHandler:(void (^)(NSURL *_Nullable url))completionHandler; + +/** + * It is recommended to generate an in-app event after the invite is sent to log the invites from the senders' perspective. + * This enables you to find the users that tend most to invite friends, and the media sources that get you these users. + */ ++ (void)logInvite:(nullable NSString *)channel parameters:(nullable NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Info.plist b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Info.plist new file mode 100644 index 000000000..981cba9d9 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Info.plist differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.abi.json b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.abi.json new file mode 100644 index 000000000..4d7c1be56 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.abi.json @@ -0,0 +1,458 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "TopLevel", + "printedName": "TopLevel", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKConnectorCommunication", + "printedName": "AFSDKConnectorCommunication", + "children": [ + { + "kind": "Function", + "name": "setPurchaseDataSendingDelegate", + "printedName": "setPurchaseDataSendingDelegate(delegate:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AppsFlyerLib.AFSDKPurchaseDataSendingDelegate", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate" + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication(im)setPurchaseDataSendingDelegateWithDelegate:", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP30setPurchaseDataSendingDelegate8delegateyAA013AFSDKPurchasehiJ0_p_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKConnectorCommunication>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "setPurchaseDataSendingDelegateWithDelegate:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AFSDKPurchaseDataSendingDelegate", + "children": [ + { + "kind": "Function", + "name": "sendDecryptReceiptRequest", + "printedName": "sendDecryptReceiptRequest(environment:receipt:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP25sendDecryptReceiptRequest11environment7receipt17completionHandlerySS_SSySDys11AnyHashableVypGSg_s5Error_pSgtctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendARSRequest", + "printedName": "sendARSRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendARSRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP14sendARSRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendARSRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendVIAPRequest", + "printedName": "sendVIAPRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendVIAPRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP15sendVIAPRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendVIAPRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendCachedPurchaseConnectorEvents", + "printedName": "sendCachedPurchaseConnectorEvents(completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "() -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP33sendCachedPurchaseConnectorEvents17completionHandleryyyc_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.private.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.private.swiftinterface new file mode 100644 index 000000000..526f18fa0 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.private.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target arm64-apple-tvos12.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.swiftdoc b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.swiftdoc new file mode 100644 index 000000000..cafd932b0 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.swiftdoc differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.swiftinterface new file mode 100644 index 000000000..526f18fa0 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target arm64-apple-tvos12.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/module.modulemap b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/module.modulemap new file mode 100644 index 000000000..abf5a4827 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/Modules/module.modulemap @@ -0,0 +1,11 @@ +framework module AppsFlyerLib { + umbrella header "AppsFlyerLib.h" + + export * + module * { export * } +} + +module AppsFlyerLib.Swift { + header "AppsFlyerLib-Swift.h" + requires objc +} diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/PrivacyInfo.xcprivacy b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..5da2889f7 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64/AppsFlyerLib.framework/PrivacyInfo.xcprivacy @@ -0,0 +1,73 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyTrackingDomains + + att-attr.whappsflyer.com + att-attr.appsflyer-cn.com + att-attr.hevents.appsflyer-cn.com + att-launches.whappsflyer.com + att-launches.appsflyer-cn.com + att-launches.hevents.appsflyer-cn.com + att-conversions.hevents.appsflyer-cn.com + att-conversions.appsflyer-cn.com + att-conversions.whappsflyer.com + att-dlsdk.hevents.appsflyer-cn.com + att-dlsdk.appsflyer-cn.com + att-dlsdk.whappsflyer.com + att-dlsdk.appsflyersdk.com + att-conversions.appsflyersdk.com + att-launches.appsflyersdk.com + att-attr.appsflyersdk.com + + NSPrivacyTracking + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + NSPrivacyAccessedAPITypeReasons + + C617.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + + + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/AppsFlyerLib b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/AppsFlyerLib new file mode 100644 index 000000000..8d04397d6 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/AppsFlyerLib differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFAdRevenueData.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFAdRevenueData.h new file mode 100644 index 000000000..2025468a3 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFAdRevenueData.h @@ -0,0 +1,68 @@ +// +// AFAdRevenueData.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 26/06/2024. +// + +typedef NS_CLOSED_ENUM(NSUInteger, AppsFlyerAdRevenueMediationNetworkType) { + AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob = 1, + AppsFlyerAdRevenueMediationNetworkTypeIronSource = 2, + AppsFlyerAdRevenueMediationNetworkTypeApplovinMax= 3, + AppsFlyerAdRevenueMediationNetworkTypeFyber = 4, + AppsFlyerAdRevenueMediationNetworkTypeAppodeal = 5, + AppsFlyerAdRevenueMediationNetworkTypeAdmost = 6, + AppsFlyerAdRevenueMediationNetworkTypeTopon = 7, + AppsFlyerAdRevenueMediationNetworkTypeTradplus = 8, + AppsFlyerAdRevenueMediationNetworkTypeYandex = 9, + AppsFlyerAdRevenueMediationNetworkTypeChartBoost = 10, + AppsFlyerAdRevenueMediationNetworkTypeUnity = 11, + AppsFlyerAdRevenueMediationNetworkTypeToponPte = 12, + AppsFlyerAdRevenueMediationNetworkTypeCustom = 13, + AppsFlyerAdRevenueMediationNetworkTypeDirectMonetization = 14 +} NS_SWIFT_NAME(MediationNetworkType); + +#define kAppsFlyerAdRevenueMonetizationNetwork @"monetization_network" +#define kAppsFlyerAdRevenueMediationNetwork @"mediation_network" +#define kAppsFlyerAdRevenueEventRevenue @"event_revenue" +#define kAppsFlyerAdRevenueEventRevenueCurrency @"event_revenue_currency" +#define kAppsFlyerAdRevenueCustomParameters @"custom_parameters" +#define kAFADRWrapperTypeGeneric @"adrevenue_sdk" + +//Pre-defined keys for non-mandatory dictionary + +//Code ISO 3166-1 format +#define kAppsFlyerAdRevenueCountry @"country" + +//ID of the ad unit for the impression +#define kAppsFlyerAdRevenueAdUnit @"ad_unit" + +//Format of the ad +#define kAppsFlyerAdRevenueAdType @"ad_type" + +//ID of the ad placement for the impression +#define kAppsFlyerAdRevenuePlacement @"placement" + + +@interface AFAdRevenueData : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (strong, nonnull, nonatomic) NSString *monetizationNetwork; +@property AppsFlyerAdRevenueMediationNetworkType mediationNetwork; +@property (strong, nonnull, nonatomic) NSString *currencyIso4217Code; +@property (strong, nonnull, nonatomic) NSNumber *eventRevenue; + +/** +* @param monetizationNetwork network which monetized the impression (@"facebook") +* @param mediationNetwork mediation source that mediated the monetization network for the impression (AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob) +* @param currencyIso4217Code reported impression’s revenue currency ISO 4217 format (@"USD") +* @param eventRevenue reported impression’s revenue (@(0.001994303)) +*/ +- (instancetype _Nonnull )initWithMonetizationNetwork:(NSString *_Nonnull)monetizationNetwork + mediationNetwork:(AppsFlyerAdRevenueMediationNetworkType)mediationNetwork + currencyIso4217Code:(NSString *_Nonnull)currencyIso4217Code + eventRevenue:(NSNumber *_Nonnull)eventRevenue; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h new file mode 100644 index 000000000..89af8a702 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKPurchaseDetails.h @@ -0,0 +1,23 @@ +// +// AFSDKPurchaseDetails.h +// AppsFlyerLib +// +// Created by Moris Gateno on 13/03/2024. +// + +@interface AFSDKPurchaseDetails : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (strong, nullable, nonatomic) NSString *productId; +@property (strong, nullable, nonatomic) NSString *price; +@property (strong, nullable, nonatomic) NSString *currency; +@property (strong, nullable, nonatomic) NSString *transactionId; + +- (instancetype _Nonnull )initWithProductId:(NSString *_Nullable)productId + price:(NSString *_Nullable)price + currency:(NSString *_Nullable)currency + transactionId:(NSString *_Nullable)transactionId; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h new file mode 100644 index 000000000..94ef0f4c2 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AFSDKValidateAndLogResult.h @@ -0,0 +1,35 @@ +// +// AFSDKValidateAndLogResult.h +// AppsFlyerLib +// +// Created by Moris Gateno on 13/03/2024. +// + + +typedef NS_CLOSED_ENUM(NSUInteger, AFSDKValidateAndLogStatus) { + AFSDKValidateAndLogStatusSuccess, + AFSDKValidateAndLogStatusFailure, + AFSDKValidateAndLogStatusError +} NS_SWIFT_NAME(ValidateAndLogStatus); + +NS_SWIFT_NAME(ValidateAndLogResult) +@interface AFSDKValidateAndLogResult : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +- (instancetype _Nonnull )initWithStatus:(AFSDKValidateAndLogStatus)status + result:(NSDictionary *_Nullable)result + errorData:(NSDictionary *_Nullable)errorData + error:(NSError *_Nullable)error; + +@property(readonly) AFSDKValidateAndLogStatus status; +// Success case +@property(readonly, nullable) NSDictionary *result; +// Server 200 with validation failure +@property(readonly, nullable) NSDictionary *errorData; +// for the error case +@property(readonly, nullable) NSError *error; + +@end + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h new file mode 100644 index 000000000..564912a92 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerConsent.h @@ -0,0 +1,26 @@ +// +// AppsFlyerConsent.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 14/01/2024. +// +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AppsFlyerConsent : NSObject + +@property (nonatomic, readonly, assign) BOOL isUserSubjectToGDPR; +@property (nonatomic, readonly, assign) BOOL hasConsentForDataUsage; +@property (nonatomic, readonly, assign) BOOL hasConsentForAdsPersonalization; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (instancetype)initForGDPRUserWithHasConsentForDataUsage:(BOOL)hasConsentForDataUsage + hasConsentForAdsPersonalization:(BOOL)hasConsentForAdsPersonalization NS_DESIGNATED_INITIALIZER; +- (instancetype)initNonGDPRUser NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h new file mode 100644 index 000000000..483f8f863 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerCrossPromotionHelper.h @@ -0,0 +1,49 @@ +// +// CrossPromotionHelper.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + AppsFlyer allows you to log and attribute installs originating + from cross promotion campaigns of your existing apps. + Afterwards, you can optimize on your cross-promotion traffic to get even better results. + */ +@interface AppsFlyerCrossPromotionHelper : NSObject + +/** + To log an impression use the following API call. + Make sure to use the promoted App ID as it appears within the AppsFlyer dashboard. + + @param appID Promoted App ID + @param campaign A campaign name + @param parameters Additional params like `@{@"af_sub1": @"val", @"custom_param": @"val2" }` +*/ ++ (void)logCrossPromoteImpression:(nonnull NSString *)appID + campaign:(nullable NSString *)campaign + parameters:(nullable NSDictionary *)parameters; + +/** + iOS allows you to utilize the StoreKit component to open + the App Store while remaining in the context of your app. + More details at https://support.appsflyer.com/hc/en-us/articles/115004481946-Cross-Promotion-Tracking#tracking-cross-promotion-impressions + + @param appID Promoted App ID + @param campaign A campaign name + @param parameters Additional params like `@{@"af_sub1": @"val", @"custom_param": @"val2" }` + @param openStoreBlock Contains promoted `clickURL` + */ ++ (void)logAndOpenStore:(nonnull NSString *)appID + campaign:(nullable NSString *)campaign + parameters:(nullable NSDictionary *)parameters + openStore:(void (^)(NSURLSession *urlSession, NSURL *clickURL))openStoreBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h new file mode 100644 index 000000000..f099aceb9 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLink.h @@ -0,0 +1,36 @@ +// +// AFSDKDeeplink.h +// AppsFlyerLib +// +// Created by Andrii Hahan on 20.08.2020. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(DeepLink) +@interface AppsFlyerDeepLink : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property (readonly, nonnull) NSDictionary *clickEvent; +@property (readonly, nullable) NSString *deeplinkValue; +@property (readonly, nullable) NSString *matchType; +@property (readonly, nullable) NSString *clickHTTPReferrer; +@property (readonly, nullable) NSString *mediaSource; +@property (readonly, nullable) NSString *campaign; +@property (readonly, nullable) NSString *campaignId; +@property (readonly, nullable) NSString *afSub1; +@property (readonly, nullable) NSString *afSub2; +@property (readonly, nullable) NSString *afSub3; +@property (readonly, nullable) NSString *afSub4; +@property (readonly, nullable) NSString *afSub5; +@property (readonly) BOOL isDeferred; + +- (NSString *)toString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h new file mode 100644 index 000000000..50d41d71e --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerDeepLinkResult.h @@ -0,0 +1,29 @@ +// +// AFSDKDeeplinkResult.h +// AppsFlyerLib +// +// Created by Andrii Hahan on 20.08.2020. +// + +#import + +@class AppsFlyerDeepLink; + +typedef NS_CLOSED_ENUM(NSUInteger, AFSDKDeepLinkResultStatus) { + AFSDKDeepLinkResultStatusNotFound, + AFSDKDeepLinkResultStatusFound, + AFSDKDeepLinkResultStatusFailure, +} NS_SWIFT_NAME(DeepLinkResultStatus); + +NS_SWIFT_NAME(DeepLinkResult) +@interface AppsFlyerDeepLinkResult : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; + +@property(readonly) AFSDKDeepLinkResultStatus status; + +@property(readonly, nullable) AppsFlyerDeepLink *deepLink; +@property(readonly, nullable) NSError *error; + +@end diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h new file mode 100644 index 000000000..4155b266d --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib-Swift.h @@ -0,0 +1,618 @@ +#if 0 +#elif defined(__arm64__) && __arm64__ +// Generated by Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +#ifndef APPSFLYERLIB_SWIFT_H +#define APPSFLYERLIB_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="AppsFlyerLib",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#elif defined(__x86_64__) && __x86_64__ +// Generated by Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +#ifndef APPSFLYERLIB_SWIFT_H +#define APPSFLYERLIB_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="AppsFlyerLib",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif + +#else +#error unsupported Swift architecture +#endif diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib.h new file mode 100644 index 000000000..182fe6e91 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLib.h @@ -0,0 +1,743 @@ +// +// AppsFlyerLib.h +// AppsFlyerLib +// +// AppsFlyer iOS SDK 6.15.1 (211) +// Copyright (c) 2012-2023 AppsFlyer Ltd. All rights reserved. +// + +#import + +#import +#import +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +// In app event names constants +#define AFEventLevelAchieved @"af_level_achieved" +#define AFEventAddPaymentInfo @"af_add_payment_info" +#define AFEventAddToCart @"af_add_to_cart" +#define AFEventAddToWishlist @"af_add_to_wishlist" +#define AFEventCompleteRegistration @"af_complete_registration" +#define AFEventTutorial_completion @"af_tutorial_completion" +#define AFEventInitiatedCheckout @"af_initiated_checkout" +#define AFEventPurchase @"af_purchase" +#define AFEventRate @"af_rate" +#define AFEventSearch @"af_search" +#define AFEventSpentCredits @"af_spent_credits" +#define AFEventAchievementUnlocked @"af_achievement_unlocked" +#define AFEventContentView @"af_content_view" +#define AFEventListView @"af_list_view" +#define AFEventTravelBooking @"af_travel_booking" +#define AFEventShare @"af_share" +#define AFEventInvite @"af_invite" +#define AFEventLogin @"af_login" +#define AFEventReEngage @"af_re_engage" +#define AFEventUpdate @"af_update" +#define AFEventOpenedFromPushNotification @"af_opened_from_push_notification" +#define AFEventLocation @"af_location_coordinates" +#define AFEventCustomerSegment @"af_customer_segment" + +#define AFEventSubscribe @"af_subscribe" +#define AFEventStartTrial @"af_start_trial" +#define AFEventAdClick @"af_ad_click" +#define AFEventAdView @"af_ad_view" + +// In app event parameter names +#define AFEventParamContent @"af_content" +#define AFEventParamAchievementId @"af_achievement_id" +#define AFEventParamLevel @"af_level" +#define AFEventParamScore @"af_score" +#define AFEventParamSuccess @"af_success" +#define AFEventParamPrice @"af_price" +#define AFEventParamContentType @"af_content_type" +#define AFEventParamContentId @"af_content_id" +#define AFEventParamContentList @"af_content_list" +#define AFEventParamCurrency @"af_currency" +#define AFEventParamQuantity @"af_quantity" +#define AFEventParamRegistrationMethod @"af_registration_method" +#define AFEventParamPaymentInfoAvailable @"af_payment_info_available" +#define AFEventParamMaxRatingValue @"af_max_rating_value" +#define AFEventParamRatingValue @"af_rating_value" +#define AFEventParamSearchString @"af_search_string" +#define AFEventParamDateA @"af_date_a" +#define AFEventParamDateB @"af_date_b" +#define AFEventParamDestinationA @"af_destination_a" +#define AFEventParamDestinationB @"af_destination_b" +#define AFEventParamDescription @"af_description" +#define AFEventParamClass @"af_class" +#define AFEventParamEventStart @"af_event_start" +#define AFEventParamEventEnd @"af_event_end" +#define AFEventParamLat @"af_lat" +#define AFEventParamLong @"af_long" +#define AFEventParamCustomerUserId @"af_customer_user_id" +#define AFEventParamValidated @"af_validated" +#define AFEventParamRevenue @"af_revenue" +#define AFEventProjectedParamRevenue @"af_projected_revenue" +#define AFEventParamReceiptId @"af_receipt_id" +#define AFEventParamTutorialId @"af_tutorial_id" +#define AFEventParamVirtualCurrencyName @"af_virtual_currency_name" +#define AFEventParamDeepLink @"af_deep_link" +#define AFEventParamOldVersion @"af_old_version" +#define AFEventParamNewVersion @"af_new_version" +#define AFEventParamReviewText @"af_review_text" +#define AFEventParamCouponCode @"af_coupon_code" +#define AFEventParamOrderId @"af_order_id" +#define AFEventParam1 @"af_param_1" +#define AFEventParam2 @"af_param_2" +#define AFEventParam3 @"af_param_3" +#define AFEventParam4 @"af_param_4" +#define AFEventParam5 @"af_param_5" +#define AFEventParam6 @"af_param_6" +#define AFEventParam7 @"af_param_7" +#define AFEventParam8 @"af_param_8" +#define AFEventParam9 @"af_param_9" +#define AFEventParam10 @"af_param_10" +#define AFEventParamTouch @"af_touch_obj" + +#define AFEventParamDepartingDepartureDate @"af_departing_departure_date" +#define AFEventParamReturningDepartureDate @"af_returning_departure_date" +#define AFEventParamDestinationList @"af_destination_list" //array of string +#define AFEventParamCity @"af_city" +#define AFEventParamRegion @"af_region" +#define AFEventParamCountry @"af_country" + + +#define AFEventParamDepartingArrivalDate @"af_departing_arrival_date" +#define AFEventParamReturningArrivalDate @"af_returning_arrival_date" +#define AFEventParamSuggestedDestinations @"af_suggested_destinations" //array of string +#define AFEventParamTravelStart @"af_travel_start" +#define AFEventParamTravelEnd @"af_travel_end" +#define AFEventParamNumAdults @"af_num_adults" +#define AFEventParamNumChildren @"af_num_children" +#define AFEventParamNumInfants @"af_num_infants" +#define AFEventParamSuggestedHotels @"af_suggested_hotels" //array of string + +#define AFEventParamUserScore @"af_user_score" +#define AFEventParamHotelScore @"af_hotel_score" +#define AFEventParamPurchaseCurrency @"af_purchase_currency" + +#define AFEventParamPreferredStarRatings @"af_preferred_star_ratings" //array of int (basically a tuple (min,max) but we'll use array of int and instruct the developer to use two values) + +#define AFEventParamPreferredPriceRange @"af_preferred_price_range" //array of int (basically a tuple (min,max) but we'll use array of int and instruct the developer to use two values) +#define AFEventParamPreferredNeighborhoods @"af_preferred_neighborhoods" //array of string +#define AFEventParamPreferredNumStops @"af_preferred_num_stops" + +/// Mail hashing type +typedef enum { + /// None + EmailCryptTypeNone = 0, + /// SHA256 + EmailCryptTypeSHA256 = 3 +} EmailCryptType; + +typedef NS_CLOSED_ENUM(NSInteger, AFSDKPlugin) { + AFSDKPluginIOSNative, + AFSDKPluginUnity, + AFSDKPluginFlutter, + AFSDKPluginReactNative, + AFSDKPluginAdobeAir, + AFSDKPluginAdobeMobile, + AFSDKPluginCocos2dx, + AFSDKPluginCordova, + AFSDKPluginMparticle, + AFSDKPluginNativeScript, + AFSDKPluginExpo, + AFSDKPluginUnreal, + AFSDKPluginXamarin, + AFSDKPluginCapacitor, + AFSDKPluginSegment, + AFSDKPluginAdobeSwiftAEP +} NS_SWIFT_NAME(Plugin); + + +NS_SWIFT_NAME(DeepLinkDelegate) +@protocol AppsFlyerDeepLinkDelegate + +@optional +- (void)didResolveDeepLink:(AppsFlyerDeepLinkResult *_Nonnull)result; + +@end + +/** + Conform and subscribe to this protocol to allow getting data about conversion and + install attribution + */ +@protocol AppsFlyerLibDelegate + +/** + `conversionInfo` contains information about install. + Organic/non-organic, etc. + @param conversionInfo May contain null values for some keys. Please handle this case. + */ +- (void)onConversionDataSuccess:(NSDictionary *)conversionInfo; + +/** + Any errors that occurred during the conversion request. + */ +- (void)onConversionDataFail:(NSError *)error; + +@optional + +/** + `attributionData` contains information about OneLink, deeplink. + */ +- (void)onAppOpenAttribution:(NSDictionary *)attributionData; + +/** + Any errors that occurred during the attribution request. + */ +- (void)onAppOpenAttributionFailure:(NSError *)error; + +/** + @abstract Sets the HTTP header fields of the ESP resolving to the given + dictionary. + @discussion This method replaces all header fields that may have + existed before this method ESP resolving call. + To keep default SDK behavior - return nil; + */ +- (NSDictionary * _Nullable)allHTTPHeaderFieldsForResolveDeepLinkURL:(NSURL *)URL; + +@end + +/** + You can log installs, app updates, sessions and additional in-app events + (including in-app purchases, game levels, etc.) + to evaluate ROI and user engagement. + The iOS SDK is compatible with all iOS/tvOS devices with iOS version 7 and above. + + @see [SDK Integration Validator](https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS) + for more information. + + */ +@interface AppsFlyerLib : NSObject + +/** + Gets the singleton instance of the AppsFlyerLib class, creating it if + necessary. + + @return The singleton instance of AppsFlyerLib. + */ ++ (AppsFlyerLib *)shared; + + +- (void)setUpInteroperabilityObject:(id)object; + +/** + In case you use your own user ID in your app, you can set this property to that ID. + Enables you to cross-reference your own unique ID with AppsFlyer’s unique ID and the other devices’ IDs + */ +@property(nonatomic, strong, nullable) NSString * customerUserID; + +/** + In case you use custom data and you want to receive it in the raw reports. + + @see [Setting additional custom data](https://support.appsflyer.com/hc/en-us/articles/207032066-AppsFlyer-SDK-Integration-iOS#setting-additional-custom-data) for more information. + */ +@property(nonatomic, strong, nullable, setter = setAdditionalData:) NSDictionary * customData; + +/** + Use this property to set your AppsFlyer's dev key + */ +@property(nonatomic, strong) NSString * appsFlyerDevKey; + +/** + Use this property to set your app's Apple ID(taken from the app's page on iTunes Connect) + */ +@property(nonatomic, strong) NSString * appleAppID; + +#ifndef AFSDK_NO_IDFA +/** + AppsFlyer SDK collect Apple's `advertisingIdentifier` if the `AdSupport.framework` included in the SDK. + You can disable this behavior by setting the following property to YES +*/ +@property(nonatomic) BOOL disableAdvertisingIdentifier; + +@property(nonatomic, strong, readonly) NSString *advertisingIdentifier; + +/** + Waits for request user authorization to access app-related data + */ +- (void)waitForATTUserAuthorizationWithTimeoutInterval:(NSTimeInterval)timeoutInterval +NS_SWIFT_NAME(waitForATTUserAuthorization(timeoutInterval:)); + +#endif + +@property(nonatomic) BOOL disableSKAdNetwork; + +/** + In case of in app purchase events, you can set the currency code your user has purchased with. + The currency code is a 3 letter code according to ISO standards + + Objective-C: + +
+ [[AppsFlyerLib shared] setCurrencyCode:@"USD"];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().currencyCode = "USD"
+ 
+ */ +@property(nonatomic, strong, nullable) NSString *currencyCode; + +/** + Prints SDK messages to the console log. This property should only be used in `DEBUG` mode. + The default value is `NO` + */ +@property(nonatomic) BOOL isDebug; + +/** + Set this flag to `YES`, to collect the current device name(e.g. "My iPhone"). Default value is `NO` + */ +@property(nonatomic) BOOL shouldCollectDeviceName; + +/** + Set your `OneLink ID` from OneLink configuration. Used in User Invites to generate a OneLink. + */ +@property(nonatomic, strong, nullable, setter = setAppInviteOneLink:) NSString * appInviteOneLinkID; + +/** + Opt-out logging for specific user + */ +@property(atomic) BOOL anonymizeUser; + +/** + Opt-out for Apple Search Ads attributions + */ +@property(atomic) BOOL disableCollectASA; + +/** + Disable Apple Ads Attribution API +[AAAtribution attributionTokenWithError:] + */ +@property(nonatomic) BOOL disableAppleAdsAttribution; + +/** + AppsFlyer delegate. See `AppsFlyerLibDelegate` + */ +@property(weak, nonatomic) id delegate; + +@property(weak, nonatomic) id deepLinkDelegate; + +/** + In app purchase receipt validation Apple environment(production or sandbox). The default value is NO + */ +@property(nonatomic) BOOL useReceiptValidationSandbox; + +/** + Set this flag to test uninstall on Apple environment(production or sandbox). The default value is NO + */ +@property(nonatomic) BOOL useUninstallSandbox; + +/** + For advertisers who wrap OneLink within another Universal Link. + An advertiser will be able to deeplink from a OneLink wrapped within another Universal Link and also log this retargeting conversion. + + Objective-C: + +
+ [[AppsFlyerLib shared] setResolveDeepLinkURLs:@[@"domain.com", @"subdomain.domain.com"]];
+ 
+ */ +@property(nonatomic, nullable, copy) NSArray *resolveDeepLinkURLs; + +/** + For advertisers who use vanity OneLinks. + + Objective-C: + +
+ [[AppsFlyerLib shared] oneLinkCustomDomains:@[@"domain.com", @"subdomain.domain.com"]];
+ 
+ */ +@property(nonatomic, nullable, copy) NSArray *oneLinkCustomDomains; + +/* + * Set phone number for each `start` event. `phoneNumber` will be sent as SHA256 string + */ +@property(nonatomic, nullable, copy) NSString *phoneNumber; + +- (NSString *)phoneNumber UNAVAILABLE_ATTRIBUTE; + +/** + To disable app's vendor identifier(IDFV), set disableIDFVCollection to true + */ +@property(nonatomic) BOOL disableIDFVCollection; + +/** + Set the language of the device. The data will be displayed in Raw Data Reports + Objective-C: + +
+ [[AppsFlyerLib shared] setCurrentDeviceLanguage:@"EN"]
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().currentDeviceLanguage("EN")
+ 
+ */ +@property(nonatomic, nullable, copy) NSString *currentDeviceLanguage; + +/** + Internal API. Please don't use. + */ +- (void)setPluginInfoWith:(AFSDKPlugin)plugin + pluginVersion:(NSString *)version + additionalParams:(NSDictionary * _Nullable)additionalParams +NS_SWIFT_NAME(setPluginInfo(plugin:version:additionalParams:)); + +/** + Enable the collection of Facebook Deferred AppLinks + Requires Facebook SDK and Facebook app on target/client device. + This API must be invoked prior to initializing the AppsFlyer SDK in order to function properly. + + Objective-C: + +
+ [[AppsFlyerLib shared] enableFacebookDeferredApplinksWithClass:[FBSDKAppLinkUtility class]]
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().enableFacebookDeferredApplinks(with: FBSDKAppLinkUtility.self)
+ 
+ + @param facebookAppLinkUtilityClass requeries method call `[FBSDKAppLinkUtility class]` as param. + */ +- (void)enableFacebookDeferredApplinksWithClass:(Class _Nullable)facebookAppLinkUtilityClass; + +/** + Use this to send the user's emails + + @param userEmails The list of strings that hold mails + @param type Hash algoritm + */ +- (void)setUserEmails:(NSArray * _Nullable)userEmails withCryptType:(EmailCryptType)type; + +/** + Start SDK session + Add the following method at the `applicationDidBecomeActive` in AppDelegate class + */ +- (void)start; + +- (void)startWithCompletionHandler:(void (^ _Nullable)(NSDictionary * _Nullable dictionary, NSError * _Nullable error))completionHandler; + +/** + Use this method to log an events with multiple values. See AppsFlyer's documentation for details. + + Objective-C: + +
+ [[AppsFlyerLib shared] logEvent:AFEventPurchase
+        withValues: @{AFEventParamRevenue  : @200,
+                      AFEventParamCurrency : @"USD",
+                      AFEventParamQuantity : @2,
+                      AFEventParamContentId: @"092",
+                      AFEventParamReceiptId: @"9277"}];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().logEvent(AFEventPurchase,
+        withValues: [AFEventParamRevenue  : "1200",
+                     AFEventParamContent  : "shoes",
+                     AFEventParamContentId: "123"])
+ 
+ + @param eventName Contains name of event that could be provided from predefined constants in `AppsFlyerLib.h` + @param values Contains dictionary of values for handling by backend + */ +- (void)logEvent:(NSString *)eventName withValues:(NSDictionary * _Nullable)values; + +- (void)logEventWithEventName:(NSString *)eventName + eventValues:(NSDictionary * _Nullable)eventValues + completionHandler:(void (^ _Nullable)(NSDictionary * _Nullable dictionary, NSError * _Nullable error))completionHandler +NS_SWIFT_NAME(logEvent(name:values:completionHandler:)); + +/** + To log and validate in app purchases you can call this method from the completeTransaction: method on + your `SKPaymentTransactionObserver`. + + @param productIdentifier The product identifier + @param price The product price + @param currency The product currency + @param transactionId The purchase transaction Id + @param params The additional param, which you want to receive it in the raw reports + @param successBlock The success callback + @param failedBlock The failure callback + */ +- (void)validateAndLogInAppPurchase:(NSString * _Nullable)productIdentifier + price:(NSString * _Nullable)price + currency:(NSString * _Nullable)currency + transactionId:(NSString * _Nullable)transactionId + additionalParameters:(NSDictionary * _Nullable)params + success:(void (^ _Nullable)(NSDictionary * response))successBlock + failure:(void (^ _Nullable)(NSError * _Nullable error, id _Nullable reponse))failedBlock NS_AVAILABLE(10_7, 7_0); + +typedef void (^AFSDKValidateAndLogCompletion)(AFSDKValidateAndLogResult * _Nullable result); + +/** + To log and validate in app purchases you can call this method from the completeTransaction: method on + your `SKPaymentTransactionObserver`. + + @param details The product details + @param extraEventValues The additional param, which you want to receive it in the raw reports + @param completionHandler The callback + */ +- (void)validateAndLogInAppPurchase:(AFSDKPurchaseDetails *)details + extraEventValues:(NSDictionary * _Nullable)extraEventValues + completionHandler:(AFSDKValidateAndLogCompletion)completionHandler NS_AVAILABLE(10_7, 7_0); + +/** + An API to provide the data from the impression payload to AdRevenue. + + @param adRevenueData object used to hold all mandatory parameters of AdRevenue event. + @param additionalParameters non-mandatory dictionary which can include pre-defined keys (kAppsFlyerAdRevenueCountry, etc) + */ +- (void)logAdRevenue:(AFAdRevenueData *)adRevenueData additionalParameters:(NSDictionary * _Nullable)additionalParameters; + +/** + To log location for geo-fencing. Does the same as code below. + +
+ AppsFlyerLib.shared().logEvent(AFEventLocation, withValues: [AFEventParamLong:longitude, AFEventParamLat:latitude])
+ 
+ + @param longitude The location longitude + @param latitude The location latitude + */ +- (void)logLocation:(double)longitude latitude:(double)latitude NS_SWIFT_NAME(logLocation(longitude:latitude:)); + +/** + This method returns AppsFlyer's internal id(unique for your app) + + @return Internal AppsFlyer Id + */ +- (NSString *)getAppsFlyerUID; + +/** + In case you want to log deep linking. Does the same as `-handleOpenURL:sourceApplication:withAnnotation`. + + @warning Preferred to use `-handleOpenURL:sourceApplication:withAnnotation`. + + @param url The URL that was passed to your AppDelegate. + @param sourceApplication The sourceApplication that passed to your AppDelegate. + */ +- (void)handleOpenURL:(NSURL * _Nullable)url sourceApplication:(NSString * _Nullable)sourceApplication API_UNAVAILABLE(macos); + +/** + In case you want to log deep linking. + Call this method from inside your AppDelegate `-application:openURL:sourceApplication:annotation:` + + @param url The URL that was passed to your AppDelegate. + @param sourceApplication The sourceApplication that passed to your AppDelegate. + @param annotation The annotation that passed to your app delegate. + */ +- (void)handleOpenURL:(NSURL * _Nullable)url + sourceApplication:(NSString * _Nullable)sourceApplication + withAnnotation:(id _Nullable)annotation API_UNAVAILABLE(macos); + +/** + Call this method from inside of your AppDelegate `-application:openURL:options:` method. + This method is functionally the same as calling the AppsFlyer method + `-handleOpenURL:sourceApplication:withAnnotation`. + + @param url The URL that was passed to your app delegate + @param options The options dictionary that was passed to your AppDelegate. + */ +- (void)handleOpenUrl:(NSURL * _Nullable)url options:(NSDictionary * _Nullable)options API_UNAVAILABLE(macos); + +/** + Allow AppsFlyer to handle restoration from an NSUserActivity. + Use this method to log deep links with OneLink. + + @param userActivity The NSUserActivity that caused the app to be opened. + */ +- (BOOL)continueUserActivity:(NSUserActivity * _Nullable)userActivity + restorationHandler:(void (^ _Nullable)(NSArray * _Nullable))restorationHandler NS_AVAILABLE_IOS(9_0) API_UNAVAILABLE(macos); + +/** + Enable AppsFlyer to handle a push notification. + + @see [Learn more here](https://support.appsflyer.com/hc/en-us/articles/207364076-Measuring-Push-Notification-Re-Engagement-Campaigns) + + @warning To make it work - set data, related to AppsFlyer under key @"af". + + @param pushPayload The `userInfo` from received remote notification. One of root keys should be @"af". + */ +- (void)handlePushNotification:(NSDictionary * _Nullable)pushPayload; + + +/** + Register uninstall - you should register for remote notification and provide AppsFlyer the push device token. + + @param deviceToken The `deviceToken` from `-application:didRegisterForRemoteNotificationsWithDeviceToken:` + */ +- (void)registerUninstall:(NSData * _Nullable)deviceToken; + +/** + Get SDK version. + + @return The AppsFlyer SDK version info. + */ +- (NSString *)getSDKVersion; + +/** + This is for internal use. + */ +- (void)remoteDebuggingCallWithData:(NSString *)data; + +/** + This is for internal use. + */ +- (void)remoteDebuggingCallV2WithData:(NSString *)dataAsString; + +/** + Used to force the trigger `onAppOpenAttribution` delegate. + Notice, re-engagement, session and launch won't be counted. + Only for OneLink/UniversalLink/Deeplink resolving. + + @param URL The param to resolve into -[AppsFlyerLibDelegate onAppOpenAttribution:] + */ +- (void)performOnAppAttributionWithURL:(NSURL * _Nullable)URL; + +/** + @brief This property accepts a string value representing the host name for all endpoints. + Can be used to Zero rate your application’s data usage. Contact your CSM for more information. + + @warning To use `default` SDK endpoint – set value to `nil`. + + Objective-C: + +
+ [[AppsFlyerLib shared] setHost:@"example.com"];
+ 
+ + Swift: + +
+ AppsFlyerLib.shared().host = "example.com"
+ 
+ */ +@property(nonatomic, strong, readonly) NSString *host; + +/** + * This function set the host name and prefix host name for all the endpoints + **/ +- (void)setHost:(NSString *)host withHostPrefix:(NSString *)hostPrefix; + +/** + * This property accepts a string value representing the prefix host name for all endpoints. + * for example "test" prefix with default host name will have the address "host.appsflyer.com" + */ +@property(nonatomic, strong, readonly) NSString *hostPrefix; + +/** + This property is responsible for timeout between sessions in seconds. + Default value is 5 seconds. + */ +@property(atomic) NSUInteger minTimeBetweenSessions; + +/** + API to shut down all SDK activities. + + @warning This will disable all requests from AppsFlyer SDK. + */ +@property(atomic) BOOL isStopped; + +/** + API to set manually Facebook deferred app link + */ +@property(nonatomic, nullable, copy) NSURL *facebookDeferredAppLink; + +/** + Block an events from being shared with ad networks and other 3rd party integrations + Must only include letters/digits or underscore, maximum length: 45 + */ +@property(nonatomic, nullable, copy) NSArray *sharingFilter DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); + +@property(nonatomic) NSUInteger deepLinkTimeout; + +/** + Block an events from being shared with any partner + This method overwrite -[AppsFlyerLib setSharingFilter:] + */ +- (void)setSharingFilterForAllPartners DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); + +/** + Block an events from being shared with ad networks and other 3rd party integrations + Must only include letters/digits or underscore, maximum length: 45 + + The sharing filter is cleared in case if `nil` or empty array passed as a parameter. + "all" keyword sets sharing filter for ALL partners, it is case insencitive and has highest priority + if passed along with another values. For example, if ["all", "examplePartner1_int", "examplePartner2_int" ] passed, + the sharing filter will be set for ALL partners. + */ +- (void)setSharingFilterForPartners:(NSArray * _Nullable)sharingFilter; + + +/** + Sets or updates the user consent data related to GDPR and DMA regulations for advertising and data usage + purposes within the application. This method must be invoked with the user's current consent status each + time the app starts or whenever there is a change in the user's consent preferences. + + Note that this method does not persist the consent data across app sessions; it only applies for the + duration of the current app session. If you wish to stop providing the consent data, you should + cease calling this method. + + @param consent an instance of AppsFlyerConsent that encapsulates the user's consent information. + */ +- (void)setConsentData:(AppsFlyerConsent *)consent; + +/** + Enable the SDK to collect and send TCF data + + @param shouldCollectConsentData indicates if the TCF data collection is enabled. + */ +- (void)enableTCFDataCollection:(BOOL)shouldCollectConsentData; + +/** + Validate if URL contains certain string and append quiery + parameters to deeplink URL. In case if URL does not contain user-defined string, + parameters are not appended to the url. + + @param containsString string to check in URL. + @param parameters NSDictionary, which containins parameters to append to the deeplink url after it passed validation. + */ +- (void)appendParametersToDeepLinkingURLWithString:(NSString *)containsString + parameters:(NSDictionary *)parameters +NS_SWIFT_NAME(appendParametersToDeeplinkURL(contains:parameters:)); + +/** + Adds array of keys, which are used to compose key path + to resolve deeplink from push notification payload `userInfo`. + + @param deepLinkPath an array of strings which contains keys to search for deeplink in payload. + */ +- (void)addPushNotificationDeepLinkPath:(NSArray *)deepLinkPath; + +/** + * Allows sending custom data for partner integration purposes. + * + * @param partnerId ID of the partner (usually has "_int" suffix) + * @param partnerInfo customer data, depends on the integration nature with specific partner + */ + +- (void)setPartnerDataWithPartnerId:(NSString * _Nullable)partnerId partnerInfo:(NSDictionary * _Nullable)partnerInfo +NS_SWIFT_NAME(setPartnerData(partnerId:partnerInfo:)); + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h new file mode 100644 index 000000000..d3ec8f4e4 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerLinkGenerator.h @@ -0,0 +1,52 @@ +// +// LinkGenerator.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Payload container for the `generateInviteUrlWithLinkGenerator:completionHandler:` from `AppsFlyerShareInviteHelper` + */ +@interface AppsFlyerLinkGenerator : NSObject + +/// Instance initialization is not allowed. Use generated instance +/// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` +- (instancetype)init NS_UNAVAILABLE; +/// Instance initialization is not allowed. Use generated instance +/// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` ++ (instancetype)new NS_UNAVAILABLE; + +@property(nonatomic, nullable, copy) NSString *brandDomain; + +/// The channel through which the invite was sent (e.g. Facebook/Gmail/etc.). Usage: Recommended +- (void)setChannel :(nonnull NSString *)channel; +/// ReferrerCustomerId setter +- (void)setReferrerCustomerId:(nonnull NSString *)referrerCustomerId; +/// A campaign name. Usage: Optional +- (void)setCampaign :(nonnull NSString *)campaign; +/// ReferrerUID setter +- (void)setReferrerUID :(nonnull NSString *)referrerUID; +/// Referrer name +- (void)setReferrerName :(nonnull NSString *)referrerName; +/// The URL to referrer user avatar. Usage: Optional +- (void)setReferrerImageURL :(nonnull NSString *)referrerImageURL; +/// AppleAppID +- (void)setAppleAppID :(nonnull NSString *)appleAppID; +/// Deeplink path +- (void)setDeeplinkPath :(nonnull NSString *)deeplinkPath; +/// Base deeplink path +- (void)setBaseDeeplink :(nonnull NSString *)baseDeeplink; +/// A single key value custom parameter. Usage: Optional +- (void)addParameterValue :(nonnull NSString *)value forKey:(NSString *)key; +/// Multiple key value custom parameters. Usage: Optional +- (void)addParameters :(nonnull NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h new file mode 100644 index 000000000..f55dbf9d0 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Headers/AppsFlyerShareInviteHelper.h @@ -0,0 +1,35 @@ +// +// ShareInviteHelper.h +// AppsFlyerLib +// +// Created by Gil Meroz on 27/01/2017. +// +// + +#import +#import + +/** + AppsFlyerShareInviteHelper + */ +@interface AppsFlyerShareInviteHelper : NSObject + +NS_ASSUME_NONNULL_BEGIN + +/** + * The AppsFlyerShareInviteHelper class builds the invite URL according to various setter methods + * which allow passing on additional information on the click. + * This information is available through `onConversionDataReceived:` when the user accepts the invite and installs the app. + * In addition, campaign and channel parameters are visible within the AppsFlyer Dashboard. + */ ++ (void)generateInviteUrlWithLinkGenerator:(AppsFlyerLinkGenerator *(^)(AppsFlyerLinkGenerator *generator))generatorCreator completionHandler:(void (^)(NSURL *_Nullable url))completionHandler; + +/** + * It is recommended to generate an in-app event after the invite is sent to log the invites from the senders' perspective. + * This enables you to find the users that tend most to invite friends, and the media sources that get you these users. + */ ++ (void)logInvite:(nullable NSString *)channel parameters:(nullable NSDictionary *)parameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Info.plist b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Info.plist new file mode 100644 index 000000000..d99af4223 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Info.plist differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.abi.json b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.abi.json new file mode 100644 index 000000000..4d7c1be56 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.abi.json @@ -0,0 +1,458 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "TopLevel", + "printedName": "TopLevel", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKConnectorCommunication", + "printedName": "AFSDKConnectorCommunication", + "children": [ + { + "kind": "Function", + "name": "setPurchaseDataSendingDelegate", + "printedName": "setPurchaseDataSendingDelegate(delegate:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AppsFlyerLib.AFSDKPurchaseDataSendingDelegate", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate" + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication(im)setPurchaseDataSendingDelegateWithDelegate:", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP30setPurchaseDataSendingDelegate8delegateyAA013AFSDKPurchasehiJ0_p_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKConnectorCommunication>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "setPurchaseDataSendingDelegateWithDelegate:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AFSDKPurchaseDataSendingDelegate", + "children": [ + { + "kind": "Function", + "name": "sendDecryptReceiptRequest", + "printedName": "sendDecryptReceiptRequest(environment:receipt:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP25sendDecryptReceiptRequest11environment7receipt17completionHandlerySS_SSySDys11AnyHashableVypGSg_s5Error_pSgtctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendARSRequest", + "printedName": "sendARSRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendARSRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP14sendARSRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendARSRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendVIAPRequest", + "printedName": "sendVIAPRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendVIAPRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP15sendVIAPRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendVIAPRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendCachedPurchaseConnectorEvents", + "printedName": "sendCachedPurchaseConnectorEvents(completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "() -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP33sendCachedPurchaseConnectorEvents17completionHandleryyyc_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.private.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.private.swiftinterface new file mode 100644 index 000000000..ec89281af --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.private.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target arm64-apple-tvos12.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftdoc b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftdoc new file mode 100644 index 000000000..21fdb56b7 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftdoc differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftinterface new file mode 100644 index 000000000..ec89281af --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target arm64-apple-tvos12.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.abi.json b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.abi.json new file mode 100644 index 000000000..4d7c1be56 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.abi.json @@ -0,0 +1,458 @@ +{ + "ABIRoot": { + "kind": "Root", + "name": "TopLevel", + "printedName": "TopLevel", + "children": [ + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKConnectorCommunication", + "printedName": "AFSDKConnectorCommunication", + "children": [ + { + "kind": "Function", + "name": "setPurchaseDataSendingDelegate", + "printedName": "setPurchaseDataSendingDelegate(delegate:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AppsFlyerLib.AFSDKPurchaseDataSendingDelegate", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate" + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication(im)setPurchaseDataSendingDelegateWithDelegate:", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP30setPurchaseDataSendingDelegate8delegateyAA013AFSDKPurchasehiJ0_p_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKConnectorCommunication>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "setPurchaseDataSendingDelegateWithDelegate:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKConnectorCommunication", + "mangledName": "$s12AppsFlyerLib27AFSDKConnectorCommunicationP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + }, + { + "kind": "Import", + "name": "Foundation", + "printedName": "Foundation", + "declKind": "Import", + "moduleName": "AppsFlyerLib", + "declAttributes": [ + "RawDocComment" + ] + }, + { + "kind": "TypeDecl", + "name": "AFSDKPurchaseDataSendingDelegate", + "printedName": "AFSDKPurchaseDataSendingDelegate", + "children": [ + { + "kind": "Function", + "name": "sendDecryptReceiptRequest", + "printedName": "sendDecryptReceiptRequest(environment:receipt:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP25sendDecryptReceiptRequest11environment7receipt17completionHandlerySS_SSySDys11AnyHashableVypGSg_s5Error_pSgtctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendDecryptReceiptRequestWithEnvironment:receipt:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendARSRequest", + "printedName": "sendARSRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendARSRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP14sendARSRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendARSRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendVIAPRequest", + "printedName": "sendVIAPRequest(with:completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int) -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Tuple", + "printedName": "([Swift.AnyHashable : Any]?, Swift.Error?, Swift.Int)", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "[Swift.AnyHashable : Any]?", + "children": [ + { + "kind": "TypeNominal", + "name": "Dictionary", + "printedName": "[Swift.AnyHashable : Any]", + "children": [ + { + "kind": "TypeNominal", + "name": "AnyHashable", + "printedName": "Swift.AnyHashable", + "usr": "s:s11AnyHashableV" + }, + { + "kind": "TypeNominal", + "name": "ProtocolComposition", + "printedName": "Any" + } + ], + "usr": "s:SD" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.Error?", + "children": [ + { + "kind": "TypeNominal", + "name": "Error", + "printedName": "Swift.Error", + "usr": "s:s5ErrorP" + } + ], + "usr": "s:Sq" + }, + { + "kind": "TypeNominal", + "name": "Int", + "printedName": "Swift.Int", + "usr": "s:Si" + } + ] + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendVIAPRequestWith:completionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP15sendVIAPRequest4with17completionHandlerySDys11AnyHashableVypG_yAISg_s5Error_pSgSitctF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendVIAPRequestWith:completionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + }, + { + "kind": "Function", + "name": "sendCachedPurchaseConnectorEvents", + "printedName": "sendCachedPurchaseConnectorEvents(completionHandler:)", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "() -> ()", + "children": [ + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + }, + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + } + ], + "declKind": "Func", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate(im)sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP33sendCachedPurchaseConnectorEvents17completionHandleryyyc_tF", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 where τ_0_0 : AppsFlyerLib.AFSDKPurchaseDataSendingDelegate>", + "sugared_genericSig": "", + "protocolReq": true, + "objc_name": "sendCachedPurchaseConnectorEventsWithCompletionHandler:", + "declAttributes": [ + "ObjC", + "RawDocComment" + ], + "reqNewWitnessTableEntry": true, + "funcSelfKind": "NonMutating" + } + ], + "declKind": "Protocol", + "usr": "c:@M@AppsFlyerLib@objc(pl)AFSDKPurchaseDataSendingDelegate", + "mangledName": "$s12AppsFlyerLib32AFSDKPurchaseDataSendingDelegateP", + "moduleName": "AppsFlyerLib", + "genericSig": "<τ_0_0 : ObjectiveC.NSObjectProtocol>", + "sugared_genericSig": "", + "declAttributes": [ + "ObjC" + ] + } + ], + "json_format_version": 8 + }, + "ConstValues": [] +} \ No newline at end of file diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.private.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.private.swiftinterface new file mode 100644 index 000000000..cb137c9a3 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.private.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target x86_64-apple-tvos12.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftdoc b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftdoc new file mode 100644 index 000000000..ab0b807b7 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftdoc differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftinterface b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftinterface new file mode 100644 index 000000000..cb137c9a3 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftinterface @@ -0,0 +1,10 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1) +// swift-module-flags: -target x86_64-apple-tvos12.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -module-name AppsFlyerLib +// swift-module-flags-ignorable: -enable-bare-slash-regex +@_exported import AppsFlyerLib +import Foundation +import Swift +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/module.modulemap b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/module.modulemap new file mode 100644 index 000000000..abf5a4827 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/Modules/module.modulemap @@ -0,0 +1,11 @@ +framework module AppsFlyerLib { + umbrella header "AppsFlyerLib.h" + + export * + module * { export * } +} + +module AppsFlyerLib.Swift { + header "AppsFlyerLib-Swift.h" + requires objc +} diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/PrivacyInfo.xcprivacy b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..5da2889f7 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/PrivacyInfo.xcprivacy @@ -0,0 +1,73 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyTrackingDomains + + att-attr.whappsflyer.com + att-attr.appsflyer-cn.com + att-attr.hevents.appsflyer-cn.com + att-launches.whappsflyer.com + att-launches.appsflyer-cn.com + att-launches.hevents.appsflyer-cn.com + att-conversions.hevents.appsflyer-cn.com + att-conversions.appsflyer-cn.com + att-conversions.whappsflyer.com + att-dlsdk.hevents.appsflyer-cn.com + att-dlsdk.appsflyer-cn.com + att-dlsdk.whappsflyer.com + att-dlsdk.appsflyersdk.com + att-conversions.appsflyersdk.com + att-launches.appsflyersdk.com + att-attr.appsflyersdk.com + + NSPrivacyTracking + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + NSPrivacyAccessedAPITypeReasons + + C617.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + + + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeDirectory b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeDirectory new file mode 100644 index 000000000..2d5e40334 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeDirectory differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements new file mode 100644 index 000000000..dbf9d6144 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements-1 b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements-1 new file mode 100644 index 000000000..a834d0994 Binary files /dev/null and b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeRequirements-1 differ diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeResources b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeResources new file mode 100644 index 000000000..ae04365b9 --- /dev/null +++ b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeResources @@ -0,0 +1,447 @@ + + + + + files + + Headers/AFAdRevenueData.h + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + Headers/AFSDKPurchaseDetails.h + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + Headers/AFSDKValidateAndLogResult.h + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + Headers/AppsFlyerConsent.h + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + Headers/AppsFlyerCrossPromotionHelper.h + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + Headers/AppsFlyerDeepLink.h + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + Headers/AppsFlyerDeepLinkResult.h + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + Headers/AppsFlyerLib-Swift.h + + FOoS78OxL9Z3oz6vB1wWn0PArv0= + + Headers/AppsFlyerLib.h + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + Headers/AppsFlyerLinkGenerator.h + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + Headers/AppsFlyerShareInviteHelper.h + + LysKB9CL90x+MWXOuuRPixmiVJo= + + Info.plist + + QBaoiDQ7L0WkBy8Tl2WAWvE7sEA= + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.private.swiftinterface + + L79lHVuEXcZQ8b/onXpEgE06X84= + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftdoc + + apgpLdpztYHXcPhawO2GqU71RX0= + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftinterface + + L79lHVuEXcZQ8b/onXpEgE06X84= + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftmodule + + WaSmeQ5whS//ufpUCPK60FkcMIY= + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.abi.json + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.private.swiftinterface + + NGZEK6+XnTIzCfCwhIPIg2HLBpw= + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftdoc + + a1yMUwktSjfVrmA0kgB0AdoKRiw= + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftinterface + + NGZEK6+XnTIzCfCwhIPIg2HLBpw= + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftmodule + + uDSEWfi8xIvLkmhbyBwmd1a4QeQ= + + Modules/module.modulemap + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + PrivacyInfo.xcprivacy + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + + files2 + + Headers/AFAdRevenueData.h + + hash + + sZe23Bbg0H2WgHLIqHQe0g++6Mo= + + hash2 + + yrAHizSHbgcNNFLOfLoHWfaZGyJOlAoDsri7G5c7ZiA= + + + Headers/AFSDKPurchaseDetails.h + + hash + + JFCJYWlcCsMjEbsAF0aQh2JXaKQ= + + hash2 + + UNK4h738nezlAQq+5weKs6WQUIVzBoBZIFYlfSGbhRA= + + + Headers/AFSDKValidateAndLogResult.h + + hash + + h3cMtJeCvFHGPKBZbeqwFQic6m0= + + hash2 + + 8B0SIW1N+hhdxvGum5eyxLMW5e/T797WSXOd2i+irv4= + + + Headers/AppsFlyerConsent.h + + hash + + Bx0db1e1DLeeuq3va7QJCITQH2k= + + hash2 + + VIYU7EyY+VmJF4vKtTem3hkI/fjGfSK7lYs2zq/9D4E= + + + Headers/AppsFlyerCrossPromotionHelper.h + + hash + + ezewyhf50Apa+1HjqfRFE7nXZIE= + + hash2 + + YgrwrWx/ZFYjXh2t5ZHY6S0EZTroYfe5Nprl3alq+Ho= + + + Headers/AppsFlyerDeepLink.h + + hash + + jDznIDDggwXT7EmzE7TYcZjhMjA= + + hash2 + + Z5nW/ynpNNV+krqJXqy1Gb+gdnruPFWutZYYX7hSt3I= + + + Headers/AppsFlyerDeepLinkResult.h + + hash + + 3J9juDAkVB2LUxC7/NDmKqgrzyQ= + + hash2 + + QsQGXKix5206DUBBUdDxfg5ykma/3V9MoHOxbz8NaOs= + + + Headers/AppsFlyerLib-Swift.h + + hash + + FOoS78OxL9Z3oz6vB1wWn0PArv0= + + hash2 + + s3bkkJu3kOu0GV4agwv8JVspCFp8hE12f3253m+G6T4= + + + Headers/AppsFlyerLib.h + + hash + + 0nmzbJUYI6XRn+AH+RNwtIO05sA= + + hash2 + + SQsJwXrqFChMPzjRNaG0wtOfEV17jrTEH4mn18wQQDY= + + + Headers/AppsFlyerLinkGenerator.h + + hash + + f1VXrpY1YtdmdrTgQJz1uOkVZL8= + + hash2 + + LskctIiGg0ZYNtNTDDkoSuDrB3xS58UmllXbrJDHg48= + + + Headers/AppsFlyerShareInviteHelper.h + + hash + + LysKB9CL90x+MWXOuuRPixmiVJo= + + hash2 + + PPGAG29u7O6ly0pq4HZsZxW1zcqL/viEKKBfZ6qzfdE= + + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.private.swiftinterface + + hash + + L79lHVuEXcZQ8b/onXpEgE06X84= + + hash2 + + cOSE0wgXydlxxS9iArMjee5G+repqMF7orSYGd46F18= + + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftdoc + + hash + + apgpLdpztYHXcPhawO2GqU71RX0= + + hash2 + + GXaXgMVijpVzqRSxGRa8A6GnfMvbWmArSH/utOYMMiQ= + + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftinterface + + hash + + L79lHVuEXcZQ8b/onXpEgE06X84= + + hash2 + + cOSE0wgXydlxxS9iArMjee5G+repqMF7orSYGd46F18= + + + Modules/AppsFlyerLib.swiftmodule/arm64-apple-tvos-simulator.swiftmodule + + hash + + WaSmeQ5whS//ufpUCPK60FkcMIY= + + hash2 + + KzbSImv8UyQ2Dw70mjrHQGj0/9H6XJh4KY+MeAoJrNc= + + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.abi.json + + hash + + jt+RNqV82PXEPsjsG7iLCtRvOgQ= + + hash2 + + i+H/fju9b0XDc8TSXvaSn/48OfztQY3hI5BlZyIAYfA= + + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.private.swiftinterface + + hash + + NGZEK6+XnTIzCfCwhIPIg2HLBpw= + + hash2 + + 8QNd2ehYb5yhI9CDLrfsKFZlRMrTN0C0PuiBZgvjlAQ= + + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftdoc + + hash + + a1yMUwktSjfVrmA0kgB0AdoKRiw= + + hash2 + + FuWmWrMhtQE2cz17LvjUKHUHkE940WWqqYxdRcLzemI= + + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftinterface + + hash + + NGZEK6+XnTIzCfCwhIPIg2HLBpw= + + hash2 + + 8QNd2ehYb5yhI9CDLrfsKFZlRMrTN0C0PuiBZgvjlAQ= + + + Modules/AppsFlyerLib.swiftmodule/x86_64-apple-tvos-simulator.swiftmodule + + hash + + uDSEWfi8xIvLkmhbyBwmd1a4QeQ= + + hash2 + + Lz3oAe9Ff8OAOlit4L2vFVtzC2+Cejh6oJuZavy1aJ4= + + + Modules/module.modulemap + + hash + + 0DEMhZAlM33ORMbvU0M8spnqShI= + + hash2 + + uZJ3EIEDqGw+lkbzR0fNsIrgzthOwNUTrpyMAJQZFc8= + + + PrivacyInfo.xcprivacy + + hash + + Z5diDfyJ8Q98I5JWKtVGWEvkXII= + + hash2 + + 6xKWyp82bfdpIC+qSEbhTjD40SP5BWjupWVxQg5FQkY= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeSignature b/dydx/Pods/AppsFlyerFramework/binaries/xcframework/full/AppsFlyerLib.xcframework/tvos-arm64_x86_64-simulator/AppsFlyerLib.framework/_CodeSignature/CodeSignature new file mode 100644 index 000000000..e69de29bb diff --git a/dydx/Pods/Atributika/LICENSE b/dydx/Pods/Atributika/LICENSE new file mode 100644 index 000000000..46e222ede --- /dev/null +++ b/dydx/Pods/Atributika/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2017-2023 Pavel Sharanda + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/dydx/Pods/Atributika/README.md b/dydx/Pods/Atributika/README.md new file mode 100644 index 000000000..cace790a9 --- /dev/null +++ b/dydx/Pods/Atributika/README.md @@ -0,0 +1,247 @@ + +

+ +

+
+ +**🚨V5 is now released!🚨** + +`Atributika` is a Swift library that provides a simple way to build NSAttributedString from HTML-like text, by identifying and styling tags, links, phone numbers, hashtags etc. + +A standalone `AtributikaViews` library offers UILabel/UITextView drop-in replacements capable of displaying highlightable and clickable links, with rich customization, and proper accessibility support. + +> [!NOTE] +> Try my new library for doing Auto Layout, a typesafe reimagination of Visual Format Language: +> https://github.com/psharanda/FixFlex + + +## Intro +While NSAttributedString is undoubtedly powerful, it's also a low-level API that needs a considerable amount of setup work. If your string is a template and the actual content is only known at runtime, this becomes complicated. When dealing with localizations, constructing NSAttributedString isn't straightforward either. + +But wait, `Atributika` comes to your rescue! + +```swift +let b = Attrs().font(.boldSystemFont(ofSize: 20)).foregroundColor(.red) + +label.attributedText = "Hello World!!!".style(tags: ["b": b]).attributedString +``` + + + +Indeed, that's much simpler. `Atributika` is easy-to-use, declarative, flexible, and handles the rough edges for you. + + +## Features + +Atributika ++ NSAttributedString builder. ++ Detects and styles HTML-like **tags** using a custom high-speed parser. ++ Detects and styles **hashtags** and **mentions** (i.e., #something and @someone). ++ Identifies and styles **links** and **phone numbers**. ++ Detects and styles regexes and NSDataDetector patterns. ++ Styles the entire string or just specified ranges. ++ Allows all the above to be chained together to parse complex strings! ++ Provides a clean and expressive API to construct styles. ++ Offers a separate set of detection utilities for standalone use. ++ Compatible with iOS, tvOS, watchOS, and macOS. + +AtributikaViews ++ Custom views with **highlightable and clickable** links. ++ Custom text styles for `normal/highlighted/disabled` states. ++ Supports custom highlighting. + +## V5 + +V5 is a major rewrite of the project, executed in early 2023. It's not fully compatible with the previous version and requires some manual migration. The introduction of breaking changes was necessary for the project's further evolution. + +Here's what's new: + +NSAttributedString Building ++ Completely rewritten HTML parser, which fixed a multitude of bugs and improved handling of edge cases. ++ More generic and robust text transforming and attribute fine-tuning APIs. + +AttributedLabel / AttributedTextView ++ Moved to a standalone library, independent of Atributika. ++ Offers proper accessibility support. ++ Improved performance and touch handling. ++ AttributedLabel is based on UILabel (lightweight, with vertically-centered text). ++ AttributedTextView is based on UITextView (supports scrolling and text selection, with text aligned to the top of the frame). + +New examples have been added to the Demo application, including: ++ Basic web browser powered by AttributedTextView ++ SwiftUI integration ++ Highlightable links for Markdown documents + +## Examples + +### Detect and style tags, provide base style for the rest of string, don't forget about special html symbols + +```swift +let redColor = UIColor(red:(0xD0 / 255.0), green: (0x02 / 255.0), blue:(0x1B / 255.0), alpha:1.0) +let a = Attrs().foregroundColor(redColor) + +let font = UIFont(name: "AvenirNext-Regular", size: 24)! +let grayColor = UIColor(white: 0x66 / 255.0, alpha: 1) +let base = Attrs().font(font).foregroundColor(grayColor) + +let str = "<a>tributik</a>" + .style(tags: ["a": a]) + .styleBase(base) + .attributedString +``` + + + +### Detect and style hashtags and mentions + +```swift +let str = "#Hello @World!!!" + .styleHashtags(Attrs().font(.boldSystemFont(ofSize: 45))) + .styleMentions(Attrs().foregroundColor(.red)) + .attributedString +``` + + + + +### Detect and style links + +```swift +let str = "Check this website http://google.com" + .styleLinks(Attrs().foregroundColor(.blue)) + .attributedString +``` + + + +### Detect and style phone numbers + +```swift +let str = "Call me (888)555-5512" + .stylePhoneNumbers(Attrs().foregroundColor(.red)) + .attributedString +``` + + + +### Uber String + +```swift +let links = Attrs().foregroundColor(.blue) +let phoneNumbers = Attrs().backgroundColor(.yellow) +let mentions = Attrs().font(.italicSystemFont(ofSize: 12)).foregroundColor(.black) +let b = Attrs().font(.boldSystemFont(ofSize: 12)) +let u = Attrs().underlineStyle(.single) + +let base = Attrs().font(.systemFont(ofSize: 12)).foregroundColor(.gray) + +let str = "@all I found really nice framework to manage attributed strings. It is called Atributika. Call me if you want to know more (123)456-7890 #swift #nsattributedstring https://github.com/psharanda/Atributika" + .style(tags: ["u": u, "b": b]) + .styleMentions(mentions) + .styleHashtags(links) + .styleLinks(links) + .stylePhoneNumbers(phoneNumbers) + .styleBase(base) + .attributedString +``` + + + +## AttributedLabel + +```swift + +let tweetLabel = AttributedLabel() + +tweetLabel.numberOfLines = 0 +tweetLabel.highlightedLinkAttributes = Attrs().foregroundColor(.red).attributes + +let baseLinkAttrs = Attrs().foregroundColor(.blue) + +let a = TagTuner { + Attrs(baseLinkAttrs).akaLink($0.tag.attributes["href"] ?? "") +} + +let hashtag = DetectionTuner { + Attrs(baseLinkAttrs).akaLink("https://twitter.com/hashtag/\($0.text.replacingOccurrences(of: "#", with: ""))") +} + +let mention = DetectionTuner { + Attrs(baseLinkAttrs).akaLink("https://twitter.com/\($0.text.replacingOccurrences(of: "@", with: ""))") +} + +let link = DetectionTuner { + Attrs(baseLinkAttrs).akaLink($0.text) +} + +let tweet = "@all I found really nice framework to manage attributed strings. It is called Atributika. Call me if you want to know more (123)456-7890 #swift #nsattributedstring https://github.com/psharanda/Atributika" + +tweetLabel.attributedText = tweet + .style(tags: ["a": a]) + .styleHashtags(hashtag) + .styleMentions(mention) + .styleLinks(link) + .attributedString + +tweetLabel.onLinkTouchUpInside = { _, val in + if let linkStr = val as? String { + if let url = URL(string: linkStr) { + UIApplication.shared.openURL(url) + } + } +} + +view.addSubview(tweetLabel) + +``` + + +## Requirements + +Current version is compatible with: + +* Swift 5.0+ +* iOS 11.0 or later +* tvOS 11.0 or later +* watchOS 4.0 or later +* macOS 10.13 or later + +Note: `AttributedLabel` / `AttributedTextView` are available only on iOS + +## Why does Atributika have one 't' in its name? +Because in Belarusian/Russian we have one letter 't' (атрыбутыка/атрибутика). So basically it is transcription, not a real word. + +## Integration + +### [Swift Package Manager](https://github.com/apple/swift-package-manager) + +Add dependency to `Package.swift` file. + +```swift + dependencies: [ + .package(url: "https://github.com/psharanda/Atributika.git", .upToNextMajor(from: "5.0.0")) + ] +``` + +### Carthage + +Add `github "psharanda/Atributika"` to your `Cartfile` + +### CocoaPods +Atributika is available through [CocoaPods](http://cocoapods.org). To install +it, simply add the following line to your Podfile: + +```ruby +pod "Atributika" +pod "AtributikaViews" +``` + +### Manual +1. Add Atributika to you project as a submodule using `git submodule add https://github.com/psharanda/Atributika.git` +2. Open the `Atributika` folder & drag `Atributika.xcodeproj` into your project tree +3. Add `Atributika.framework` to your target's `Link Binary with Libraries` Build Phase +4. Import Atributika with `import Atributika` and you're ready to go + +## License + +Atributika is available under the MIT license. See the LICENSE file for more info. diff --git a/dydx/Pods/Atributika/Sources/Core/AttributedStringBuilder.swift b/dydx/Pods/Atributika/Sources/Core/AttributedStringBuilder.swift new file mode 100644 index 000000000..9b42e3922 --- /dev/null +++ b/dydx/Pods/Atributika/Sources/Core/AttributedStringBuilder.swift @@ -0,0 +1,170 @@ +// +// Copyright © 2017-2023 Pavel Sharanda. All rights reserved. +// + +import Foundation + +public final class AttributedStringBuilder { + public let string: String + public private(set) var baseAttributes: AttributesProvider + + public struct AttributesRangeInfo { + public let attributes: AttributesProvider + public let range: Range + public let level: Int + + public init(attributes: AttributesProvider, range: Range, level: Int) { + self.attributes = attributes + self.range = range + self.level = level + } + } + + private var currentMaxLevel: Int = 0 + + public private(set) var attributesRangeInfo: [AttributesRangeInfo] + + public init(string: String, attributesRangeInfo: [AttributesRangeInfo], baseAttributes: AttributesProvider) { + self.string = string + self.attributesRangeInfo = attributesRangeInfo + self.baseAttributes = baseAttributes + } + + public convenience init(string: String, baseAttributes: AttributesProvider = [NSAttributedString.Key: Any]()) { + self.init(string: string, attributesRangeInfo: [], baseAttributes: baseAttributes) + } + + public convenience init(attributedString: NSAttributedString, baseAttributes: AttributesProvider = [NSAttributedString.Key: Any]()) { + let string = attributedString.string + var info: [AttributesRangeInfo] = [] + + attributedString.enumerateAttributes(in: NSMakeRange(0, attributedString.length), options: []) { attributes, range, _ in + if let range = Range(range, in: string) { + info.append(AttributesRangeInfo(attributes: attributes, range: range, level: -1)) + } + } + + self.init(string: string, attributesRangeInfo: info, baseAttributes: baseAttributes) + } + + public convenience init( + htmlString: String, + baseAttributes: AttributesProvider = [NSAttributedString.Key: Any](), + tags: [String: TagTuning] = [:] + ) { + let (string, tagsInfo) = htmlString.detectTags(tags: tags) + var info: [AttributesRangeInfo] = [] + + var newLevel = 0 + tagsInfo.forEach { t in + newLevel = max(t.level, newLevel) + if let style = tags[t.tag.name.lowercased()] { + info.append(AttributesRangeInfo(attributes: style.style(context: TagContext(tag: t.tag, outerTags: t.outerTags)), range: t.range, level: t.level)) + } + } + + self.init(string: string, attributesRangeInfo: info, baseAttributes: baseAttributes) + currentMaxLevel = newLevel + } + + public var attributedString: NSAttributedString { + let attributedString = NSMutableAttributedString(string: string, attributes: baseAttributes.attributes) + + let info = attributesRangeInfo.sorted { + $0.level < $1.level + } + + for i in info { + let attributes = i.attributes + if attributes.attributes.count > 0 { + attributedString.addAttributes(attributes.attributes, range: NSRange(i.range, in: string)) + } + } + + return attributedString + } + + public func styleBase(_ attributes: AttributesProvider) -> Self { + baseAttributes = attributes + return self + } + + public func styleHashtags(_ attributes: DetectionTuning) -> Self { + return style(ranges: string.detectHashtags(), + attributes: attributes) + } + + public func styleMentions(_ attributes: DetectionTuning) -> Self { + return style(ranges: string.detectMentions(), + attributes: attributes) + } + + public func style(regex: String, options: NSRegularExpression.Options = [], attributes: DetectionTuning) -> Self { + return style(ranges: string.detect(regex: regex, options: options), + attributes: attributes) + } + + public func style(textCheckingTypes: NSTextCheckingResult.CheckingType, attributes: DetectionTuning) -> Self { + return style(ranges: string.detect(textCheckingTypes: textCheckingTypes), + attributes: attributes) + } + + public func stylePhoneNumbers(_ attributes: DetectionTuning) -> Self { + return style(ranges: string.detectPhoneNumbers(), + attributes: attributes) + } + + public func styleLinks(_ attributes: DetectionTuning) -> Self { + return style(ranges: string.detectLinks(), + attributes: attributes) + } + + public func style(range: Range, attributes: DetectionTuning) -> Self { + return style(ranges: [range], attributes: attributes) + } + + public func style(ranges: [Range], attributes: DetectionTuning) -> Self { + currentMaxLevel += 1 + let info = ranges.map { range in + let detectionContext = DetectionContext( + range: range, + text: String(string[range]), + existingAttributes: attributesRangeInfo.compactMap { + $0.range.clamped(to: range) == range ? $0.attributes : nil + } + ) + + return AttributesRangeInfo( + attributes: attributes.style(context: detectionContext), + range: range, + level: currentMaxLevel + ) + } + + attributesRangeInfo.append(contentsOf: info) + return self + } +} + +public protocol HTMLSpecialsProvider { + func stringForHTMLSpecial(_ htmlSpecial: String) -> String? +} + +public struct DefaultHTMLSpecialsProvider: HTMLSpecialsProvider { + public func stringForHTMLSpecial(_ htmlSpecial: String) -> String? { + return HTMLSpecials[htmlSpecial].map { String($0) } + } + + private let HTMLSpecials: [String: Character] = [ + "quot": "\u{22}", + "amp": "\u{26}", + "apos": "\u{27}", + "lt": "\u{3C}", + "gt": "\u{3E}", + "nbsp": "\u{A0}", + ] +} + +public extension AttributedStringBuilder { + static var htmlSpecialsProvider: HTMLSpecialsProvider = DefaultHTMLSpecialsProvider() +} diff --git a/dydx/Pods/Atributika/Sources/Core/Attrs.swift b/dydx/Pods/Atributika/Sources/Core/Attrs.swift new file mode 100644 index 000000000..c032350c5 --- /dev/null +++ b/dydx/Pods/Atributika/Sources/Core/Attrs.swift @@ -0,0 +1,190 @@ +// +// Copyright © 2017-2023 Pavel Sharanda. All rights reserved. +// + +import Foundation + +#if os(macOS) + import AppKit +#else + import UIKit +#endif + +public protocol AttributesProvider { + var attributes: [NSAttributedString.Key: Any] { get } +} + +extension Dictionary: AttributesProvider where Key == NSAttributedString.Key, Value == Any { + public var attributes: [NSAttributedString.Key: Any] { + return self + } +} + +public final class Attrs: AttributesProvider { + public private(set) var attributes: [NSAttributedString.Key: Any] + + public init(_ attributes: AttributesProvider = [NSAttributedString.Key: Any]()) { + self.attributes = attributes.attributes + } + + public func copy() -> Attrs { + return Attrs(attributes) + } + + @discardableResult + public func attribute(_ key: NSAttributedString.Key, _ value: Any) -> Self { + attributes.updateValue(value, forKey: key) + return self + } + + @discardableResult + public func paragraphStyle(_ value: NSParagraphStyle) -> Self { + return attribute(.paragraphStyle, value) + } + + #if os(macOS) + @discardableResult + public func font(_ value: NSFont) -> Self { + return attribute(.font, value) + } + + @discardableResult + public func foregroundColor(_ value: NSColor) -> Self { + return attribute(.foregroundColor, value) + } + + @discardableResult + public func backgroundColor(_ value: NSColor) -> Self { + return attribute(.backgroundColor, value) + } + + @discardableResult + public func strikethroughColor(_ value: NSColor) -> Self { + return attribute(.strikethroughColor, value) + } + + @discardableResult + public func underlineColor(_ value: NSColor) -> Self { + return attribute(.underlineColor, value) + } + + @discardableResult + public func strokeColor(_ value: NSColor) -> Self { + return attribute(.strokeColor, value) + } + #else + @discardableResult + public func font(_ value: UIFont) -> Self { + return attribute(.font, value) + } + + @discardableResult + public func foregroundColor(_ value: UIColor) -> Self { + return attribute(.foregroundColor, value) + } + + @discardableResult + public func backgroundColor(_ value: UIColor) -> Self { + return attribute(.backgroundColor, value) + } + + @discardableResult + public func strikethroughColor(_ value: UIColor) -> Self { + return attribute(.strikethroughColor, value) + } + + @discardableResult + public func underlineColor(_ value: UIColor) -> Self { + return attribute(.underlineColor, value) + } + + @discardableResult + public func strokeColor(_ value: UIColor) -> Self { + return attribute(.strokeColor, value) + } + #endif + + @discardableResult + public func ligature(_ value: Int) -> Self { + return attribute(.ligature, value) + } + + @discardableResult + public func kern(_ value: Float) -> Self { + return attribute(.kern, value) + } + + @discardableResult + public func strikethroughStyle(_ value: NSUnderlineStyle) -> Self { + return attribute(.strikethroughStyle, value.rawValue) + } + + @discardableResult + public func underlineStyle(_ value: NSUnderlineStyle) -> Self { + return attribute(.underlineStyle, value.rawValue) + } + + @discardableResult + public func strokeWidth(_ value: Float) -> Self { + return attribute(.strokeWidth, value) + } + + #if !os(watchOS) + @discardableResult + public func shadow(_ value: NSShadow) -> Self { + return attribute(.shadow, value) + } + #endif + + @discardableResult + public func textEffect(_ value: String) -> Self { + return attribute(.textEffect, value) + } + + #if !os(watchOS) + @discardableResult + public func attachment(_ value: NSTextAttachment) -> Self { + return attribute(.attachment, value) + } + #endif + + @discardableResult + public func link(_ value: URL) -> Self { + return attribute(.link, value) + } + + @discardableResult + public func link(_ value: String) -> Self { + return attribute(.link, value) + } + + @discardableResult + public func akaLink(_ link: URL) -> Attrs { + return attribute(NSAttributedString.Key("Atributika.Link"), link) + } + + @discardableResult + public func akaLink(_ link: String) -> Attrs { + return attribute(NSAttributedString.Key("Atributika.Link"), link) + } + + @discardableResult + public func baselineOffset(_ value: Float) -> Self { + return attribute(.baselineOffset, value) + } + + @discardableResult + public func obliqueness(_ value: Float) -> Self { + return attribute(.obliqueness, value) + } + + @discardableResult + public func expansion(_ value: Float) -> Self { + return attribute(.expansion, value) + } + + @discardableResult + public func writingDirection(_ value: NSWritingDirection) -> Self { + return attribute(.writingDirection, value.rawValue) + } +} diff --git a/dydx/Pods/Atributika/Sources/Core/Detection.swift b/dydx/Pods/Atributika/Sources/Core/Detection.swift new file mode 100644 index 000000000..72b25a9a9 --- /dev/null +++ b/dydx/Pods/Atributika/Sources/Core/Detection.swift @@ -0,0 +1,48 @@ +// +// Copyright © 2017-2023 Pavel Sharanda. All rights reserved. +// + +import Foundation + +public struct DetectionContext { + public let range: Range + public let text: String + public let existingAttributes: [AttributesProvider] + + public func firstExistingAttributeValue(for key: NSAttributedString.Key) -> Any? { + for a in existingAttributes { + if let value = a.attributes[key] { + return value + } + } + return nil + } +} + +public protocol DetectionTuning { + func style(context: DetectionContext) -> AttributesProvider +} + +public struct DetectionTuner: DetectionTuning { + public func style(context: DetectionContext) -> AttributesProvider { + return _style(context) + } + + private let _style: (DetectionContext) -> AttributesProvider + + public init(style: @escaping (DetectionContext) -> AttributesProvider) { + _style = style + } +} + +extension Attrs: DetectionTuning { + public func style(context _: DetectionContext) -> AttributesProvider { + return self + } +} + +extension Dictionary: DetectionTuning where Key == NSAttributedString.Key, Value == Any { + public func style(context _: DetectionContext) -> AttributesProvider { + return self + } +} diff --git a/dydx/Pods/Atributika/Sources/Core/NSScanner+Compat.swift b/dydx/Pods/Atributika/Sources/Core/NSScanner+Compat.swift new file mode 100755 index 000000000..30817ad07 --- /dev/null +++ b/dydx/Pods/Atributika/Sources/Core/NSScanner+Compat.swift @@ -0,0 +1,85 @@ +// +// Copyright © 2017-2023 Pavel Sharanda. All rights reserved. +// + +import Foundation + +extension Scanner { + func _scanCharacters(from set: CharacterSet) -> String? { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { + return scanCharacters(from: set) + } else { + var value: NSString? = "" + if scanCharacters(from: set, into: &value) { + return value as String? + } + return nil + } + } + + func _scanUpToCharacters(from set: CharacterSet) -> String? { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { + return scanUpToCharacters(from: set) + } else { + var value: NSString? = "" + if scanUpToCharacters(from: set, into: &value) { + return value as String? + } + return nil + } + } + + func _scanString(_ str: String) -> String? { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { + return scanString(str) + } else { + var value: NSString? = "" + if scanString(str, into: &value) { + return value as String? + } + return nil + } + } + + func _scanUpToString(_ str: String) -> String? { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { + return scanUpToString(str) + } else { + var value: NSString? = "" + if scanUpTo(str, into: &value) { + return value as String? + } + return nil + } + } + + func currentCharacter() -> Character? { + guard !isAtEnd else { + return nil + } + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { + return string[currentIndex] + } else { + guard let nextCharRange = Range(NSRange(location: scanLocation, length: 0), in: string) else { + return nil + } + return string[nextCharRange.lowerBound] + } + } + + func _scanCharacter() -> Character? { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { + return scanCharacter() + } else { + guard !isAtEnd, let nextCharRange = Range(NSRange(location: scanLocation, length: 0), in: string) else { + return nil + } + let char = string[nextCharRange.lowerBound] + let charRange = nextCharRange.lowerBound ..< string.index(after: nextCharRange.lowerBound) + let nsRange = NSRange(charRange, in: string) + scanLocation += nsRange.length + + return char + } + } +} diff --git a/dydx/Pods/Atributika/Sources/Core/String+Detection.swift b/dydx/Pods/Atributika/Sources/Core/String+Detection.swift new file mode 100644 index 000000000..76174cb43 --- /dev/null +++ b/dydx/Pods/Atributika/Sources/Core/String+Detection.swift @@ -0,0 +1,54 @@ +// +// Copyright © 2017-2023 Pavel Sharanda. All rights reserved. +// + +import Foundation + +public extension String { + func detect(regex: String, options: NSRegularExpression.Options = []) -> [Range] { + var ranges = [Range]() + + let dataDetector = try? NSRegularExpression(pattern: regex, options: options) + dataDetector?.enumerateMatches( + in: self, options: [], range: NSMakeRange(0, (self as NSString).length), + using: { result, _, _ in + if let r = result, let range = Range(r.range, in: self) { + ranges.append(range) + } + } + ) + + return ranges + } + + func detectHashtags() -> [Range] { + return detect(regex: "#[^\\p{Pd}\\p{Ps}\\p{Pe}\\p{Pi}\\p{Pf}\\p{Po}\\p{Z}\\p{C}\\p{S}]+") + } + + func detectMentions() -> [Range] { + return detect(regex: "@[^\\p{Pd}\\p{Ps}\\p{Pe}\\p{Pi}\\p{Pf}\\p{Po}\\p{Z}\\p{C}\\p{S}]+") + } + + func detect(textCheckingTypes: NSTextCheckingResult.CheckingType) -> [Range] { + var ranges = [Range]() + + let dataDetector = try? NSDataDetector(types: textCheckingTypes.rawValue) + dataDetector?.enumerateMatches( + in: self, options: [], range: NSMakeRange(0, (self as NSString).length), + using: { result, _, _ in + if let r = result, let range = Range(r.range, in: self) { + ranges.append(range) + } + } + ) + return ranges + } + + func detectPhoneNumbers() -> [Range] { + return detect(textCheckingTypes: [.phoneNumber]) + } + + func detectLinks() -> [Range] { + return detect(textCheckingTypes: [.link]) + } +} diff --git a/dydx/Pods/Atributika/Sources/Core/String+HTML.swift b/dydx/Pods/Atributika/Sources/Core/String+HTML.swift new file mode 100644 index 000000000..505ccc62c --- /dev/null +++ b/dydx/Pods/Atributika/Sources/Core/String+HTML.swift @@ -0,0 +1,303 @@ +// +// Copyright © 2017-2023 Pavel Sharanda. All rights reserved. +// + +import Foundation + +public struct TagInfo: Equatable { + public let tag: Tag + public let range: Range + public let level: Int + public let outerTags: [Tag] +} + +extension String { + // MARK: - html specials + + private static let allowedTagCharacters = CharacterSet(charactersIn: ".-_").union(CharacterSet.alphanumerics) + + private func unescapeAsNumber() -> String? { + let isHexadecimal = hasPrefix("X") || hasPrefix("x") + let radix = isHexadecimal ? 16 : 10 + + let numberStartIndex = index(startIndex, offsetBy: isHexadecimal ? 1 : 0) + let numberString = String(self[numberStartIndex ..< endIndex]) + + guard let codePoint = UInt32(numberString, radix: radix), + let scalar = UnicodeScalar(codePoint) + else { + return nil + } + + return String(scalar) + } + + private func parseSpecial(_ scanner: Scanner, _ resultString: inout String) { + if scanner._scanString("#") != nil { + if let potentialSpecial = scanner._scanCharacters(from: CharacterSet.alphanumerics) { + if scanner._scanString(";") != nil { + resultString.append(potentialSpecial.unescapeAsNumber() ?? "&#\(potentialSpecial);") + } else { + resultString.append("&#\(potentialSpecial)") + } + } else { + resultString.append("&#") + } + } else { + if let potentialSpecial = scanner._scanCharacters(from: CharacterSet.letters) { + if scanner._scanString(";") != nil { + resultString.append(AttributedStringBuilder.htmlSpecialsProvider.stringForHTMLSpecial(potentialSpecial) ?? "&\(potentialSpecial);") + } else { + resultString.append("&\(potentialSpecial)") + } + } else { + resultString.append("&") + } + } + } + + // MARK: - tags + + private enum StoredStringIndex { + case index(String.Index) + case offset(Int) + } + + private struct TagStackItem { + let tag: Tag + + let startIndex: StoredStringIndex + let endIndex: String.Index + let level: Int + + let outerTags: [Tag] + + func startIndex(in string: String) -> String.Index { + switch startIndex { + case let .index(index): + return index + case let .offset(offset): + return string.index(string.startIndex, offsetBy: offset) + } + } + } + + private func parseClosingTag(_ scanner: Scanner, _ tagsStack: inout [String.TagStackItem], _ tags: [String: TagTuning], _ resultString: inout String, _ tagsInfo: inout [TagInfo]) { + _ = scanner._scanString("/") + guard let tagName = scanner._scanCharacters(from: Self.allowedTagCharacters)?.lowercased() else { + resultString.append("= startIndex { + return false + } + return true + } + } + } + + tagsInfo.append( + TagInfo( + tag: tagStackItem.tag, + range: startIndex ..< resultString.endIndex, + level: tagStackItem.level, + outerTags: tagStackItem.outerTags + )) + tagsStack.remove(at: index) + break + } + } + _ = scanner._scanUpToString(">") + _ = scanner._scanString(">") + } + + private func parseOpeningTag(_ scanner: Scanner, _ resultString: inout String, _ tags: [String: TagTuning], _ tagsStack: inout [String.TagStackItem], _ tagsInfo: inout [TagInfo]) { + let tagName = scanner._scanCharacters(from: Self.allowedTagCharacters)!.lowercased() + + var selfClosing = false + var attributes: [String: String] = [:] + + var paramName: String? + var paramValue: String? + + while !scanner.isAtEnd { + _ = scanner._scanCharacters(from: CharacterSet.whitespaces) + + guard let currentCharacter = scanner.currentCharacter() else { + break + } + + if scanner._scanString(">") != nil { + break + } else if scanner._scanString("/") != nil { + selfClosing = true + } else if scanner._scanString("=") != nil { + _ = scanner._scanCharacters(from: CharacterSet.whitespaces) + + if let quote = scanner._scanString("\"") ?? scanner._scanString("'") { + if let scannedParamValue = scanner._scanUpToCharacters(from: CharacterSet(charactersIn: quote)) { + _ = scanner._scanString(quote) + paramValue = scannedParamValue + } + } else { + paramValue = scanner._scanUpToCharacters(from: CharacterSet.whitespaces.union(CharacterSet(charactersIn: "/>"))) + } + + if let val = paramValue { + let valScanner = Scanner(string: val) + var newVal = "" + + while !valScanner.isAtEnd { + if let str = valScanner._scanUpToString("&") { + newVal.append(str) + } + + if valScanner._scanString("&") != nil { + parseSpecial(valScanner, &newVal) + } + } + + paramValue = newVal + } + + if let name = paramName, let value = paramValue { + attributes[name] = value + paramName = nil + paramValue = nil + } + } else if let firstUnicodeScalar = currentCharacter.unicodeScalars.first, Self.allowedTagCharacters.contains(firstUnicodeScalar) { + if let prevParamName = paramName { + attributes[prevParamName] = "" + paramName = nil + } + paramName = scanner._scanCharacters(from: Self.allowedTagCharacters)!.lowercased() + } else { + _ = scanner._scanCharacter() + } + } + + if let name = paramName { + attributes[name] = paramValue ?? "" + paramName = nil + paramValue = nil + } + + let startIndex = resultString.endIndex + + let tag = Tag(name: tagName, attributes: attributes) + let nextLevel = (tagsStack.last?.level ?? -1) + 1 + let outerTags = tagsStack.map { $0.tag } + + if let tuner = tags[tagName], + let str = tuner.transform(context: TagContext(tag: tag, + outerTags: outerTags), + part: .opening(selfClosing: selfClosing)) + { + resultString.append(str) + } else if tagName == "br" { + resultString.append("\n") + } + + if selfClosing { + tagsInfo.append( + TagInfo( + tag: tag, + range: startIndex ..< resultString.endIndex, + level: nextLevel, + outerTags: outerTags + )) + } else { + let storedStartIndex: StoredStringIndex + if #available(iOS 13.0, *) { + storedStartIndex = .index(startIndex) + } else { + storedStartIndex = .offset(resultString.distance(from: resultString.startIndex, to: startIndex)) + } + + tagsStack.append(TagStackItem( + tag: tag, + startIndex: storedStartIndex, + endIndex: resultString.endIndex, level: nextLevel, + outerTags: outerTags + )) + } + } + + private func parseTag(_ tags: [String: TagTuning], _ scanner: Scanner, _ resultString: inout String, _ tagsInfo: inout [TagInfo], _ tagsStack: inout [TagStackItem]) { + guard let nextChar = scanner.currentCharacter(), let nextUnicodeScalar = nextChar.unicodeScalars.first else { + resultString.append("<") + return + } + if nextChar == "/" { + parseClosingTag(scanner, &tagsStack, tags, &resultString, &tagsInfo) + } else if CharacterSet.letters.contains(nextUnicodeScalar) { + parseOpeningTag(scanner, &resultString, tags, &tagsStack, &tagsInfo) + } else if scanner._scanString("!--") != nil { + _ = scanner._scanUpToString("-->") + _ = scanner._scanString("-->") + } else if scanner._scanString("!") != nil { + _ = scanner._scanUpToString(">") + _ = scanner._scanString(">") + } else { + resultString.append("<") + } + } + + // MARK: - main + + private static let htmlControlChars = CharacterSet(charactersIn: "<&") + + public func detectTags(tags: [String: TagTuning] = [:]) -> (string: String, tagsInfo: [TagInfo]) { + let scanner = Scanner(string: self) + scanner.charactersToBeSkipped = nil + + var resultString = String() + var tagsInfo = [TagInfo]() + var tagsStack = [TagStackItem]() + + while !scanner.isAtEnd { + if let textString = scanner._scanUpToCharacters(from: String.htmlControlChars) { + resultString.append(textString) + } else if scanner._scanString("<") != nil { + parseTag(tags, scanner, &resultString, &tagsInfo, &tagsStack) + } else if scanner._scanString("&") != nil { + parseSpecial(scanner, &resultString) + } + } + + for tagStackItem in tagsStack { + tagsInfo.append( + TagInfo( + tag: tagStackItem.tag, + range: tagStackItem.startIndex(in: resultString) ..< tagStackItem.endIndex, + level: tagStackItem.level, + outerTags: tagStackItem.outerTags + )) + } + + return (resultString, tagsInfo) + } +} diff --git a/dydx/Pods/Atributika/Sources/Core/String+Style.swift b/dydx/Pods/Atributika/Sources/Core/String+Style.swift new file mode 100644 index 000000000..58421d204 --- /dev/null +++ b/dydx/Pods/Atributika/Sources/Core/String+Style.swift @@ -0,0 +1,43 @@ +// +// Copyright © 2017-2023 Pavel Sharanda. All rights reserved. +// + +import Foundation + +public extension String { + func style(tags: [String: TagTuning]) -> AttributedStringBuilder { + return AttributedStringBuilder(htmlString: self, tags: tags) + } + + func styleBase(_ attributes: AttributesProvider) -> AttributedStringBuilder { + return AttributedStringBuilder(string: self, baseAttributes: attributes) + } + + func styleHashtags(_ attributes: DetectionTuning) -> AttributedStringBuilder { + return AttributedStringBuilder(string: self).styleHashtags(attributes) + } + + func styleMentions(_ attributes: DetectionTuning) -> AttributedStringBuilder { + return AttributedStringBuilder(string: self).styleMentions(attributes) + } + + func style(regex: String, options: NSRegularExpression.Options = [], attributes: DetectionTuning) -> AttributedStringBuilder { + return AttributedStringBuilder(string: self).style(regex: regex, options: options, attributes: attributes) + } + + func style(textCheckingTypes: NSTextCheckingResult.CheckingType, attributes: DetectionTuning) -> AttributedStringBuilder { + return AttributedStringBuilder(string: self).style(textCheckingTypes: textCheckingTypes, attributes: attributes) + } + + func stylePhoneNumbers(_ attributes: DetectionTuning) -> AttributedStringBuilder { + return AttributedStringBuilder(string: self).stylePhoneNumbers(attributes) + } + + func styleLinks(_ attributes: DetectionTuning) -> AttributedStringBuilder { + return AttributedStringBuilder(string: self).styleLinks(attributes) + } + + func style(range: Range, attributes: DetectionTuning) -> AttributedStringBuilder { + return AttributedStringBuilder(string: self).style(range: range, attributes: attributes) + } +} diff --git a/dydx/Pods/Atributika/Sources/Core/Tag.swift b/dydx/Pods/Atributika/Sources/Core/Tag.swift new file mode 100644 index 000000000..262d68947 --- /dev/null +++ b/dydx/Pods/Atributika/Sources/Core/Tag.swift @@ -0,0 +1,101 @@ +// +// Copyright © 2017-2023 Pavel Sharanda. All rights reserved. +// + +import Foundation + +public struct Tag: Equatable { + public let name: String + public let attributes: [String: String] + + public init(name: String, attributes: [String: String]) { + self.name = name + self.attributes = attributes + } +} + +public enum TagPart: Equatable { + case opening(selfClosing: Bool) + case closing + case content(Substring) +} + +public struct TagContext { + public let tag: Tag + public let outerTags: [Tag] + public init(tag: Tag, outerTags: [Tag]) { + self.tag = tag + self.outerTags = outerTags + } +} + +public protocol TagTuning { + func style(context: TagContext) -> AttributesProvider + func transform(context: TagContext, part: TagPart) -> String? +} + +public struct TagTuner: TagTuning { + public func style(context: TagContext) -> AttributesProvider { + return _style(context) + } + + public func transform(context: TagContext, part: TagPart) -> String? { + return _transform(context, part) + } + + private let _style: (TagContext) -> AttributesProvider + private let _transform: (TagContext, TagPart) -> String? + + public init(style: @escaping (TagContext) -> AttributesProvider, transform: @escaping (TagContext, TagPart) -> String?) { + _style = style + _transform = transform + } + + public init(style: @escaping (TagContext) -> AttributesProvider) { + _style = style + _transform = { _, _ in nil } + } + + public init(transform: @escaping (TagContext, TagPart) -> String?) { + _style = { _ in [NSAttributedString.Key: Any]() } + _transform = transform + } + + public init(attributes: AttributesProvider = [NSAttributedString.Key: Any](), + openingTagReplacement: String? = nil, + closingTagReplacement: String? = nil, + contentReplacement: String? = nil) + { + _style = { _ in attributes } + _transform = { _, part in + switch part { + case .opening: + return openingTagReplacement + case .closing: + return closingTagReplacement + case .content: + return contentReplacement + } + } + } +} + +extension Attrs: TagTuning { + public func style(context _: TagContext) -> AttributesProvider { + return self + } + + public func transform(context _: TagContext, part _: TagPart) -> String? { + return nil + } +} + +extension Dictionary: TagTuning where Key == NSAttributedString.Key, Value == Any { + public func style(context _: TagContext) -> AttributesProvider { + return self + } + + public func transform(context _: TagContext, part _: TagPart) -> String? { + return nil + } +} diff --git a/dydx/Pods/BigInt/LICENSE.md b/dydx/Pods/BigInt/LICENSE.md new file mode 100644 index 000000000..18cefd118 --- /dev/null +++ b/dydx/Pods/BigInt/LICENSE.md @@ -0,0 +1,20 @@ + +Copyright (c) 2016-2017 Károly Lőrentey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dydx/Pods/BigInt/README.md b/dydx/Pods/BigInt/README.md new file mode 100644 index 000000000..2256249cd --- /dev/null +++ b/dydx/Pods/BigInt/README.md @@ -0,0 +1,430 @@ +[![BigInt](https://github.com/attaswift/BigInt/raw/master/images/banner.png)](https://github.com/attaswift/BigInt) + +* [Overview](#overview) +* [API Documentation](#api) +* [License](#license) +* [Requirements and Integration](#integration) +* [Implementation Notes](#notes) + * [Full-width multiplication and division primitives](#fullwidth) + * [Why is there no generic `BigInt` type?](#generics) +* [Calculation Samples](#samples) + * [Obligatory factorial demo](#factorial) + * [RSA Cryptography](#rsa) + * [Calculating the Digits of π](#pi) + +[![Swift 3](https://img.shields.io/badge/Swift-5-blue.svg)](https://developer.apple.com/swift/) +[![License](https://img.shields.io/badge/licence-MIT-blue.svg)](http://cocoapods.org/pods/BigInt) +[![Platform](https://img.shields.io/cocoapods/p/BigInt.svg)](http://cocoapods.org/pods/BigInt) + +[![Build Status](https://travis-ci.org/attaswift/BigInt.svg?branch=master)](https://travis-ci.org/attaswift/BigInt) +[![Code Coverage](https://codecov.io/github/attaswift/BigInt/coverage.svg?branch=master)](https://codecov.io/github/attaswift/BigInt?branch=master) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg)](https://github.com/Carthage/Carthage) +[![Version](https://img.shields.io/cocoapods/v/BigInt.svg)](http://cocoapods.org/pods/BigInt) + +## Overview + +This repository provides [integer types of arbitrary width][wiki] implemented +in 100% pure Swift. The underlying representation is in base 2^64, using `Array`. + +[wiki]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic + +This module is handy when you need an integer type that's wider than `UIntMax`, but +you don't want to add [The GNU Multiple Precision Arithmetic Library][GMP] +as a dependency. + +[GMP]: https://gmplib.org + +Two big integer types are included: [`BigUInt`][BigUInt] and [`BigInt`][BigInt], +the latter being the signed variant. +Both of these are Swift structs with copy-on-write value semantics, and they can be used much +like any other integer type. + +The library provides implementations for some of the most frequently useful functions on +big integers, including + +- All functionality from [`Comparable`][comparison] and [`Hashable`][hashing] + +- [The full set of arithmetic operators][addition]: `+`, `-`, `*`, `/`, `%`, `+=`, `-=`, `*=`, `/=`, `%=` + - [Addition][addition] and [subtraction][subtraction] have variants that allow for + shifting the digits of the second operand on the fly. + - Unsigned subtraction will trap when the result would be negative. + ([There are variants][subtraction] that return an overflow flag.) + - [Multiplication][mul] uses brute force for numbers up to 1024 digits, then switches to Karatsuba's recursive method. + (This limit is configurable, see `BigUInt.directMultiplicationLimit`.) + - A [fused multiply-add][fused] method is also available, along with other [special-case variants][multiplication]. + - [Division][division] uses Knuth's Algorithm D, with its 3/2 digits wide quotient approximation. + It will trap when the divisor is zero. + - [`BigUInt.divide`][divide] returns the quotient and + remainder at once; this is faster than calculating them separately. + +- [Bitwise operators][bitwise]: `~`, `|`, `&`, `^`, `|=`, `&=`, `^=`, plus the following read-only properties: + - [`width`][width]: the minimum number of bits required to store the integer, + - [`trailingZeroBitCount`][trailingZeroBitCount]: the number of trailing zero bits in the binary representation, + - [`leadingZeroBitCount`][leadingZeroBitCount]: the number of leading zero bits (when the last digit isn't full), + +- [Shift operators][shift]: `>>`, `<<`, `>>=`, `<<=` + +- Methods to [convert `NSData` to big integers][data] and vice versa. + +- Support for [generating random integers][random] of specified maximum width or magnitude. + +- Radix conversion to/from [`String`s][radix1] and [big integers][radix2] up to base 36 (using repeated divisions). + - Big integers use this to implement `StringLiteralConvertible` (in base 10). + +- [`sqrt(n)`][sqrt]: The square root of an integer (using Newton's method). + +- [`BigUInt.gcd(n, m)`][GCD]: The greatest common divisor of two integers (Stein's algorithm). + +- [`base.power(exponent, modulus)`][powmod]: Modular exponentiation (right-to-left binary method). + [Vanilla exponentiation][power] is also available. +- [`n.inverse(modulus)`][inverse]: Multiplicative inverse in modulo arithmetic (extended Euclidean algorithm). +- [`n.isPrime()`][prime]: Miller–Rabin primality test. + +The implementations are intended to be reasonably efficient, but they are unlikely to be +competitive with GMP at all, even when I happened to implement an algorithm with same asymptotic +behavior as GMP. (I haven't performed a comparison benchmark, though.) + +The library has 100% unit test coverage. Sadly this does not imply that there are no bugs +in it. + +## API Documentation + +Generated API docs are available at http://attaswift.github.io/BigInt/. + +## License + +BigInt can be used, distributed and modified under [the MIT license][license]. + +## Requirements and Integration + +BigInt 4.0.0 requires Swift 4.2 (The last version with support for Swift 3.x was BigInt 2.1.0. +The last version with support for Swift 2 was BigInt 1.3.0.) + +| Swift Version | last BigInt Version| +| ------------- |:-------------------| +| 3.x | 2.1.0 | +| 4.0 | 3.1.0 | +| 4.2 | 4.0.0 | +| 5.0 | 5.0.0 | + +BigInt deploys to macOS 10.10, iOS 9, watchOS 2 and tvOS 9. +It has been tested on the latest OS releases only---however, as the module uses very few platform-provided APIs, +there should be very few issues with earlier versions. + +BigInt uses no APIs specific to Apple platforms, so +it should be easy to port it to other operating systems. + +Setup instructions: + +- **Swift Package Manager:** + Although the Package Manager is still in its infancy, BigInt provides experimental support for it. + Add this to the dependency section of your `Package.swift` manifest: + + ```Swift + .package(url: "https://github.com/attaswift/BigInt.git", from: "5.0.0") + ``` + +- **CocoaPods:** Put this in your `Podfile`: + + ```Ruby + pod 'BigInt', '~> 5.0' + ``` + +- **Carthage:** Put this in your `Cartfile`: + + ``` + github "attaswift/BigInt" ~> 5.0 + ``` + +## Implementation notes + +[`BigUInt`][BigUInt] is a `MutableCollectionType` of its 64-bit digits, with the least significant +digit at index 0. As a convenience, [`BigUInt`][BigUInt] allows you to subscript it with indexes at +or above its `count`. [The subscript operator][subscript] returns 0 for out-of-bound `get`s and +automatically extends the array on out-of-bound `set`s. This makes memory management simpler. + +[`BigInt`][BigInt] is just a tiny wrapper around a `BigUInt` [absolute value][abs] and a +[sign bit][negative], both of which are accessible as public read-write properties. + +### Why is there no generic `BigInt` type? + +The types provided by `BigInt` are not parametric—this is very much intentional, as +Swift generics cost us dearly at runtime in this use case. In every approach I tried, +making arbitrary-precision arithmetic operations work with a generic `Digit` type parameter +resulted in code that was literally *ten times slower*. If you can make the algorithms generic +without such a huge performance hit, [please enlighten me][twitter]! + +This is an area that I plan to investigate more, as it would be useful to have generic +implementations for arbitrary-width arithmetic operations. (Polynomial division and decimal bases +are two examples.) The library already implements double-digit multiplication and division as +extension methods on a protocol with an associated type requirement; this has not measurably affected +performance. Unfortunately, the same is not true for `BigUInt`'s methods. + +Of course, as a last resort, we could just duplicate the code to create a separate +generic variant that was slower but more flexible. + +[license]: https://github.com/attaswift/BigInt/blob/master/LICENSE.md +[twitter]: https://twitter.com/lorentey +[BigUInt]: http://attaswift.github.io/BigInt/Structs/BigUInt.html +[BigInt]: http://attaswift.github.io/BigInt/Structs/BigInt.html +[comparison]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/Comparison +[hashing]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/Hashing +[addition]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/Addition +[subtraction]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/Subtraction +[mul]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/s:ZFV6BigInt7BigUIntoi1mFTS0_S0__S0_ +[fused]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/s:FV6BigInt7BigUInt14multiplyAndAddFTS0_Vs6UInt6410atPositionSi_T_ +[multiplication]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/Multiplication +[division]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/Division +[divide]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/s:FV6BigInt7BigUInt7dividedFT2byS0__T8quotientS0_9remainderS0__ +[bitwise]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/Bitwise%20Operations +[width]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/s:vV6BigInt7BigUInt5widthSi +[leadingZeroBitCount]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/s:vV6BigInt7BigUInt13leadingZeroBitCountSi +[trailingZeroBitCount]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/s:vV6BigInt7BigUInt14trailingZeroBitCountSi +[shift]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/Shift%20Operators +[data]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/NSData%20Conversion +[random]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/Random%20Integers +[radix1]: http://attaswift.github.io/BigInt/Extensions/String.html#/s:FE6BigIntSScFTVS_7BigUInt5radixSi9uppercaseSb_SS +[radix2]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/s:FV6BigInt7BigUIntcFTSS5radixSi_GSqS0__ +[sqrt]: http://attaswift.github.io/BigInt/Functions.html#/s:F6BigInt4sqrtFVS_7BigUIntS0_ +[GCD]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/s:ZFV6BigInt7BigUInt3gcdFTS0_S0__S0_ +[powmod]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/s:FV6BigInt7BigUInt5powerFTS0_7modulusS0__S0_ +[power]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/s:FV6BigInt7BigUInt5powerFSiS0_ +[inverse]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/s:FV6BigInt7BigUInt7inverseFS0_GSqS0__ +[prime]: http://attaswift.github.io/BigInt/Structs/BigUInt.html#/Primality%20Testing +[abs]: http://attaswift.github.io/BigInt/Structs/BigInt.html#/s:vV6BigInt6BigInt3absVS_7BigUInt +[negative]: http://attaswift.github.io/BigInt/Structs/BigInt.html#/s:vV6BigInt6BigInt8negativeSb +[subscript]: https://github.com/attaswift/BigInt/blob/v2.0.0/Sources/BigUInt.swift#L216-L239 +[fullmuldiv]: https://github.com/attaswift/BigInt/blob/v2.0.0/Sources/BigDigit.swift#L96-L167 + + +## Calculation Samples + +### Obligatory Factorial Demo + +It is easy to use `BigInt` to calculate the factorial function for any integer: + +```Swift +import BigInt + +func factorial(_ n: Int) -> BigInt { + return (1 ... n).map { BigInt($0) }.reduce(BigInt(1), *) +} + +print(factorial(10)) +==> 362880 + +print(factorial(100)) +==> 93326215443944152681699238856266700490715968264381621468592963895217599993229915 + 6089414639761565182862536979208272237582511852109168640000000000000000000000 + +print(factorial(1000)) +==> 40238726007709377354370243392300398571937486421071463254379991042993851239862902 + 05920442084869694048004799886101971960586316668729948085589013238296699445909974 + 24504087073759918823627727188732519779505950995276120874975462497043601418278094 + 64649629105639388743788648733711918104582578364784997701247663288983595573543251 + 31853239584630755574091142624174743493475534286465766116677973966688202912073791 + 43853719588249808126867838374559731746136085379534524221586593201928090878297308 + 43139284440328123155861103697680135730421616874760967587134831202547858932076716 + 91324484262361314125087802080002616831510273418279777047846358681701643650241536 + 91398281264810213092761244896359928705114964975419909342221566832572080821333186 + 11681155361583654698404670897560290095053761647584772842188967964624494516076535 + 34081989013854424879849599533191017233555566021394503997362807501378376153071277 + 61926849034352625200015888535147331611702103968175921510907788019393178114194545 + 25722386554146106289218796022383897147608850627686296714667469756291123408243920 + 81601537808898939645182632436716167621791689097799119037540312746222899880051954 + 44414282012187361745992642956581746628302955570299024324153181617210465832036786 + 90611726015878352075151628422554026517048330422614397428693306169089796848259012 + 54583271682264580665267699586526822728070757813918581788896522081643483448259932 + 66043367660176999612831860788386150279465955131156552036093988180612138558600301 + 43569452722420634463179746059468257310379008402443243846565724501440282188525247 + 09351906209290231364932734975655139587205596542287497740114133469627154228458623 + 77387538230483865688976461927383814900140767310446640259899490222221765904339901 + 88601856652648506179970235619389701786004081188972991831102117122984590164192106 + 88843871218556461249607987229085192968193723886426148396573822911231250241866493 + 53143970137428531926649875337218940694281434118520158014123344828015051399694290 + 15348307764456909907315243327828826986460278986432113908350621709500259738986355 + 42771967428222487575867657523442202075736305694988250879689281627538488633969099 + 59826280956121450994871701244516461260379029309120889086942028510640182154399457 + 15680594187274899809425474217358240106367740459574178516082923013535808184009699 + 63725242305608559037006242712434169090041536901059339838357779394109700277534720 + 00000000000000000000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000000000000000000000000000000000 + 00000 +``` + +Well, I guess that's all right, but it's not very interesting. Let's try something more useful. + +### RSA Cryptography + +The `BigInt` module provides all necessary parts to implement an (overly) +simple [RSA cryptography system][RSA]. + +[RSA]: https://en.wikipedia.org/wiki/RSA_(cryptosystem) + +Let's start with a simple function that generates a random n-bit prime. The module +includes a function to generate random integers of a specific size, and also an +`isPrime` method that performs the Miller–Rabin primality test. These are all we need: + +```Swift +func generatePrime(_ width: Int) -> BigUInt { + while true { + var random = BigUInt.randomInteger(withExactWidth: width) + random |= BigUInt(1) + if random.isPrime() { + return random + } + } +} + +let p = generatePrime(1024) +==> 13308187650642192396256419911012544845370493728424936791561478318443071617242872 + 81980956747087187419914435169914161116601678883358611076800743580556055714173922 + 08406194264346635072293912609713085260354070700055888678514690878149253177960273 + 775659537560220378850112471985434373425534121373466492101182463962031 + +let q = generatePrime(1024) +==> 17072954422657145489547308812333368925007949054501204983863958355897172093173783 + 10108226596943999553784252564650624766276133157586733504784616138305701168410157 + 80784336308507083874651158029602582993233111593356512531869546706885170044355115 + 669728424124141763799008880327106952436883614887277350838425336156327 +``` + +Cool! Now that we have two large primes, we can produce an RSA public/private keypair +out of them. + +```Swift +typealias Key = (modulus: BigUInt, exponent: BigUInt) + +let n = p * q +==> 22721008120758282530010953362926306641542233757318103044313144976976529789946696 + 15454966720907712515917481418981591379647635391260569349099666410127279690367978 + 81184375533755888994370640857883754985364288413796100527262763202679037134021810 + 57933883525572232242690805678883227791774442041516929419640051653934584376704034 + 63953169772816907280591934423237977258358097846511079947337857778137177570668391 + 57455417707100275487770399281417352829897118140972240757708561027087217205975220 + 02207275447810167397968435583004676293892340103729490987263776871467057582629588 + 916498579594964478080508868267360515953225283461208420137 + +let e: BigUInt = 65537 +let phi = (p - 1) * (q - 1) +let d = e.inverse(phi)! // d * e % phi == 1 +==> 13964664343869014759736350480776837992604500903989703383202366291905558996277719 + 77822086142456362972689566985925179681282432115598451765899180050962461295573831 + 37069237934291884106584820998146965085531433195106686745474222222620986858696591 + 69836532468835154412554521152103642453158895363417640676611704542784576974374954 + 45789456921660619938185093118762690200980720312508614337759620606992462563490422 + 76669559556568917533268479190948959560397579572761529852891246283539604545691244 + 89999692877158676643042118662613875863504016129837099223040687512684532694527109 + 80742873307409704484365002175294665608486688146261327793 + +let publicKey: Key = (n, e) +let privateKey: Key = (n, d) +``` + +In RSA, modular exponentiation is used to encrypt (and decrypt) messages. + +```Swift +func encrypt(_ message: BigUInt, key: Key) -> BigUInt { + return message.power(key.exponent, modulus: key.modulus) +} +``` + +Let's try out our new keypair by converting a string into UTF-8, interpreting +the resulting binary representation as a big integer, and encrypting it with the +public key. `BigUInt` has an initializer that takes an `NSData`, so this is pretty +easy to do: + +```Swift +let secret: BigUInt = BigUInt("Arbitrary precision arithmetic is fun!".dataUsingEncoding(NSUTF8StringEncoding)!) +==> 83323446846105976078466731524728681905293067701804838925389198929123912971229457 + 68818568737 + +let cyphertext = encrypt(secret, key: publicKey) +==> 95186982543485985200666516508066093880038842892337880561554910904277290917831453 + 54854954722744805432145474047391353716305176389470779020645959135298322520888633 + 61674945129099575943384767330342554525120384485469428048962027149169876127890306 + 77028183904699491962050888974524603226290836984166164759586952419343589385279641 + 47999991283152843977988979846238236160274201261075188190509539751990119132013021 + 74866638595734222867005089157198503204192264814750832072844208520394603054901706 + 06024394731371973402595826456435944968439153664617188570808940022471990638468783 + 49208193955207336172861151720299024935127021719852700882 +``` + +Well, it looks encrypted all right, but can we get the original message back? +In theory, encrypting the cyphertext with the private key returns the original message. +Let's see: + +```Swift +let plaintext = encrypt(cyphertext, key: privateKey) +==> 83323446846105976078466731524728681905293067701804838925389198929123912971229457 + 68818568737 + +let received = String(data: plaintext.serialize(), encoding: NSUTF8StringEncoding) +==> "Arbitrary precision arithmetic is fun!" +``` + +Yay! This is truly terrific, but please don't use this example code in an actual +cryptography system. RSA has lots of subtle (and some not so subtle) complications +that we ignored to keep this example short. + +### Calculating the Digits of π + +Another fun activity to try with `BigInt`s is to generate the digits of π. +Let's try implementing [Jeremy Gibbon's spigot algorithm][spigot]. +This is a rather slow algorithm as π-generators go, but it makes up for it with its grooviness +factor: it's remarkably short, it only uses (big) integer arithmetic, and every iteration +produces a single new digit in the base-10 representation of π. This naturally leads to an +implementation as an infinite `GeneratorType`: + +[spigot]: http://www.cs.ox.ac.uk/jeremy.gibbons/publications/spigot.pdf + +```Swift +func digitsOfPi() -> AnyGenerator { + var q: BigUInt = 1 + var r: BigUInt = 180 + var t: BigUInt = 60 + var i: UInt64 = 2 // Does not overflow until digit #826_566_842 + return AnyIterator { + let u: UInt64 = 3 * (3 * i + 1) * (3 * i + 2) + let y = (q.multiplied(byDigit: 27 * i - 12) + 5 * r) / (5 * t) + (q, r, t) = ( + 10 * q.multiplied(byDigit: i * (2 * i - 1)), + 10 * (q.multiplied(byDigit: 5 * i - 2) + r - y * t).multiplied(byDigit: u), + t.multiplied(byDigit: u)) + i += 1 + return Int(y[0]) + } +} +``` + +Well, that was surprisingly easy. But does it work? Of course it does! + +```Swift +var digits = "π ≈ " +var count = 0 +for digit in digitsOfPi() { + assert(digit < 10) + digits += String(digit) + count += 1 + if count == 1 { digits += "." } + if count == 1000 { break } +} + +digits +==> π ≈ 3.14159265358979323846264338327950288419716939937510582097494459230781640628 + 62089986280348253421170679821480865132823066470938446095505822317253594081284811 + 17450284102701938521105559644622948954930381964428810975665933446128475648233786 + 78316527120190914564856692346034861045432664821339360726024914127372458700660631 + 55881748815209209628292540917153643678925903600113305305488204665213841469519415 + 11609433057270365759591953092186117381932611793105118548074462379962749567351885 + 75272489122793818301194912983367336244065664308602139494639522473719070217986094 + 37027705392171762931767523846748184676694051320005681271452635608277857713427577 + 89609173637178721468440901224953430146549585371050792279689258923542019956112129 + 02196086403441815981362977477130996051870721134999999837297804995105973173281609 + 63185950244594553469083026425223082533446850352619311881710100031378387528865875 + 33208381420617177669147303598253490428755468731159562863882353787593751957781857 + 780532171226806613001927876611195909216420198 +``` + +Now go and have some fun with big integers on your own! diff --git a/dydx/Pods/BigInt/Sources/Addition.swift b/dydx/Pods/BigInt/Sources/Addition.swift new file mode 100644 index 000000000..34f4d44e8 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Addition.swift @@ -0,0 +1,126 @@ +// +// Addition.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-03. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension BigUInt { + //MARK: Addition + + /// Add `word` to this integer in place. + /// `word` is shifted `shift` words to the left before being added. + /// + /// - Complexity: O(max(count, shift)) + internal mutating func addWord(_ word: Word, shiftedBy shift: Int = 0) { + precondition(shift >= 0) + var carry = word + var i = shift + while carry > 0 { + let (d, c) = self[i].addingReportingOverflow(carry) + self[i] = d + carry = (c ? 1 : 0) + i += 1 + } + } + + /// Add the digit `d` to this integer and return the result. + /// `d` is shifted `shift` words to the left before being added. + /// + /// - Complexity: O(max(count, shift)) + internal func addingWord(_ word: Word, shiftedBy shift: Int = 0) -> BigUInt { + var r = self + r.addWord(word, shiftedBy: shift) + return r + } + + /// Add `b` to this integer in place. + /// `b` is shifted `shift` words to the left before being added. + /// + /// - Complexity: O(max(count, b.count + shift)) + internal mutating func add(_ b: BigUInt, shiftedBy shift: Int = 0) { + precondition(shift >= 0) + var carry = false + var bi = 0 + let bc = b.count + while bi < bc || carry { + let ai = shift + bi + let (d, c) = self[ai].addingReportingOverflow(b[bi]) + if carry { + let (d2, c2) = d.addingReportingOverflow(1) + self[ai] = d2 + carry = c || c2 + } + else { + self[ai] = d + carry = c + } + bi += 1 + } + } + + /// Add `b` to this integer and return the result. + /// `b` is shifted `shift` words to the left before being added. + /// + /// - Complexity: O(max(count, b.count + shift)) + internal func adding(_ b: BigUInt, shiftedBy shift: Int = 0) -> BigUInt { + var r = self + r.add(b, shiftedBy: shift) + return r + } + + /// Increment this integer by one. If `shift` is non-zero, it selects + /// the word that is to be incremented. + /// + /// - Complexity: O(count + shift) + internal mutating func increment(shiftedBy shift: Int = 0) { + self.addWord(1, shiftedBy: shift) + } + + /// Add `a` and `b` together and return the result. + /// + /// - Complexity: O(max(a.count, b.count)) + public static func +(a: BigUInt, b: BigUInt) -> BigUInt { + return a.adding(b) + } + + /// Add `a` and `b` together, and store the sum in `a`. + /// + /// - Complexity: O(max(a.count, b.count)) + public static func +=(a: inout BigUInt, b: BigUInt) { + a.add(b, shiftedBy: 0) + } +} + +extension BigInt { + /// Add `a` to `b` and return the result. + public static func +(a: BigInt, b: BigInt) -> BigInt { + switch (a.sign, b.sign) { + case (.plus, .plus): + return BigInt(sign: .plus, magnitude: a.magnitude + b.magnitude) + case (.minus, .minus): + return BigInt(sign: .minus, magnitude: a.magnitude + b.magnitude) + case (.plus, .minus): + if a.magnitude >= b.magnitude { + return BigInt(sign: .plus, magnitude: a.magnitude - b.magnitude) + } + else { + return BigInt(sign: .minus, magnitude: b.magnitude - a.magnitude) + } + case (.minus, .plus): + if b.magnitude >= a.magnitude { + return BigInt(sign: .plus, magnitude: b.magnitude - a.magnitude) + } + else { + return BigInt(sign: .minus, magnitude: a.magnitude - b.magnitude) + } + } + } + + /// Add `b` to `a` in place. + public static func +=(a: inout BigInt, b: BigInt) { + a = a + b + } +} + diff --git a/dydx/Pods/BigInt/Sources/BigInt.swift b/dydx/Pods/BigInt/Sources/BigInt.swift new file mode 100644 index 000000000..11318ffcc --- /dev/null +++ b/dydx/Pods/BigInt/Sources/BigInt.swift @@ -0,0 +1,74 @@ +// +// BigInt.swift +// BigInt +// +// Created by Károly Lőrentey on 2015-12-27. +// Copyright © 2016-2017 Károly Lőrentey. +// + +//MARK: BigInt + +/// An arbitary precision signed integer type, also known as a "big integer". +/// +/// Operations on big integers never overflow, but they might take a long time to execute. +/// The amount of memory (and address space) available is the only constraint to the magnitude of these numbers. +/// +/// This particular big integer type uses base-2^64 digits to represent integers. +/// +/// `BigInt` is essentially a tiny wrapper that extends `BigUInt` with a sign bit and provides signed integer +/// operations. Both the underlying absolute value and the negative/positive flag are available as read-write +/// properties. +/// +/// Not all algorithms of `BigUInt` are available for `BigInt` values; for example, there is no square root or +/// primality test for signed integers. When you need to call one of these, just extract the absolute value: +/// +/// ```Swift +/// BigInt(255).abs.isPrime() // Returns false +/// ``` +/// +public struct BigInt: SignedInteger { + public enum Sign { + case plus + case minus + } + + public typealias Magnitude = BigUInt + + /// The type representing a digit in `BigInt`'s underlying number system. + public typealias Word = BigUInt.Word + + public static var isSigned: Bool { + return true + } + + /// The absolute value of this integer. + public var magnitude: BigUInt + + /// True iff the value of this integer is negative. + public var sign: Sign + + /// Initializes a new big integer with the provided absolute number and sign flag. + public init(sign: Sign, magnitude: BigUInt) { + self.sign = (magnitude.isZero ? .plus : sign) + self.magnitude = magnitude + } + + /// Return true iff this integer is zero. + /// + /// - Complexity: O(1) + public var isZero: Bool { + return magnitude.isZero + } + + /// Returns `-1` if this value is negative and `1` if it’s positive; otherwise, `0`. + /// + /// - Returns: The sign of this number, expressed as an integer of the same type. + public func signum() -> BigInt { + switch sign { + case .plus: + return isZero ? 0 : 1 + case .minus: + return -1 + } + } +} diff --git a/dydx/Pods/BigInt/Sources/BigUInt.swift b/dydx/Pods/BigInt/Sources/BigUInt.swift new file mode 100644 index 000000000..81aa9a837 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/BigUInt.swift @@ -0,0 +1,386 @@ +// +// BigUInt.swift +// BigInt +// +// Created by Károly Lőrentey on 2015-12-26. +// Copyright © 2016-2017 Károly Lőrentey. +// + +/// An arbitary precision unsigned integer type, also known as a "big integer". +/// +/// Operations on big integers never overflow, but they may take a long time to execute. +/// The amount of memory (and address space) available is the only constraint to the magnitude of these numbers. +/// +/// This particular big integer type uses base-2^64 digits to represent integers; you can think of it as a wrapper +/// around `Array`. (In fact, `BigUInt` only uses an array if there are more than two digits.) +public struct BigUInt: UnsignedInteger { + /// The type representing a digit in `BigUInt`'s underlying number system. + public typealias Word = UInt + + /// The storage variants of a `BigUInt`. + enum Kind { + /// Value consists of the two specified words (low and high). Either or both words may be zero. + case inline(Word, Word) + /// Words are stored in a slice of the storage array. + case slice(from: Int, to: Int) + /// Words are stored in the storage array. + case array + } + + internal fileprivate (set) var kind: Kind // Internal for testing only + internal fileprivate (set) var storage: [Word] // Internal for testing only; stored separately to prevent COW copies + + /// Initializes a new BigUInt with value 0. + public init() { + self.kind = .inline(0, 0) + self.storage = [] + } + + internal init(word: Word) { + self.kind = .inline(word, 0) + self.storage = [] + } + + internal init(low: Word, high: Word) { + self.kind = .inline(low, high) + self.storage = [] + } + + /// Initializes a new BigUInt with the specified digits. The digits are ordered from least to most significant. + public init(words: [Word]) { + self.kind = .array + self.storage = words + normalize() + } + + internal init(words: [Word], from startIndex: Int, to endIndex: Int) { + self.kind = .slice(from: startIndex, to: endIndex) + self.storage = words + normalize() + } +} + +extension BigUInt { + public static var isSigned: Bool { + return false + } + + /// Return true iff this integer is zero. + /// + /// - Complexity: O(1) + var isZero: Bool { + switch kind { + case .inline(0, 0): return true + case .array: return storage.isEmpty + default: + return false + } + } + + /// Returns `1` if this value is, positive; otherwise, `0`. + /// + /// - Returns: The sign of this number, expressed as an integer of the same type. + public func signum() -> BigUInt { + return isZero ? 0 : 1 + } +} + +extension BigUInt { + mutating func ensureArray() { + switch kind { + case let .inline(w0, w1): + kind = .array + storage = w1 != 0 ? [w0, w1] + : w0 != 0 ? [w0] + : [] + case let .slice(from: start, to: end): + kind = .array + storage = Array(storage[start ..< end]) + case .array: + break + } + } + + var capacity: Int { + guard case .array = kind else { return 0 } + return storage.capacity + } + + mutating func reserveCapacity(_ minimumCapacity: Int) { + switch kind { + case let .inline(w0, w1): + kind = .array + storage.reserveCapacity(minimumCapacity) + if w1 != 0 { + storage.append(w0) + storage.append(w1) + } + else if w0 != 0 { + storage.append(w0) + } + case let .slice(from: start, to: end): + kind = .array + var words: [Word] = [] + words.reserveCapacity(Swift.max(end - start, minimumCapacity)) + words.append(contentsOf: storage[start ..< end]) + storage = words + case .array: + storage.reserveCapacity(minimumCapacity) + } + } + + /// Gets rid of leading zero digits in the digit array and converts slices into inline digits when possible. + internal mutating func normalize() { + switch kind { + case .slice(from: let start, to: var end): + assert(start >= 0 && end <= storage.count && start <= end) + while start < end, storage[end - 1] == 0 { + end -= 1 + } + switch end - start { + case 0: + kind = .inline(0, 0) + storage = [] + case 1: + kind = .inline(storage[start], 0) + storage = [] + case 2: + kind = .inline(storage[start], storage[start + 1]) + storage = [] + case storage.count: + assert(start == 0) + kind = .array + default: + kind = .slice(from: start, to: end) + } + case .array where storage.last == 0: + while storage.last == 0 { + storage.removeLast() + } + default: + break + } + } + + /// Set this integer to 0 without releasing allocated storage capacity (if any). + mutating func clear() { + self.load(0) + } + + /// Set this integer to `value` by copying its digits without releasing allocated storage capacity (if any). + mutating func load(_ value: BigUInt) { + switch kind { + case .inline, .slice: + self = value + case .array: + self.storage.removeAll(keepingCapacity: true) + self.storage.append(contentsOf: value.words) + } + } +} + +extension BigUInt { + //MARK: Collection-like members + + /// The number of digits in this integer, excluding leading zero digits. + var count: Int { + switch kind { + case let .inline(w0, w1): + return w1 != 0 ? 2 + : w0 != 0 ? 1 + : 0 + case let .slice(from: start, to: end): + return end - start + case .array: + return storage.count + } + } + + /// Get or set a digit at a given index. + /// + /// - Note: Unlike a normal collection, it is OK for the index to be greater than or equal to `endIndex`. + /// The subscripting getter returns zero for indexes beyond the most significant digit. + /// Setting these extended digits automatically appends new elements to the underlying digit array. + /// - Requires: index >= 0 + /// - Complexity: The getter is O(1). The setter is O(1) if the conditions below are true; otherwise it's O(count). + /// - The integer's storage is not shared with another integer + /// - The integer wasn't created as a slice of another integer + /// - `index < count` + subscript(_ index: Int) -> Word { + get { + precondition(index >= 0) + switch (kind, index) { + case (.inline(let w0, _), 0): return w0 + case (.inline(_, let w1), 1): return w1 + case (.slice(from: let start, to: let end), _) where index < end - start: + return storage[start + index] + case (.array, _) where index < storage.count: + return storage[index] + default: + return 0 + } + } + set(word) { + precondition(index >= 0) + switch (kind, index) { + case let (.inline(_, w1), 0): + kind = .inline(word, w1) + case let (.inline(w0, _), 1): + kind = .inline(w0, word) + case let (.slice(from: start, to: end), _) where index < end - start: + replace(at: index, with: word) + case (.array, _) where index < storage.count: + replace(at: index, with: word) + default: + extend(at: index, with: word) + } + } + } + + private mutating func replace(at index: Int, with word: Word) { + ensureArray() + precondition(index < storage.count) + storage[index] = word + if word == 0, index == storage.count - 1 { + normalize() + } + } + + private mutating func extend(at index: Int, with word: Word) { + guard word != 0 else { return } + reserveCapacity(index + 1) + precondition(index >= storage.count) + storage.append(contentsOf: repeatElement(0, count: index - storage.count)) + storage.append(word) + } + + /// Returns an integer built from the digits of this integer in the given range. + internal func extract(_ bounds: Range) -> BigUInt { + switch kind { + case let .inline(w0, w1): + let bounds = bounds.clamped(to: 0 ..< 2) + if bounds == 0 ..< 2 { + return BigUInt(low: w0, high: w1) + } + else if bounds == 0 ..< 1 { + return BigUInt(word: w0) + } + else if bounds == 1 ..< 2 { + return BigUInt(word: w1) + } + else { + return BigUInt() + } + case let .slice(from: start, to: end): + let s = Swift.min(end, start + Swift.max(bounds.lowerBound, 0)) + let e = Swift.max(s, (bounds.upperBound > end - start ? end : start + bounds.upperBound)) + return BigUInt(words: storage, from: s, to: e) + case .array: + let b = bounds.clamped(to: storage.startIndex ..< storage.endIndex) + return BigUInt(words: storage, from: b.lowerBound, to: b.upperBound) + } + } + + internal func extract(_ bounds: Bounds) -> BigUInt where Bounds.Bound == Int { + return self.extract(bounds.relative(to: 0 ..< Int.max)) + } +} + +extension BigUInt { + internal mutating func shiftRight(byWords amount: Int) { + assert(amount >= 0) + guard amount > 0 else { return } + switch kind { + case let .inline(_, w1) where amount == 1: + kind = .inline(w1, 0) + case .inline(_, _): + kind = .inline(0, 0) + case let .slice(from: start, to: end): + let s = start + amount + if s >= end { + kind = .inline(0, 0) + } + else { + kind = .slice(from: s, to: end) + normalize() + } + case .array: + if amount >= storage.count { + storage.removeAll(keepingCapacity: true) + } + else { + storage.removeFirst(amount) + } + } + } + + internal mutating func shiftLeft(byWords amount: Int) { + assert(amount >= 0) + guard amount > 0 else { return } + guard !isZero else { return } + switch kind { + case let .inline(w0, 0) where amount == 1: + kind = .inline(0, w0) + case let .inline(w0, w1): + let c = (w1 == 0 ? 1 : 2) + storage.reserveCapacity(amount + c) + storage.append(contentsOf: repeatElement(0, count: amount)) + storage.append(w0) + if w1 != 0 { + storage.append(w1) + } + kind = .array + case let .slice(from: start, to: end): + var words: [Word] = [] + words.reserveCapacity(amount + count) + words.append(contentsOf: repeatElement(0, count: amount)) + words.append(contentsOf: storage[start ..< end]) + storage = words + kind = .array + case .array: + storage.insert(contentsOf: repeatElement(0, count: amount), at: 0) + } + } +} + +extension BigUInt { + //MARK: Low and High + + /// Split this integer into a high-order and a low-order part. + /// + /// - Requires: count > 1 + /// - Returns: `(low, high)` such that + /// - `self == low.add(high, shiftedBy: middleIndex)` + /// - `high.width <= floor(width / 2)` + /// - `low.width <= ceil(width / 2)` + /// - Complexity: Typically O(1), but O(count) in the worst case, because high-order zero digits need to be removed after the split. + internal var split: (high: BigUInt, low: BigUInt) { + precondition(count > 1) + let mid = middleIndex + return (self.extract(mid...), self.extract(.. 1 + internal var low: BigUInt { + return self.extract(0 ..< middleIndex) + } + + /// The high-order half of this BigUInt. + /// + /// - Returns: `self[middleIndex ..< count]` + /// - Requires: count > 1 + internal var high: BigUInt { + return self.extract(middleIndex ..< count) + } +} + diff --git a/dydx/Pods/BigInt/Sources/Bitwise Ops.swift b/dydx/Pods/BigInt/Sources/Bitwise Ops.swift new file mode 100644 index 000000000..0d00148bd --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Bitwise Ops.swift @@ -0,0 +1,121 @@ +// +// Bitwise Ops.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-03. +// Copyright © 2016-2017 Károly Lőrentey. +// + +//MARK: Bitwise Operations + +extension BigUInt { + /// Return the ones' complement of `a`. + /// + /// - Complexity: O(a.count) + public static prefix func ~(a: BigUInt) -> BigUInt { + return BigUInt(words: a.words.map { ~$0 }) + } + + /// Calculate the bitwise OR of `a` and `b`, and store the result in `a`. + /// + /// - Complexity: O(max(a.count, b.count)) + public static func |= (a: inout BigUInt, b: BigUInt) { + a.reserveCapacity(b.count) + for i in 0 ..< b.count { + a[i] |= b[i] + } + } + + /// Calculate the bitwise AND of `a` and `b` and return the result. + /// + /// - Complexity: O(max(a.count, b.count)) + public static func &= (a: inout BigUInt, b: BigUInt) { + for i in 0 ..< Swift.max(a.count, b.count) { + a[i] &= b[i] + } + } + + /// Calculate the bitwise XOR of `a` and `b` and return the result. + /// + /// - Complexity: O(max(a.count, b.count)) + public static func ^= (a: inout BigUInt, b: BigUInt) { + a.reserveCapacity(b.count) + for i in 0 ..< b.count { + a[i] ^= b[i] + } + } +} + +extension BigInt { + public static prefix func ~(x: BigInt) -> BigInt { + switch x.sign { + case .plus: + return BigInt(sign: .minus, magnitude: x.magnitude + 1) + case .minus: + return BigInt(sign: .plus, magnitude: x.magnitude - 1) + } + } + + public static func &(lhs: inout BigInt, rhs: BigInt) -> BigInt { + let left = lhs.words + let right = rhs.words + // Note we aren't using left.count/right.count here; we account for the sign bit separately later. + let count = Swift.max(lhs.magnitude.count, rhs.magnitude.count) + var words: [UInt] = [] + words.reserveCapacity(count) + for i in 0 ..< count { + words.append(left[i] & right[i]) + } + if lhs.sign == .minus && rhs.sign == .minus { + words.twosComplement() + return BigInt(sign: .minus, magnitude: BigUInt(words: words)) + } + return BigInt(sign: .plus, magnitude: BigUInt(words: words)) + } + + public static func |(lhs: inout BigInt, rhs: BigInt) -> BigInt { + let left = lhs.words + let right = rhs.words + // Note we aren't using left.count/right.count here; we account for the sign bit separately later. + let count = Swift.max(lhs.magnitude.count, rhs.magnitude.count) + var words: [UInt] = [] + words.reserveCapacity(count) + for i in 0 ..< count { + words.append(left[i] | right[i]) + } + if lhs.sign == .minus || rhs.sign == .minus { + words.twosComplement() + return BigInt(sign: .minus, magnitude: BigUInt(words: words)) + } + return BigInt(sign: .plus, magnitude: BigUInt(words: words)) + } + + public static func ^(lhs: inout BigInt, rhs: BigInt) -> BigInt { + let left = lhs.words + let right = rhs.words + // Note we aren't using left.count/right.count here; we account for the sign bit separately later. + let count = Swift.max(lhs.magnitude.count, rhs.magnitude.count) + var words: [UInt] = [] + words.reserveCapacity(count) + for i in 0 ..< count { + words.append(left[i] ^ right[i]) + } + if (lhs.sign == .minus) != (rhs.sign == .minus) { + words.twosComplement() + return BigInt(sign: .minus, magnitude: BigUInt(words: words)) + } + return BigInt(sign: .plus, magnitude: BigUInt(words: words)) + } + + public static func &=(lhs: inout BigInt, rhs: BigInt) { + lhs = lhs & rhs + } + + public static func |=(lhs: inout BigInt, rhs: BigInt) { + lhs = lhs | rhs + } + + public static func ^=(lhs: inout BigInt, rhs: BigInt) { + lhs = lhs ^ rhs + } +} diff --git a/dydx/Pods/BigInt/Sources/Codable.swift b/dydx/Pods/BigInt/Sources/Codable.swift new file mode 100644 index 000000000..b53b30be5 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Codable.swift @@ -0,0 +1,155 @@ +// +// Codable.swift +// BigInt +// +// Created by Károly Lőrentey on 2017-8-11. +// Copyright © 2016-2017 Károly Lőrentey. +// + + +// Little-endian to big-endian +struct Units: RandomAccessCollection +where Words.Element: FixedWidthInteger, Words.Index == Int { + typealias Word = Words.Element + let words: Words + init(of type: Unit.Type, _ words: Words) { + precondition(Word.bitWidth % Unit.bitWidth == 0 || Unit.bitWidth % Word.bitWidth == 0) + self.words = words + } + var count: Int { return (words.count * Word.bitWidth + Unit.bitWidth - 1) / Unit.bitWidth } + var startIndex: Int { return 0 } + var endIndex: Int { return count } + subscript(_ index: Int) -> Unit { + let index = count - 1 - index + if Unit.bitWidth == Word.bitWidth { + return Unit(words[index]) + } + else if Unit.bitWidth > Word.bitWidth { + let c = Unit.bitWidth / Word.bitWidth + var unit: Unit = 0 + var j = 0 + for i in (c * index) ..< Swift.min(c * (index + 1), words.endIndex) { + unit |= Unit(words[i]) << j + j += Word.bitWidth + } + return unit + } + // Unit.bitWidth < Word.bitWidth + let c = Word.bitWidth / Unit.bitWidth + let i = index / c + let j = index % c + return Unit(truncatingIfNeeded: words[i] >> (j * Unit.bitWidth)) + } +} + +extension Array where Element: FixedWidthInteger { + // Big-endian to little-endian + init(count: Int?, generator: () throws -> Unit?) rethrows { + typealias Word = Element + precondition(Word.bitWidth % Unit.bitWidth == 0 || Unit.bitWidth % Word.bitWidth == 0) + self = [] + if Unit.bitWidth == Word.bitWidth { + if let count = count { + self.reserveCapacity(count) + } + while let unit = try generator() { + self.append(Word(unit)) + } + } + else if Unit.bitWidth > Word.bitWidth { + let wordsPerUnit = Unit.bitWidth / Word.bitWidth + if let count = count { + self.reserveCapacity(count * wordsPerUnit) + } + while let unit = try generator() { + var shift = Unit.bitWidth - Word.bitWidth + while shift >= 0 { + self.append(Word(truncatingIfNeeded: unit >> shift)) + shift -= Word.bitWidth + } + } + } + else { + let unitsPerWord = Word.bitWidth / Unit.bitWidth + if let count = count { + self.reserveCapacity((count + unitsPerWord - 1) / unitsPerWord) + } + var word: Word = 0 + var c = 0 + while let unit = try generator() { + word <<= Unit.bitWidth + word |= Word(unit) + c += Unit.bitWidth + if c == Word.bitWidth { + self.append(word) + word = 0 + c = 0 + } + } + if c > 0 { + self.append(word << c) + var shifted: Word = 0 + for i in self.indices { + let word = self[i] + self[i] = shifted | (word >> c) + shifted = word << (Word.bitWidth - c) + } + } + } + self.reverse() + } +} + +extension BigInt: Codable { + public init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + + // Decode sign + let sign: BigInt.Sign + switch try container.decode(String.self) { + case "+": + sign = .plus + case "-": + sign = .minus + default: + throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, + debugDescription: "Invalid big integer sign")) + } + + // Decode magnitude + let words = try [UInt](count: container.count?.advanced(by: -1)) { () -> UInt64? in + guard !container.isAtEnd else { return nil } + return try container.decode(UInt64.self) + } + let magnitude = BigUInt(words: words) + + self.init(sign: sign, magnitude: magnitude) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(sign == .plus ? "+" : "-") + let units = Units(of: UInt64.self, self.magnitude.words) + if units.isEmpty { + try container.encode(0 as UInt64) + } + else { + try container.encode(contentsOf: units) + } + } +} + +extension BigUInt: Codable { + public init(from decoder: Decoder) throws { + let value = try BigInt(from: decoder) + guard value.sign == .plus else { + throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, + debugDescription: "BigUInt cannot hold a negative value")) + } + self = value.magnitude + } + + public func encode(to encoder: Encoder) throws { + try BigInt(sign: .plus, magnitude: self).encode(to: encoder) + } +} diff --git a/dydx/Pods/BigInt/Sources/Comparable.swift b/dydx/Pods/BigInt/Sources/Comparable.swift new file mode 100644 index 000000000..d9ab87e7e --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Comparable.swift @@ -0,0 +1,63 @@ +// +// Comparable.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-03. +// Copyright © 2016-2017 Károly Lőrentey. +// + +import Foundation + +extension BigUInt: Comparable { + //MARK: Comparison + + /// Compare `a` to `b` and return an `NSComparisonResult` indicating their order. + /// + /// - Complexity: O(count) + public static func compare(_ a: BigUInt, _ b: BigUInt) -> ComparisonResult { + if a.count != b.count { return a.count > b.count ? .orderedDescending : .orderedAscending } + for i in (0 ..< a.count).reversed() { + let ad = a[i] + let bd = b[i] + if ad != bd { return ad > bd ? .orderedDescending : .orderedAscending } + } + return .orderedSame + } + + /// Return true iff `a` is equal to `b`. + /// + /// - Complexity: O(count) + public static func ==(a: BigUInt, b: BigUInt) -> Bool { + return BigUInt.compare(a, b) == .orderedSame + } + + /// Return true iff `a` is less than `b`. + /// + /// - Complexity: O(count) + public static func <(a: BigUInt, b: BigUInt) -> Bool { + return BigUInt.compare(a, b) == .orderedAscending + } +} + +extension BigInt { + /// Return true iff `a` is equal to `b`. + public static func ==(a: BigInt, b: BigInt) -> Bool { + return a.sign == b.sign && a.magnitude == b.magnitude + } + + /// Return true iff `a` is less than `b`. + public static func <(a: BigInt, b: BigInt) -> Bool { + switch (a.sign, b.sign) { + case (.plus, .plus): + return a.magnitude < b.magnitude + case (.plus, .minus): + return false + case (.minus, .plus): + return true + case (.minus, .minus): + return a.magnitude > b.magnitude + } + } +} + + diff --git a/dydx/Pods/BigInt/Sources/Data Conversion.swift b/dydx/Pods/BigInt/Sources/Data Conversion.swift new file mode 100644 index 000000000..25c65521d --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Data Conversion.swift @@ -0,0 +1,179 @@ +// +// Data Conversion.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-04. +// Copyright © 2016-2017 Károly Lőrentey. +// + +import Foundation + +extension BigUInt { + //MARK: NSData Conversion + + /// Initialize a BigInt from bytes accessed from an UnsafeRawBufferPointer + public init(_ buffer: UnsafeRawBufferPointer) { + // This assumes Word is binary. + precondition(Word.bitWidth % 8 == 0) + + self.init() + + let length = buffer.count + guard length > 0 else { return } + let bytesPerDigit = Word.bitWidth / 8 + var index = length / bytesPerDigit + var c = bytesPerDigit - length % bytesPerDigit + if c == bytesPerDigit { + c = 0 + index -= 1 + } + + var word: Word = 0 + for byte in buffer { + word <<= 8 + word += Word(byte) + c += 1 + if c == bytesPerDigit { + self[index] = word + index -= 1 + c = 0 + word = 0 + } + } + assert(c == 0 && word == 0 && index == -1) + } + + + /// Initializes an integer from the bits stored inside a piece of `Data`. + /// The data is assumed to be in network (big-endian) byte order. + public init(_ data: Data) { + // This assumes Word is binary. + precondition(Word.bitWidth % 8 == 0) + + self.init() + + let length = data.count + guard length > 0 else { return } + let bytesPerDigit = Word.bitWidth / 8 + var index = length / bytesPerDigit + var c = bytesPerDigit - length % bytesPerDigit + if c == bytesPerDigit { + c = 0 + index -= 1 + } + let word: Word = data.withUnsafeBytes { buffPtr in + var word: Word = 0 + let p = buffPtr.bindMemory(to: UInt8.self) + for byte in p { + word <<= 8 + word += Word(byte) + c += 1 + if c == bytesPerDigit { + self[index] = word + index -= 1 + c = 0 + word = 0 + } + } + return word + } + assert(c == 0 && word == 0 && index == -1) + } + + /// Return a `Data` value that contains the base-256 representation of this integer, in network (big-endian) byte order. + public func serialize() -> Data { + // This assumes Digit is binary. + precondition(Word.bitWidth % 8 == 0) + + let byteCount = (self.bitWidth + 7) / 8 + + guard byteCount > 0 else { return Data() } + + var data = Data(count: byteCount) + data.withUnsafeMutableBytes { buffPtr in + let p = buffPtr.bindMemory(to: UInt8.self) + var i = byteCount - 1 + for var word in self.words { + for _ in 0 ..< Word.bitWidth / 8 { + p[i] = UInt8(word & 0xFF) + word >>= 8 + if i == 0 { + assert(word == 0) + break + } + i -= 1 + } + } + } + return data + } +} + +extension BigInt { + + /// Initialize a BigInt from bytes accessed from an UnsafeRawBufferPointer, + /// where the first byte indicates sign (0 for positive, 1 for negative) + public init(_ buffer: UnsafeRawBufferPointer) { + // This assumes Word is binary. + precondition(Word.bitWidth % 8 == 0) + + self.init() + + let length = buffer.count + + // Serialized data for a BigInt should contain at least 2 bytes: one representing + // the sign, and another for the non-zero magnitude. Zero is represented by an + // empty Data struct, and negative zero is not supported. + guard length > 1, let firstByte = buffer.first else { return } + + // The first byte gives the sign + // This byte is compared to a bitmask to allow additional functionality to be added + // to this byte in the future. + self.sign = firstByte & 0b1 == 0 ? .plus : .minus + + self.magnitude = BigUInt(UnsafeRawBufferPointer(rebasing: buffer.dropFirst(1))) + } + + /// Initializes an integer from the bits stored inside a piece of `Data`. + /// The data is assumed to be in network (big-endian) byte order with a first + /// byte to represent the sign (0 for positive, 1 for negative) + public init(_ data: Data) { + // This assumes Word is binary. + // This is the same assumption made when initializing BigUInt from Data + precondition(Word.bitWidth % 8 == 0) + + self.init() + + // Serialized data for a BigInt should contain at least 2 bytes: one representing + // the sign, and another for the non-zero magnitude. Zero is represented by an + // empty Data struct, and negative zero is not supported. + guard data.count > 1, let firstByte = data.first else { return } + + // The first byte gives the sign + // This byte is compared to a bitmask to allow additional functionality to be added + // to this byte in the future. + self.sign = firstByte & 0b1 == 0 ? .plus : .minus + + // The remaining bytes are read and stored as the magnitude + self.magnitude = BigUInt(data.dropFirst(1)) + } + + /// Return a `Data` value that contains the base-256 representation of this integer, in network (big-endian) byte order and a prepended byte to indicate the sign (0 for positive, 1 for negative) + public func serialize() -> Data { + // Create a data object for the magnitude portion of the BigInt + let magnitudeData = self.magnitude.serialize() + + // Similar to BigUInt, a value of 0 should return an initialized, empty Data struct + guard magnitudeData.count > 0 else { return magnitudeData } + + // Create a new Data struct for the signed BigInt value + var data = Data(capacity: magnitudeData.count + 1) + + // The first byte should be 0 for a positive value, or 1 for a negative value + // i.e., the sign bit is the LSB + data.append(self.sign == .plus ? 0 : 1) + + data.append(magnitudeData) + return data + } +} diff --git a/dydx/Pods/BigInt/Sources/Division.swift b/dydx/Pods/BigInt/Sources/Division.swift new file mode 100644 index 000000000..4393f52e9 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Division.swift @@ -0,0 +1,374 @@ +// +// Division.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-03. +// Copyright © 2016-2017 Károly Lőrentey. +// + +//MARK: Full-width multiplication and division + +extension FixedWidthInteger where Magnitude == Self { + private var halfShift: Self { + return Self(Self.bitWidth / 2) + + } + private var high: Self { + return self &>> halfShift + } + + private var low: Self { + let mask: Self = 1 &<< halfShift - 1 + return self & mask + } + + private var upshifted: Self { + return self &<< halfShift + } + + private var split: (high: Self, low: Self) { + return (self.high, self.low) + } + + private init(_ value: (high: Self, low: Self)) { + self = value.high.upshifted + value.low + } + + /// Divide the double-width integer `dividend` by `self` and return the quotient and remainder. + /// + /// - Requires: `dividend.high < self`, so that the result will fit in a single digit. + /// - Complexity: O(1) with 2 divisions, 6 multiplications and ~12 or so additions/subtractions. + internal func fastDividingFullWidth(_ dividend: (high: Self, low: Self.Magnitude)) -> (quotient: Self, remainder: Self) { + // Division is complicated; doing it with single-digit operations is maddeningly complicated. + // This is a Swift adaptation for "divlu2" in Hacker's Delight, + // which is in turn a C adaptation of Knuth's Algorithm D (TAOCP vol 2, 4.3.1). + precondition(dividend.high < self) + + // This replaces the implementation in stdlib, which is much slower. + // FIXME: Speed up stdlib. It should use full-width idiv on Intel processors, and + // fall back to a reasonably fast algorithm elsewhere. + + // The trick here is that we're actually implementing a 4/2 long division using half-words, + // with the long division loop unrolled into two 3/2 half-word divisions. + // Luckily, 3/2 half-word division can be approximated by a single full-word division operation + // that, when the divisor is normalized, differs from the correct result by at most 2. + + /// Find the half-word quotient in `u / vn`, which must be normalized. + /// `u` contains three half-words in the two halves of `u.high` and the lower half of + /// `u.low`. (The weird distribution makes for a slightly better fit with the input.) + /// `vn` contains the normalized divisor, consisting of two half-words. + /// + /// - Requires: u.high < vn && u.low.high == 0 && vn.leadingZeroBitCount == 0 + func quotient(dividing u: (high: Self, low: Self), by vn: Self) -> Self { + let (vn1, vn0) = vn.split + // Get approximate quotient. + let (q, r) = u.high.quotientAndRemainder(dividingBy: vn1) + let p = q * vn0 + // q is often already correct, but sometimes the approximation overshoots by at most 2. + // The code that follows checks for this while being careful to only perform single-digit operations. + if q.high == 0 && p <= r.upshifted + u.low { return q } + let r2 = r + vn1 + if r2.high != 0 { return q - 1 } + if (q - 1).high == 0 && p - vn0 <= r2.upshifted + u.low { return q - 1 } + //assert((r + 2 * vn1).high != 0 || p - 2 * vn0 <= (r + 2 * vn1).upshifted + u.low) + return q - 2 + } + /// Divide 3 half-digits by 2 half-digits to get a half-digit quotient and a full-digit remainder. + /// + /// - Requires: u.high < v && u.low.high == 0 && vn.width = width(Digit) + func quotientAndRemainder(dividing u: (high: Self, low: Self), by v: Self) -> (quotient: Self, remainder: Self) { + let q = quotient(dividing: u, by: v) + // Note that `uh.low` masks off a couple of bits, and `q * v` and the + // subtraction are likely to overflow. Despite this, the end result (remainder) will + // still be correct and it will fit inside a single (full) Digit. + let r = Self(u) &- q &* v + assert(r < v) + return (q, r) + } + + // Normalize the dividend and the divisor (self) such that the divisor has no leading zeroes. + let z = Self(self.leadingZeroBitCount) + let w = Self(Self.bitWidth) - z + let vn = self << z + + let un32 = (z == 0 ? dividend.high : (dividend.high &<< z) | (dividend.low &>> w)) // No bits are lost + let un10 = dividend.low &<< z + let (un1, un0) = un10.split + + // Divide `(un32,un10)` by `vn`, splitting the full 4/2 division into two 3/2 ones. + let (q1, un21) = quotientAndRemainder(dividing: (un32, un1), by: vn) + let (q0, rn) = quotientAndRemainder(dividing: (un21, un0), by: vn) + + // Undo normalization of the remainder and combine the two halves of the quotient. + let mod = rn >> z + let div = Self((q1, q0)) + return (div, mod) + } + + /// Return the quotient of the 3/2-word division `x/y` as a single word. + /// + /// - Requires: (x.0, x.1) <= y && y.0.high != 0 + /// - Returns: The exact value when it fits in a single word, otherwise `Self`. + static func approximateQuotient(dividing x: (Self, Self, Self), by y: (Self, Self)) -> Self { + // Start with q = (x.0, x.1) / y.0, (or Word.max on overflow) + var q: Self + var r: Self + if x.0 == y.0 { + q = Self.max + let (s, o) = x.0.addingReportingOverflow(x.1) + if o { return q } + r = s + } + else { + (q, r) = y.0.fastDividingFullWidth((x.0, x.1)) + } + // Now refine q by considering x.2 and y.1. + // Note that since y is normalized, q * y - x is between 0 and 2. + let (ph, pl) = q.multipliedFullWidth(by: y.1) + if ph < r || (ph == r && pl <= x.2) { return q } + + let (r1, ro) = r.addingReportingOverflow(y.0) + if ro { return q - 1 } + + let (pl1, so) = pl.subtractingReportingOverflow(y.1) + let ph1 = (so ? ph - 1 : ph) + + if ph1 < r1 || (ph1 == r1 && pl1 <= x.2) { return q - 1 } + return q - 2 + } +} + +extension BigUInt { + //MARK: Division + + /// Divide this integer by the word `y`, leaving the quotient in its place and returning the remainder. + /// + /// - Requires: y > 0 + /// - Complexity: O(count) + internal mutating func divide(byWord y: Word) -> Word { + precondition(y > 0) + if y == 1 { return 0 } + + var remainder: Word = 0 + for i in (0 ..< count).reversed() { + let u = self[i] + (self[i], remainder) = y.fastDividingFullWidth((remainder, u)) + } + return remainder + } + + /// Divide this integer by the word `y` and return the resulting quotient and remainder. + /// + /// - Requires: y > 0 + /// - Returns: (quotient, remainder) where quotient = floor(x/y), remainder = x - quotient * y + /// - Complexity: O(x.count) + internal func quotientAndRemainder(dividingByWord y: Word) -> (quotient: BigUInt, remainder: Word) { + var div = self + let mod = div.divide(byWord: y) + return (div, mod) + } + + /// Divide `x` by `y`, putting the quotient in `x` and the remainder in `y`. + /// Reusing integers like this reduces the number of allocations during the calculation. + static func divide(_ x: inout BigUInt, by y: inout BigUInt) { + // This is a Swift adaptation of "divmnu" from Hacker's Delight, which is in + // turn a C adaptation of Knuth's Algorithm D (TAOCP vol 2, 4.3.1). + + precondition(!y.isZero) + + // First, let's take care of the easy cases. + if x < y { + (x, y) = (0, x) + return + } + if y.count == 1 { + // The single-word case reduces to a simpler loop. + y = BigUInt(x.divide(byWord: y[0])) + return + } + + // In the hard cases, we will perform the long division algorithm we learned in school. + // It works by successively calculating the single-word quotient of the top y.count + 1 + // words of x divided by y, replacing the top of x with the remainder, and repeating + // the process one word lower. + // + // The tricky part is that the algorithm needs to be able to do n+1/n word divisions, + // but we only have a primitive for dividing two words by a single + // word. (Remember that this step is also tricky when we do it on paper!) + // + // The solution is that the long division can be approximated by a single full division + // using just the most significant words. We can then use multiplications and + // subtractions to refine the approximation until we get the correct quotient word. + // + // We could do this by doing a simple 2/1 full division, but Knuth goes one step further, + // and implements a 3/2 division. This results in an exact approximation in the + // vast majority of cases, eliminating an extra subtraction over big integers. + // + // The function `approximateQuotient` above implements Knuth's 3/2 division algorithm. + // It requires that the divisor's most significant word is larger than + // Word.max / 2. This ensures that the approximation has tiny error bounds, + // which is what makes this entire approach viable. + // To satisfy this requirement, we will normalize the division by multiplying + // both the divisor and the dividend by the same (small) factor. + let z = y.leadingZeroBitCount + y <<= z + x <<= z // We'll calculate the remainder in the normalized dividend. + var quotient = BigUInt() + assert(y.leadingZeroBitCount == 0) + + // We're ready to start the long division! + let dc = y.count + let d1 = y[dc - 1] + let d0 = y[dc - 2] + var product: BigUInt = 0 + for j in (dc ... x.count).reversed() { + // Approximate dividing the top dc+1 words of `remainder` using the topmost 3/2 words. + let r2 = x[j] + let r1 = x[j - 1] + let r0 = x[j - 2] + let q = Word.approximateQuotient(dividing: (r2, r1, r0), by: (d1, d0)) + + // Multiply the entire divisor with `q` and subtract the result from remainder. + // Normalization ensures the 3/2 quotient will either be exact for the full division, or + // it may overshoot by at most 1, in which case the product will be greater + // than the remainder. + product.load(y) + product.multiply(byWord: q) + if product <= x.extract(j - dc ..< j + 1) { + x.subtract(product, shiftedBy: j - dc) + quotient[j - dc] = q + } + else { + // This case is extremely rare -- it has a probability of 1/2^(Word.bitWidth - 1). + x.add(y, shiftedBy: j - dc) + x.subtract(product, shiftedBy: j - dc) + quotient[j - dc] = q - 1 + } + } + // The remainder's normalization needs to be undone, but otherwise we're done. + x >>= z + y = x + x = quotient + } + + /// Divide `x` by `y`, putting the remainder in `x`. + mutating func formRemainder(dividingBy y: BigUInt, normalizedBy shift: Int) { + precondition(!y.isZero) + assert(y.leadingZeroBitCount == 0) + if y.count == 1 { + let remainder = self.divide(byWord: y[0] >> shift) + self.load(BigUInt(remainder)) + return + } + self <<= shift + if self >= y { + let dc = y.count + let d1 = y[dc - 1] + let d0 = y[dc - 2] + var product: BigUInt = 0 + for j in (dc ... self.count).reversed() { + let r2 = self[j] + let r1 = self[j - 1] + let r0 = self[j - 2] + let q = Word.approximateQuotient(dividing: (r2, r1, r0), by: (d1, d0)) + product.load(y) + product.multiply(byWord: q) + if product <= self.extract(j - dc ..< j + 1) { + self.subtract(product, shiftedBy: j - dc) + } + else { + self.add(y, shiftedBy: j - dc) + self.subtract(product, shiftedBy: j - dc) + } + } + } + self >>= shift + } + + + /// Divide this integer by `y` and return the resulting quotient and remainder. + /// + /// - Requires: `y > 0` + /// - Returns: `(quotient, remainder)` where `quotient = floor(self/y)`, `remainder = self - quotient * y` + /// - Complexity: O(count^2) + public func quotientAndRemainder(dividingBy y: BigUInt) -> (quotient: BigUInt, remainder: BigUInt) { + var x = self + var y = y + BigUInt.divide(&x, by: &y) + return (x, y) + } + + /// Divide `x` by `y` and return the quotient. + /// + /// - Note: Use `divided(by:)` if you also need the remainder. + public static func /(x: BigUInt, y: BigUInt) -> BigUInt { + return x.quotientAndRemainder(dividingBy: y).quotient + } + + /// Divide `x` by `y` and return the remainder. + /// + /// - Note: Use `divided(by:)` if you also need the remainder. + public static func %(x: BigUInt, y: BigUInt) -> BigUInt { + var x = x + let shift = y.leadingZeroBitCount + x.formRemainder(dividingBy: y << shift, normalizedBy: shift) + return x + } + + /// Divide `x` by `y` and store the quotient in `x`. + /// + /// - Note: Use `divided(by:)` if you also need the remainder. + public static func /=(x: inout BigUInt, y: BigUInt) { + var y = y + BigUInt.divide(&x, by: &y) + } + + /// Divide `x` by `y` and store the remainder in `x`. + /// + /// - Note: Use `divided(by:)` if you also need the remainder. + public static func %=(x: inout BigUInt, y: BigUInt) { + let shift = y.leadingZeroBitCount + x.formRemainder(dividingBy: y << shift, normalizedBy: shift) + } +} + +extension BigInt { + /// Divide this integer by `y` and return the resulting quotient and remainder. + /// + /// - Requires: `y > 0` + /// - Returns: `(quotient, remainder)` where `quotient = floor(self/y)`, `remainder = self - quotient * y` + /// - Complexity: O(count^2) + public func quotientAndRemainder(dividingBy y: BigInt) -> (quotient: BigInt, remainder: BigInt) { + var a = self.magnitude + var b = y.magnitude + BigUInt.divide(&a, by: &b) + return (BigInt(sign: self.sign == y.sign ? .plus : .minus, magnitude: a), + BigInt(sign: self.sign, magnitude: b)) + } + + /// Divide `a` by `b` and return the quotient. Traps if `b` is zero. + public static func /(a: BigInt, b: BigInt) -> BigInt { + return BigInt(sign: a.sign == b.sign ? .plus : .minus, magnitude: a.magnitude / b.magnitude) + } + + /// Divide `a` by `b` and return the remainder. The result has the same sign as `a`. + public static func %(a: BigInt, b: BigInt) -> BigInt { + return BigInt(sign: a.sign, magnitude: a.magnitude % b.magnitude) + } + + /// Return the result of `a` mod `b`. The result is always a nonnegative integer that is less than the absolute value of `b`. + public func modulus(_ mod: BigInt) -> BigInt { + let remainder = self.magnitude % mod.magnitude + return BigInt( + self.sign == .minus && !remainder.isZero + ? mod.magnitude - remainder + : remainder) + } +} + +extension BigInt { + /// Divide `a` by `b` storing the quotient in `a`. + public static func /=(a: inout BigInt, b: BigInt) { a = a / b } + /// Divide `a` by `b` storing the remainder in `a`. + public static func %=(a: inout BigInt, b: BigInt) { a = a % b } +} diff --git a/dydx/Pods/BigInt/Sources/Exponentiation.swift b/dydx/Pods/BigInt/Sources/Exponentiation.swift new file mode 100644 index 000000000..9d7ee85da --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Exponentiation.swift @@ -0,0 +1,119 @@ +// +// Exponentiation.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-03. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension BigUInt { + //MARK: Exponentiation + + /// Returns this integer raised to the power `exponent`. + /// + /// This function calculates the result by [successively squaring the base while halving the exponent][expsqr]. + /// + /// [expsqr]: https://en.wikipedia.org/wiki/Exponentiation_by_squaring + /// + /// - Note: This function can be unreasonably expensive for large exponents, which is why `exponent` is + /// a simple integer value. If you want to calculate big exponents, you'll probably need to use + /// the modulo arithmetic variant. + /// - Returns: 1 if `exponent == 0`, otherwise `self` raised to `exponent`. (This implies that `0.power(0) == 1`.) + /// - SeeAlso: `BigUInt.power(_:, modulus:)` + /// - Complexity: O((exponent * self.count)^log2(3)) or somesuch. The result may require a large amount of memory, too. + public func power(_ exponent: Int) -> BigUInt { + if exponent == 0 { return 1 } + if exponent == 1 { return self } + if exponent < 0 { + precondition(!self.isZero) + return self == 1 ? 1 : 0 + } + if self <= 1 { return self } + var result = BigUInt(1) + var b = self + var e = exponent + while e > 0 { + if e & 1 == 1 { + result *= b + } + e >>= 1 + b *= b + } + return result + } + + /// Returns the remainder of this integer raised to the power `exponent` in modulo arithmetic under `modulus`. + /// + /// Uses the [right-to-left binary method][rtlb]. + /// + /// [rtlb]: https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method + /// + /// - Complexity: O(exponent.count * modulus.count^log2(3)) or somesuch + public func power(_ exponent: BigUInt, modulus: BigUInt) -> BigUInt { + precondition(!modulus.isZero) + if modulus == (1 as BigUInt) { return 0 } + let shift = modulus.leadingZeroBitCount + let normalizedModulus = modulus << shift + var result = BigUInt(1) + var b = self + b.formRemainder(dividingBy: normalizedModulus, normalizedBy: shift) + for var e in exponent.words { + for _ in 0 ..< Word.bitWidth { + if e & 1 == 1 { + result *= b + result.formRemainder(dividingBy: normalizedModulus, normalizedBy: shift) + } + e >>= 1 + b *= b + b.formRemainder(dividingBy: normalizedModulus, normalizedBy: shift) + } + } + return result + } +} + +extension BigInt { + /// Returns this integer raised to the power `exponent`. + /// + /// This function calculates the result by [successively squaring the base while halving the exponent][expsqr]. + /// + /// [expsqr]: https://en.wikipedia.org/wiki/Exponentiation_by_squaring + /// + /// - Note: This function can be unreasonably expensive for large exponents, which is why `exponent` is + /// a simple integer value. If you want to calculate big exponents, you'll probably need to use + /// the modulo arithmetic variant. + /// - Returns: 1 if `exponent == 0`, otherwise `self` raised to `exponent`. (This implies that `0.power(0) == 1`.) + /// - SeeAlso: `BigUInt.power(_:, modulus:)` + /// - Complexity: O((exponent * self.count)^log2(3)) or somesuch. The result may require a large amount of memory, too. + public func power(_ exponent: Int) -> BigInt { + return BigInt(sign: self.sign == .minus && exponent & 1 != 0 ? .minus : .plus, + magnitude: self.magnitude.power(exponent)) + } + + /// Returns the remainder of this integer raised to the power `exponent` in modulo arithmetic under `modulus`. + /// + /// Uses the [right-to-left binary method][rtlb]. + /// + /// [rtlb]: https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method + /// + /// - Complexity: O(exponent.count * modulus.count^log2(3)) or somesuch + public func power(_ exponent: BigInt, modulus: BigInt) -> BigInt { + precondition(!modulus.isZero) + if modulus.magnitude == 1 { return 0 } + if exponent.isZero { return 1 } + if exponent == 1 { return self.modulus(modulus) } + if exponent < 0 { + precondition(!self.isZero) + guard magnitude == 1 else { return 0 } + guard sign == .minus else { return 1 } + guard exponent.magnitude[0] & 1 != 0 else { return 1 } + return BigInt(modulus.magnitude - 1) + } + let power = self.magnitude.power(exponent.magnitude, + modulus: modulus.magnitude) + if self.sign == .plus || exponent.magnitude[0] & 1 == 0 || power.isZero { + return BigInt(power) + } + return BigInt(modulus.magnitude - power) + } +} diff --git a/dydx/Pods/BigInt/Sources/Floating Point Conversion.swift b/dydx/Pods/BigInt/Sources/Floating Point Conversion.swift new file mode 100644 index 000000000..6c2395a31 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Floating Point Conversion.swift @@ -0,0 +1,73 @@ +// +// Floating Point Conversion.swift +// BigInt +// +// Created by Károly Lőrentey on 2017-08-11. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension BigUInt { + public init?(exactly source: T) { + guard source.isFinite else { return nil } + guard !source.isZero else { self = 0; return } + guard source.sign == .plus else { return nil } + let value = source.rounded(.towardZero) + guard value == source else { return nil } + assert(value.floatingPointClass == .positiveNormal) + assert(value.exponent >= 0) + let significand = value.significandBitPattern + self = (BigUInt(1) << value.exponent) + BigUInt(significand) >> (T.significandBitCount - Int(value.exponent)) + } + + public init(_ source: T) { + self.init(exactly: source.rounded(.towardZero))! + } +} + +extension BigInt { + public init?(exactly source: T) { + switch source.sign{ + case .plus: + guard let magnitude = BigUInt(exactly: source) else { return nil } + self = BigInt(sign: .plus, magnitude: magnitude) + case .minus: + guard let magnitude = BigUInt(exactly: -source) else { return nil } + self = BigInt(sign: .minus, magnitude: magnitude) + } + } + + public init(_ source: T) { + self.init(exactly: source.rounded(.towardZero))! + } +} + +extension BinaryFloatingPoint where RawExponent: FixedWidthInteger, RawSignificand: FixedWidthInteger { + public init(_ value: BigInt) { + guard !value.isZero else { self = 0; return } + let v = value.magnitude + let bitWidth = v.bitWidth + var exponent = bitWidth - 1 + let shift = bitWidth - Self.significandBitCount - 1 + var significand = value.magnitude >> (shift - 1) + if significand[0] & 3 == 3 { // Handle rounding + significand >>= 1 + significand += 1 + if significand.trailingZeroBitCount >= Self.significandBitCount { + exponent += 1 + } + } + else { + significand >>= 1 + } + let bias = 1 << (Self.exponentBitCount - 1) - 1 + guard exponent <= bias else { self = Self.infinity; return } + significand &= 1 << Self.significandBitCount - 1 + self = Self.init(sign: value.sign == .plus ? .plus : .minus, + exponentBitPattern: RawExponent(bias + exponent), + significandBitPattern: RawSignificand(significand)) + } + + public init(_ value: BigUInt) { + self.init(BigInt(sign: .plus, magnitude: value)) + } +} diff --git a/dydx/Pods/BigInt/Sources/GCD.swift b/dydx/Pods/BigInt/Sources/GCD.swift new file mode 100644 index 000000000..d55605dce --- /dev/null +++ b/dydx/Pods/BigInt/Sources/GCD.swift @@ -0,0 +1,80 @@ +// +// GCD.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-03. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension BigUInt { + //MARK: Greatest Common Divisor + + /// Returns the greatest common divisor of `self` and `b`. + /// + /// - Complexity: O(count^2) where count = max(self.count, b.count) + public func greatestCommonDivisor(with b: BigUInt) -> BigUInt { + // This is Stein's algorithm: https://en.wikipedia.org/wiki/Binary_GCD_algorithm + if self.isZero { return b } + if b.isZero { return self } + + let az = self.trailingZeroBitCount + let bz = b.trailingZeroBitCount + let twos = Swift.min(az, bz) + + var (x, y) = (self >> az, b >> bz) + if x < y { swap(&x, &y) } + + while !x.isZero { + x >>= x.trailingZeroBitCount + if x < y { swap(&x, &y) } + x -= y + } + return y << twos + } + + /// Returns the [multiplicative inverse of this integer in modulo `modulus` arithmetic][inverse], + /// or `nil` if there is no such number. + /// + /// [inverse]: https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers + /// + /// - Returns: If `gcd(self, modulus) == 1`, the value returned is an integer `a < modulus` such that `(a * self) % modulus == 1`. If `self` and `modulus` aren't coprime, the return value is `nil`. + /// - Requires: modulus > 1 + /// - Complexity: O(count^3) + public func inverse(_ modulus: BigUInt) -> BigUInt? { + precondition(modulus > 1) + var t1 = BigInt(0) + var t2 = BigInt(1) + var r1 = modulus + var r2 = self + while !r2.isZero { + let quotient = r1 / r2 + (t1, t2) = (t2, t1 - BigInt(quotient) * t2) + (r1, r2) = (r2, r1 - quotient * r2) + } + if r1 > 1 { return nil } + if t1.sign == .minus { return modulus - t1.magnitude } + return t1.magnitude + } +} + +extension BigInt { + /// Returns the greatest common divisor of `a` and `b`. + /// + /// - Complexity: O(count^2) where count = max(a.count, b.count) + public func greatestCommonDivisor(with b: BigInt) -> BigInt { + return BigInt(self.magnitude.greatestCommonDivisor(with: b.magnitude)) + } + + /// Returns the [multiplicative inverse of this integer in modulo `modulus` arithmetic][inverse], + /// or `nil` if there is no such number. + /// + /// [inverse]: https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers + /// + /// - Returns: If `gcd(self, modulus) == 1`, the value returned is an integer `a < modulus` such that `(a * self) % modulus == 1`. If `self` and `modulus` aren't coprime, the return value is `nil`. + /// - Requires: modulus.magnitude > 1 + /// - Complexity: O(count^3) + public func inverse(_ modulus: BigInt) -> BigInt? { + guard let inv = self.magnitude.inverse(modulus.magnitude) else { return nil } + return BigInt(self.sign == .plus || inv.isZero ? inv : modulus.magnitude - inv) + } +} diff --git a/dydx/Pods/BigInt/Sources/Hashable.swift b/dydx/Pods/BigInt/Sources/Hashable.swift new file mode 100644 index 000000000..c5dc0e642 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Hashable.swift @@ -0,0 +1,26 @@ +// +// Hashable.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-03. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension BigUInt: Hashable { + //MARK: Hashing + + /// Append this `BigUInt` to the specified hasher. + public func hash(into hasher: inout Hasher) { + for word in self.words { + hasher.combine(word) + } + } +} + +extension BigInt: Hashable { + /// Append this `BigInt` to the specified hasher. + public func hash(into hasher: inout Hasher) { + hasher.combine(sign) + hasher.combine(magnitude) + } +} diff --git a/dydx/Pods/BigInt/Sources/Integer Conversion.swift b/dydx/Pods/BigInt/Sources/Integer Conversion.swift new file mode 100644 index 000000000..9a210e4a4 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Integer Conversion.swift @@ -0,0 +1,89 @@ +// +// Integer Conversion.swift +// BigInt +// +// Created by Károly Lőrentey on 2017-08-11. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension BigUInt { + public init?(exactly source: T) { + guard source >= (0 as T) else { return nil } + if source.bitWidth <= 2 * Word.bitWidth { + var it = source.words.makeIterator() + self.init(low: it.next() ?? 0, high: it.next() ?? 0) + precondition(it.next() == nil, "Length of BinaryInteger.words is greater than its bitWidth") + } + else { + self.init(words: source.words) + } + } + + public init(_ source: T) { + precondition(source >= (0 as T), "BigUInt cannot represent negative values") + self.init(exactly: source)! + } + + public init(truncatingIfNeeded source: T) { + self.init(words: source.words) + } + + public init(clamping source: T) { + if source <= (0 as T) { + self.init() + } + else { + self.init(words: source.words) + } + } +} + +extension BigInt { + public init() { + self.init(sign: .plus, magnitude: 0) + } + + /// Initializes a new signed big integer with the same value as the specified unsigned big integer. + public init(_ integer: BigUInt) { + self.magnitude = integer + self.sign = .plus + } + + public init(_ source: T) where T : BinaryInteger { + if source >= (0 as T) { + self.init(sign: .plus, magnitude: BigUInt(source)) + } + else { + var words = Array(source.words) + words.twosComplement() + self.init(sign: .minus, magnitude: BigUInt(words: words)) + } + } + + public init?(exactly source: T) where T : BinaryInteger { + self.init(source) + } + + public init(clamping source: T) where T : BinaryInteger { + self.init(source) + } + + public init(truncatingIfNeeded source: T) where T : BinaryInteger { + self.init(source) + } +} + +extension BigUInt: ExpressibleByIntegerLiteral { + /// Initialize a new big integer from an integer literal. + public init(integerLiteral value: UInt64) { + self.init(value) + } +} + +extension BigInt: ExpressibleByIntegerLiteral { + /// Initialize a new big integer from an integer literal. + public init(integerLiteral value: Int64) { + self.init(value) + } +} + diff --git a/dydx/Pods/BigInt/Sources/Multiplication.swift b/dydx/Pods/BigInt/Sources/Multiplication.swift new file mode 100644 index 000000000..635c36a5f --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Multiplication.swift @@ -0,0 +1,165 @@ +// +// Multiplication.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-03. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension BigUInt { + + //MARK: Multiplication + + /// Multiply this big integer by a single word, and store the result in place of the original big integer. + /// + /// - Complexity: O(count) + public mutating func multiply(byWord y: Word) { + guard y != 0 else { self = 0; return } + guard y != 1 else { return } + var carry: Word = 0 + let c = self.count + for i in 0 ..< c { + let (h, l) = self[i].multipliedFullWidth(by: y) + let (low, o) = l.addingReportingOverflow(carry) + self[i] = low + carry = (o ? h + 1 : h) + } + self[c] = carry + } + + /// Multiply this big integer by a single Word, and return the result. + /// + /// - Complexity: O(count) + public func multiplied(byWord y: Word) -> BigUInt { + var r = self + r.multiply(byWord: y) + return r + } + + /// Multiply `x` by `y`, and add the result to this integer, optionally shifted `shift` words to the left. + /// + /// - Note: This is the fused multiply/shift/add operation; it is more efficient than doing the components + /// individually. (The fused operation doesn't need to allocate space for temporary big integers.) + /// - Returns: `self` is set to `self + (x * y) << (shift * 2^Word.bitWidth)` + /// - Complexity: O(count) + public mutating func multiplyAndAdd(_ x: BigUInt, _ y: Word, shiftedBy shift: Int = 0) { + precondition(shift >= 0) + guard y != 0 && x.count > 0 else { return } + guard y != 1 else { self.add(x, shiftedBy: shift); return } + var mulCarry: Word = 0 + var addCarry = false + let xc = x.count + var xi = 0 + while xi < xc || addCarry || mulCarry > 0 { + let (h, l) = x[xi].multipliedFullWidth(by: y) + let (low, o) = l.addingReportingOverflow(mulCarry) + mulCarry = (o ? h + 1 : h) + + let ai = shift + xi + let (sum1, so1) = self[ai].addingReportingOverflow(low) + if addCarry { + let (sum2, so2) = sum1.addingReportingOverflow(1) + self[ai] = sum2 + addCarry = so1 || so2 + } + else { + self[ai] = sum1 + addCarry = so1 + } + xi += 1 + } + } + + /// Multiply this integer by `y` and return the result. + /// + /// - Note: This uses the naive O(n^2) multiplication algorithm unless both arguments have more than + /// `BigUInt.directMultiplicationLimit` words. + /// - Complexity: O(n^log2(3)) + public func multiplied(by y: BigUInt) -> BigUInt { + // This method is mostly defined for symmetry with the rest of the arithmetic operations. + return self * y + } + + /// Multiplication switches to an asymptotically better recursive algorithm when arguments have more words than this limit. + public static var directMultiplicationLimit: Int = 1024 + + /// Multiply `a` by `b` and return the result. + /// + /// - Note: This uses the naive O(n^2) multiplication algorithm unless both arguments have more than + /// `BigUInt.directMultiplicationLimit` words. + /// - Complexity: O(n^log2(3)) + public static func *(x: BigUInt, y: BigUInt) -> BigUInt { + let xc = x.count + let yc = y.count + if xc == 0 { return BigUInt() } + if yc == 0 { return BigUInt() } + if yc == 1 { return x.multiplied(byWord: y[0]) } + if xc == 1 { return y.multiplied(byWord: x[0]) } + + if Swift.min(xc, yc) <= BigUInt.directMultiplicationLimit { + // Long multiplication. + let left = (xc < yc ? y : x) + let right = (xc < yc ? x : y) + var result = BigUInt() + for i in (0 ..< right.count).reversed() { + result.multiplyAndAdd(left, right[i], shiftedBy: i) + } + return result + } + + if yc < xc { + let (xh, xl) = x.split + var r = xl * y + r.add(xh * y, shiftedBy: x.middleIndex) + return r + } + else if xc < yc { + let (yh, yl) = y.split + var r = yl * x + r.add(yh * x, shiftedBy: y.middleIndex) + return r + } + + let shift = x.middleIndex + + // Karatsuba multiplication: + // x * y = * = (ignoring carry) + let (a, b) = x.split + let (c, d) = y.split + + let high = a * c + let low = b * d + let xp = a >= b + let yp = c >= d + let xm = (xp ? a - b : b - a) + let ym = (yp ? c - d : d - c) + let m = xm * ym + + var r = low + r.add(high, shiftedBy: 2 * shift) + r.add(low, shiftedBy: shift) + r.add(high, shiftedBy: shift) + if xp == yp { + r.subtract(m, shiftedBy: shift) + } + else { + r.add(m, shiftedBy: shift) + } + return r + } + + /// Multiply `a` by `b` and store the result in `a`. + public static func *=(a: inout BigUInt, b: BigUInt) { + a = a * b + } +} + +extension BigInt { + /// Multiply `a` with `b` and return the result. + public static func *(a: BigInt, b: BigInt) -> BigInt { + return BigInt(sign: a.sign == b.sign ? .plus : .minus, magnitude: a.magnitude * b.magnitude) + } + + /// Multiply `a` with `b` in place. + public static func *=(a: inout BigInt, b: BigInt) { a = a * b } +} diff --git a/dydx/Pods/BigInt/Sources/Prime Test.swift b/dydx/Pods/BigInt/Sources/Prime Test.swift new file mode 100644 index 000000000..7f1871104 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Prime Test.swift @@ -0,0 +1,153 @@ +// +// Prime Test.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-04. +// Copyright © 2016-2017 Károly Lőrentey. +// + +/// The first several [prime numbers][primes]. +/// +/// [primes]: https://oeis.org/A000040 +let primes: [BigUInt.Word] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41] + +/// The ith element in this sequence is the smallest composite number that passes the strong probable prime test +/// for all of the first (i+1) primes. +/// +/// This is sequence [A014233](http://oeis.org/A014233) on the [Online Encyclopaedia of Integer Sequences](http://oeis.org). +let pseudoPrimes: [BigUInt] = [ + /* 2 */ 2_047, + /* 3 */ 1_373_653, + /* 5 */ 25_326_001, + /* 7 */ 3_215_031_751, + /* 11 */ 2_152_302_898_747, + /* 13 */ 3_474_749_660_383, + /* 17 */ 341_550_071_728_321, + /* 19 */ 341_550_071_728_321, + /* 23 */ 3_825_123_056_546_413_051, + /* 29 */ 3_825_123_056_546_413_051, + /* 31 */ 3_825_123_056_546_413_051, + /* 37 */ "318665857834031151167461", + /* 41 */ "3317044064679887385961981", +] + +extension BigUInt { + //MARK: Primality Testing + + /// Returns true iff this integer passes the [strong probable prime test][sppt] for the specified base. + /// + /// [sppt]: https://en.wikipedia.org/wiki/Probable_prime + public func isStrongProbablePrime(_ base: BigUInt) -> Bool { + precondition(base > (1 as BigUInt)) + precondition(self > (0 as BigUInt)) + let dec = self - 1 + + let r = dec.trailingZeroBitCount + let d = dec >> r + + var test = base.power(d, modulus: self) + if test == 1 || test == dec { return true } + + if r > 0 { + let shift = self.leadingZeroBitCount + let normalized = self << shift + for _ in 1 ..< r { + test *= test + test.formRemainder(dividingBy: normalized, normalizedBy: shift) + if test == 1 { + return false + } + if test == dec { return true } + } + } + return false + } + + /// Returns true if this integer is probably prime. Returns false if this integer is definitely not prime. + /// + /// This function performs a probabilistic [Miller-Rabin Primality Test][mrpt], consisting of `rounds` iterations, + /// each calculating the strong probable prime test for a random base. The number of rounds is 10 by default, + /// but you may specify your own choice. + /// + /// To speed things up, the function checks if `self` is divisible by the first few prime numbers before + /// diving into (slower) Miller-Rabin testing. + /// + /// Also, when `self` is less than 82 bits wide, `isPrime` does a deterministic test that is guaranteed to + /// return a correct result. + /// + /// [mrpt]: https://en.wikipedia.org/wiki/Miller–Rabin_primality_test + public func isPrime(rounds: Int = 10) -> Bool { + if count <= 1 && self[0] < 2 { return false } + if count == 1 && self[0] < 4 { return true } + + // Even numbers above 2 aren't prime. + if self[0] & 1 == 0 { return false } + + // Quickly check for small primes. + for i in 1 ..< primes.count { + let p = primes[i] + if self.count == 1 && self[0] == p { + return true + } + if self.quotientAndRemainder(dividingByWord: p).remainder == 0 { + return false + } + } + + /// Give an exact answer when we can. + if self < pseudoPrimes.last! { + for i in 0 ..< pseudoPrimes.count { + guard isStrongProbablePrime(BigUInt(primes[i])) else { + break + } + if self < pseudoPrimes[i] { + // `self` is below the lowest pseudoprime corresponding to the prime bases we tested. It's a prime! + return true + } + } + return false + } + + /// Otherwise do as many rounds of random SPPT as required. + for _ in 0 ..< rounds { + let random = BigUInt.randomInteger(lessThan: self - 2) + 2 + guard isStrongProbablePrime(random) else { + return false + } + } + + // Well, it smells primey to me. + return true + } +} + +extension BigInt { + //MARK: Primality Testing + + /// Returns true iff this integer passes the [strong probable prime test][sppt] for the specified base. + /// + /// [sppt]: https://en.wikipedia.org/wiki/Probable_prime + public func isStrongProbablePrime(_ base: BigInt) -> Bool { + precondition(base.sign == .plus) + if self.sign == .minus { return false } + return self.magnitude.isStrongProbablePrime(base.magnitude) + } + + /// Returns true if this integer is probably prime. Returns false if this integer is definitely not prime. + /// + /// This function performs a probabilistic [Miller-Rabin Primality Test][mrpt], consisting of `rounds` iterations, + /// each calculating the strong probable prime test for a random base. The number of rounds is 10 by default, + /// but you may specify your own choice. + /// + /// To speed things up, the function checks if `self` is divisible by the first few prime numbers before + /// diving into (slower) Miller-Rabin testing. + /// + /// Also, when `self` is less than 82 bits wide, `isPrime` does a deterministic test that is guaranteed to + /// return a correct result. + /// + /// [mrpt]: https://en.wikipedia.org/wiki/Miller–Rabin_primality_test + public func isPrime(rounds: Int = 10) -> Bool { + if self.sign == .minus { return false } + return self.magnitude.isPrime(rounds: rounds) + } +} diff --git a/dydx/Pods/BigInt/Sources/Random.swift b/dydx/Pods/BigInt/Sources/Random.swift new file mode 100644 index 000000000..bea98caf0 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Random.swift @@ -0,0 +1,101 @@ +// +// Random.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-04. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension BigUInt { + /// Create a big unsigned integer consisting of `width` uniformly distributed random bits. + /// + /// - Parameter width: The maximum number of one bits in the result. + /// - Parameter generator: The source of randomness. + /// - Returns: A big unsigned integer less than `1 << width`. + public static func randomInteger(withMaximumWidth width: Int, using generator: inout RNG) -> BigUInt { + var result = BigUInt.zero + var bitsLeft = width + var i = 0 + let wordsNeeded = (width + Word.bitWidth - 1) / Word.bitWidth + if wordsNeeded > 2 { + result.reserveCapacity(wordsNeeded) + } + while bitsLeft >= Word.bitWidth { + result[i] = generator.next() + i += 1 + bitsLeft -= Word.bitWidth + } + if bitsLeft > 0 { + let mask: Word = (1 << bitsLeft) - 1 + result[i] = (generator.next() as Word) & mask + } + return result + } + + /// Create a big unsigned integer consisting of `width` uniformly distributed random bits. + /// + /// - Note: I use a `SystemRandomGeneratorGenerator` as the source of randomness. + /// + /// - Parameter width: The maximum number of one bits in the result. + /// - Returns: A big unsigned integer less than `1 << width`. + public static func randomInteger(withMaximumWidth width: Int) -> BigUInt { + var rng = SystemRandomNumberGenerator() + return randomInteger(withMaximumWidth: width, using: &rng) + } + + /// Create a big unsigned integer consisting of `width-1` uniformly distributed random bits followed by a one bit. + /// + /// - Note: If `width` is zero, the result is zero. + /// + /// - Parameter width: The number of bits required to represent the answer. + /// - Parameter generator: The source of randomness. + /// - Returns: A random big unsigned integer whose width is `width`. + public static func randomInteger(withExactWidth width: Int, using generator: inout RNG) -> BigUInt { + // width == 0 -> return 0 because there is no room for a one bit. + // width == 1 -> return 1 because there is no room for any random bits. + guard width > 1 else { return BigUInt(width) } + var result = randomInteger(withMaximumWidth: width - 1, using: &generator) + result[(width - 1) / Word.bitWidth] |= 1 << Word((width - 1) % Word.bitWidth) + return result + } + + /// Create a big unsigned integer consisting of `width-1` uniformly distributed random bits followed by a one bit. + /// + /// - Note: If `width` is zero, the result is zero. + /// - Note: I use a `SystemRandomGeneratorGenerator` as the source of randomness. + /// + /// - Returns: A random big unsigned integer whose width is `width`. + public static func randomInteger(withExactWidth width: Int) -> BigUInt { + var rng = SystemRandomNumberGenerator() + return randomInteger(withExactWidth: width, using: &rng) + } + + /// Create a uniformly distributed random unsigned integer that's less than the specified limit. + /// + /// - Precondition: `limit > 0`. + /// + /// - Parameter limit: The upper bound on the result. + /// - Parameter generator: The source of randomness. + /// - Returns: A random big unsigned integer that is less than `limit`. + public static func randomInteger(lessThan limit: BigUInt, using generator: inout RNG) -> BigUInt { + precondition(limit > 0, "\(#function): 0 is not a valid limit") + let width = limit.bitWidth + var random = randomInteger(withMaximumWidth: width, using: &generator) + while random >= limit { + random = randomInteger(withMaximumWidth: width, using: &generator) + } + return random + } + + /// Create a uniformly distributed random unsigned integer that's less than the specified limit. + /// + /// - Precondition: `limit > 0`. + /// - Note: I use a `SystemRandomGeneratorGenerator` as the source of randomness. + /// + /// - Parameter limit: The upper bound on the result. + /// - Returns: A random big unsigned integer that is less than `limit`. + public static func randomInteger(lessThan limit: BigUInt) -> BigUInt { + var rng = SystemRandomNumberGenerator() + return randomInteger(lessThan: limit, using: &rng) + } +} diff --git a/dydx/Pods/BigInt/Sources/Shifts.swift b/dydx/Pods/BigInt/Sources/Shifts.swift new file mode 100644 index 000000000..e676e4143 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Shifts.swift @@ -0,0 +1,211 @@ +// +// Shifts.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-03. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension BigUInt { + + //MARK: Shift Operators + + internal func shiftedLeft(by amount: Word) -> BigUInt { + guard amount > 0 else { return self } + + let ext = Int(amount / Word(Word.bitWidth)) // External shift amount (new words) + let up = Word(amount % Word(Word.bitWidth)) // Internal shift amount (subword shift) + let down = Word(Word.bitWidth) - up + + var result = BigUInt() + if up > 0 { + var i = 0 + var lowbits: Word = 0 + while i < self.count || lowbits > 0 { + let word = self[i] + result[i + ext] = word << up | lowbits + lowbits = word >> down + i += 1 + } + } + else { + for i in 0 ..< self.count { + result[i + ext] = self[i] + } + } + return result + } + + internal mutating func shiftLeft(by amount: Word) { + guard amount > 0 else { return } + + let ext = Int(amount / Word(Word.bitWidth)) // External shift amount (new words) + let up = Word(amount % Word(Word.bitWidth)) // Internal shift amount (subword shift) + let down = Word(Word.bitWidth) - up + + if up > 0 { + var i = 0 + var lowbits: Word = 0 + while i < self.count || lowbits > 0 { + let word = self[i] + self[i] = word << up | lowbits + lowbits = word >> down + i += 1 + } + } + if ext > 0 && self.count > 0 { + self.shiftLeft(byWords: ext) + } + } + + internal func shiftedRight(by amount: Word) -> BigUInt { + guard amount > 0 else { return self } + guard amount < self.bitWidth else { return 0 } + + let ext = Int(amount / Word(Word.bitWidth)) // External shift amount (new words) + let down = Word(amount % Word(Word.bitWidth)) // Internal shift amount (subword shift) + let up = Word(Word.bitWidth) - down + + var result = BigUInt() + if down > 0 { + var highbits: Word = 0 + for i in (ext ..< self.count).reversed() { + let word = self[i] + result[i - ext] = highbits | word >> down + highbits = word << up + } + } + else { + for i in (ext ..< self.count).reversed() { + result[i - ext] = self[i] + } + } + return result + } + + internal mutating func shiftRight(by amount: Word) { + guard amount > 0 else { return } + guard amount < self.bitWidth else { self.clear(); return } + + let ext = Int(amount / Word(Word.bitWidth)) // External shift amount (new words) + let down = Word(amount % Word(Word.bitWidth)) // Internal shift amount (subword shift) + let up = Word(Word.bitWidth) - down + + if ext > 0 { + self.shiftRight(byWords: ext) + } + if down > 0 { + var i = self.count - 1 + var highbits: Word = 0 + while i >= 0 { + let word = self[i] + self[i] = highbits | word >> down + highbits = word << up + i -= 1 + } + } + } + + public static func >>=(lhs: inout BigUInt, rhs: Other) { + if rhs < (0 as Other) { + lhs <<= (0 - rhs) + } + else if rhs >= lhs.bitWidth { + lhs.clear() + } + else { + lhs.shiftRight(by: UInt(rhs)) + } + } + + public static func <<=(lhs: inout BigUInt, rhs: Other) { + if rhs < (0 as Other) { + lhs >>= (0 - rhs) + return + } + lhs.shiftLeft(by: Word(exactly: rhs)!) + } + + public static func >>(lhs: BigUInt, rhs: Other) -> BigUInt { + if rhs < (0 as Other) { + return lhs << (0 - rhs) + } + if rhs > Word.max { + return 0 + } + return lhs.shiftedRight(by: UInt(rhs)) + } + + public static func <<(lhs: BigUInt, rhs: Other) -> BigUInt { + if rhs < (0 as Other) { + return lhs >> (0 - rhs) + } + return lhs.shiftedLeft(by: Word(exactly: rhs)!) + } +} + +extension BigInt { + func shiftedLeft(by amount: Word) -> BigInt { + return BigInt(sign: self.sign, magnitude: self.magnitude.shiftedLeft(by: amount)) + } + + mutating func shiftLeft(by amount: Word) { + self.magnitude.shiftLeft(by: amount) + } + + func shiftedRight(by amount: Word) -> BigInt { + let m = self.magnitude.shiftedRight(by: amount) + return BigInt(sign: self.sign, magnitude: self.sign == .minus && m.isZero ? 1 : m) + } + + mutating func shiftRight(by amount: Word) { + magnitude.shiftRight(by: amount) + if sign == .minus, magnitude.isZero { + magnitude.load(1) + } + } + + public static func &<<(left: BigInt, right: BigInt) -> BigInt { + return left.shiftedLeft(by: right.words[0]) + } + + public static func &<<=(left: inout BigInt, right: BigInt) { + left.shiftLeft(by: right.words[0]) + } + + public static func &>>(left: BigInt, right: BigInt) -> BigInt { + return left.shiftedRight(by: right.words[0]) + } + + public static func &>>=(left: inout BigInt, right: BigInt) { + left.shiftRight(by: right.words[0]) + } + + public static func <<(lhs: BigInt, rhs: Other) -> BigInt { + guard rhs >= (0 as Other) else { return lhs >> (0 - rhs) } + return lhs.shiftedLeft(by: Word(rhs)) + } + + public static func <<=(lhs: inout BigInt, rhs: Other) { + if rhs < (0 as Other) { + lhs >>= (0 - rhs) + } + else { + lhs.shiftLeft(by: Word(rhs)) + } + } + + public static func >>(lhs: BigInt, rhs: Other) -> BigInt { + guard rhs >= (0 as Other) else { return lhs << (0 - rhs) } + return lhs.shiftedRight(by: Word(rhs)) + } + + public static func >>=(lhs: inout BigInt, rhs: Other) { + if rhs < (0 as Other) { + lhs <<= (0 - rhs) + } + else { + lhs.shiftRight(by: Word(rhs)) + } + } +} diff --git a/dydx/Pods/BigInt/Sources/Square Root.swift b/dydx/Pods/BigInt/Sources/Square Root.swift new file mode 100644 index 000000000..68db0691c --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Square Root.swift @@ -0,0 +1,41 @@ +// +// Square Root.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-03. +// Copyright © 2016-2017 Károly Lőrentey. +// + +//MARK: Square Root + +extension BigUInt { + /// Returns the integer square root of a big integer; i.e., the largest integer whose square isn't greater than `value`. + /// + /// - Returns: floor(sqrt(self)) + public func squareRoot() -> BigUInt { + // This implementation uses Newton's method. + guard !self.isZero else { return BigUInt() } + var x = BigUInt(1) << ((self.bitWidth + 1) / 2) + var y: BigUInt = 0 + while true { + y.load(self) + y /= x + y += x + y >>= 1 + if x == y || x == y - 1 { break } + x = y + } + return x + } +} + +extension BigInt { + /// Returns the integer square root of a big integer; i.e., the largest integer whose square isn't greater than `value`. + /// + /// - Requires: self >= 0 + /// - Returns: floor(sqrt(self)) + public func squareRoot() -> BigInt { + precondition(self.sign == .plus) + return BigInt(sign: .plus, magnitude: self.magnitude.squareRoot()) + } +} diff --git a/dydx/Pods/BigInt/Sources/Strideable.swift b/dydx/Pods/BigInt/Sources/Strideable.swift new file mode 100644 index 000000000..2b79babd1 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Strideable.swift @@ -0,0 +1,38 @@ +// +// Strideable.swift +// BigInt +// +// Created by Károly Lőrentey on 2017-08-11. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension BigUInt: Strideable { + /// A type that can represent the distance between two values ofa `BigUInt`. + public typealias Stride = BigInt + + /// Adds `n` to `self` and returns the result. Traps if the result would be less than zero. + public func advanced(by n: BigInt) -> BigUInt { + return n.sign == .minus ? self - n.magnitude : self + n.magnitude + } + + /// Returns the (potentially negative) difference between `self` and `other` as a `BigInt`. Never traps. + public func distance(to other: BigUInt) -> BigInt { + return BigInt(other) - BigInt(self) + } +} + +extension BigInt: Strideable { + public typealias Stride = BigInt + + /// Returns `self + n`. + public func advanced(by n: Stride) -> BigInt { + return self + n + } + + /// Returns `other - self`. + public func distance(to other: BigInt) -> Stride { + return other - self + } +} + + diff --git a/dydx/Pods/BigInt/Sources/String Conversion.swift b/dydx/Pods/BigInt/Sources/String Conversion.swift new file mode 100644 index 000000000..d6f340c93 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/String Conversion.swift @@ -0,0 +1,236 @@ +// +// String Conversion.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-03. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension BigUInt { + + //MARK: String Conversion + + /// Calculates the number of numerals in a given radix that fit inside a single `Word`. + /// + /// - Returns: (chars, power) where `chars` is highest that satisfy `radix^chars <= 2^Word.bitWidth`. `power` is zero + /// if radix is a power of two; otherwise `power == radix^chars`. + fileprivate static func charsPerWord(forRadix radix: Int) -> (chars: Int, power: Word) { + var power: Word = 1 + var overflow = false + var count = 0 + while !overflow { + let (p, o) = power.multipliedReportingOverflow(by: Word(radix)) + overflow = o + if !o || p == 0 { + count += 1 + power = p + } + } + return (count, power) + } + + /// Initialize a big integer from an ASCII representation in a given radix. Numerals above `9` are represented by + /// letters from the English alphabet. + /// + /// - Requires: `radix > 1 && radix < 36` + /// - Parameter `text`: A string consisting of characters corresponding to numerals in the given radix. (0-9, a-z, A-Z) + /// - Parameter `radix`: The base of the number system to use, or 10 if unspecified. + /// - Returns: The integer represented by `text`, or nil if `text` contains a character that does not represent a numeral in `radix`. + public init?(_ text: S, radix: Int = 10) { + precondition(radix > 1) + let (charsPerWord, power) = BigUInt.charsPerWord(forRadix: radix) + + var words: [Word] = [] + var end = text.endIndex + var start = end + var count = 0 + while start != text.startIndex { + start = text.index(before: start) + count += 1 + if count == charsPerWord { + guard let d = Word.init(text[start ..< end], radix: radix) else { return nil } + words.append(d) + end = start + count = 0 + } + } + if start != end { + guard let d = Word.init(text[start ..< end], radix: radix) else { return nil } + words.append(d) + } + + if power == 0 { + self.init(words: words) + } + else { + self.init() + for d in words.reversed() { + self.multiply(byWord: power) + self.addWord(d) + } + } + } +} + +extension BigInt { + /// Initialize a big integer from an ASCII representation in a given radix. Numerals above `9` are represented by + /// letters from the English alphabet. + /// + /// - Requires: `radix > 1 && radix < 36` + /// - Parameter `text`: A string optionally starting with "-" or "+" followed by characters corresponding to numerals in the given radix. (0-9, a-z, A-Z) + /// - Parameter `radix`: The base of the number system to use, or 10 if unspecified. + /// - Returns: The integer represented by `text`, or nil if `text` contains a character that does not represent a numeral in `radix`. + public init?(_ text: S, radix: Int = 10) { + var magnitude: BigUInt? + var sign: Sign = .plus + if text.first == "-" { + sign = .minus + let text = text.dropFirst() + magnitude = BigUInt(text, radix: radix) + } + else if text.first == "+" { + let text = text.dropFirst() + magnitude = BigUInt(text, radix: radix) + } + else { + magnitude = BigUInt(text, radix: radix) + } + guard let m = magnitude else { return nil } + self.magnitude = m + self.sign = sign + } +} + +extension String { + /// Initialize a new string with the base-10 representation of an unsigned big integer. + /// + /// - Complexity: O(v.count^2) + public init(_ v: BigUInt) { self.init(v, radix: 10, uppercase: false) } + + /// Initialize a new string representing an unsigned big integer in the given radix (base). + /// + /// Numerals greater than 9 are represented as letters from the English alphabet, + /// starting with `a` if `uppercase` is false or `A` otherwise. + /// + /// - Requires: radix > 1 && radix <= 36 + /// - Complexity: O(count) when radix is a power of two; otherwise O(count^2). + public init(_ v: BigUInt, radix: Int, uppercase: Bool = false) { + precondition(radix > 1) + let (charsPerWord, power) = BigUInt.charsPerWord(forRadix: radix) + + guard !v.isZero else { self = "0"; return } + + var parts: [String] + if power == 0 { + parts = v.words.map { String($0, radix: radix, uppercase: uppercase) } + } + else { + parts = [] + var rest = v + while !rest.isZero { + let mod = rest.divide(byWord: power) + parts.append(String(mod, radix: radix, uppercase: uppercase)) + } + } + assert(!parts.isEmpty) + + self = "" + var first = true + for part in parts.reversed() { + let zeroes = charsPerWord - part.count + assert(zeroes >= 0) + if !first && zeroes > 0 { + // Insert leading zeroes for mid-Words + self += String(repeating: "0", count: zeroes) + } + first = false + self += part + } + } + + /// Initialize a new string representing a signed big integer in the given radix (base). + /// + /// Numerals greater than 9 are represented as letters from the English alphabet, + /// starting with `a` if `uppercase` is false or `A` otherwise. + /// + /// - Requires: radix > 1 && radix <= 36 + /// - Complexity: O(count) when radix is a power of two; otherwise O(count^2). + public init(_ value: BigInt, radix: Int = 10, uppercase: Bool = false) { + self = String(value.magnitude, radix: radix, uppercase: uppercase) + if value.sign == .minus { + self = "-" + self + } + } +} + +extension BigUInt: ExpressibleByStringLiteral { + /// Initialize a new big integer from a Unicode scalar. + /// The scalar must represent a decimal digit. + public init(unicodeScalarLiteral value: UnicodeScalar) { + self = BigUInt(String(value), radix: 10)! + } + + /// Initialize a new big integer from an extended grapheme cluster. + /// The cluster must consist of a decimal digit. + public init(extendedGraphemeClusterLiteral value: String) { + self = BigUInt(value, radix: 10)! + } + + /// Initialize a new big integer from a decimal number represented by a string literal of arbitrary length. + /// The string must contain only decimal digits. + public init(stringLiteral value: StringLiteralType) { + self = BigUInt(value, radix: 10)! + } +} + +extension BigInt: ExpressibleByStringLiteral { + /// Initialize a new big integer from a Unicode scalar. + /// The scalar must represent a decimal digit. + public init(unicodeScalarLiteral value: UnicodeScalar) { + self = BigInt(String(value), radix: 10)! + } + + /// Initialize a new big integer from an extended grapheme cluster. + /// The cluster must consist of a decimal digit. + public init(extendedGraphemeClusterLiteral value: String) { + self = BigInt(value, radix: 10)! + } + + /// Initialize a new big integer from a decimal number represented by a string literal of arbitrary length. + /// The string must contain only decimal digits. + public init(stringLiteral value: StringLiteralType) { + self = BigInt(value, radix: 10)! + } +} + +extension BigUInt: CustomStringConvertible { + /// Return the decimal representation of this integer. + public var description: String { + return String(self, radix: 10) + } +} + +extension BigInt: CustomStringConvertible { + /// Return the decimal representation of this integer. + public var description: String { + return String(self, radix: 10) + } +} + +extension BigUInt: CustomPlaygroundDisplayConvertible { + + /// Return the playground quick look representation of this integer. + public var playgroundDescription: Any { + let text = String(self) + return text + " (\(self.bitWidth) bits)" + } +} + +extension BigInt: CustomPlaygroundDisplayConvertible { + + /// Return the playground quick look representation of this integer. + public var playgroundDescription: Any { + let text = String(self) + return text + " (\(self.magnitude.bitWidth) bits)" + } +} diff --git a/dydx/Pods/BigInt/Sources/Subtraction.swift b/dydx/Pods/BigInt/Sources/Subtraction.swift new file mode 100644 index 000000000..5ac872e65 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Subtraction.swift @@ -0,0 +1,169 @@ +// +// Subtraction.swift +// BigInt +// +// Created by Károly Lőrentey on 2016-01-03. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension BigUInt { + //MARK: Subtraction + + /// Subtract `word` from this integer in place, returning a flag indicating if the operation + /// caused an arithmetic overflow. `word` is shifted `shift` words to the left before being subtracted. + /// + /// - Note: If the result indicates an overflow, then `self` becomes the two's complement of the absolute difference. + /// - Complexity: O(count) + internal mutating func subtractWordReportingOverflow(_ word: Word, shiftedBy shift: Int = 0) -> Bool { + precondition(shift >= 0) + var carry: Word = word + var i = shift + let count = self.count + while carry > 0 && i < count { + let (d, c) = self[i].subtractingReportingOverflow(carry) + self[i] = d + carry = (c ? 1 : 0) + i += 1 + } + return carry > 0 + } + + /// Subtract `word` from this integer, returning the difference and a flag that is true if the operation + /// caused an arithmetic overflow. `word` is shifted `shift` words to the left before being subtracted. + /// + /// - Note: If `overflow` is true, then the returned value is the two's complement of the absolute difference. + /// - Complexity: O(count) + internal func subtractingWordReportingOverflow(_ word: Word, shiftedBy shift: Int = 0) -> (partialValue: BigUInt, overflow: Bool) { + var result = self + let overflow = result.subtractWordReportingOverflow(word, shiftedBy: shift) + return (result, overflow) + } + + /// Subtract a digit `d` from this integer in place. + /// `d` is shifted `shift` digits to the left before being subtracted. + /// + /// - Requires: self >= d * 2^shift + /// - Complexity: O(count) + internal mutating func subtractWord(_ word: Word, shiftedBy shift: Int = 0) { + let overflow = subtractWordReportingOverflow(word, shiftedBy: shift) + precondition(!overflow) + } + + /// Subtract a digit `d` from this integer and return the result. + /// `d` is shifted `shift` digits to the left before being subtracted. + /// + /// - Requires: self >= d * 2^shift + /// - Complexity: O(count) + internal func subtractingWord(_ word: Word, shiftedBy shift: Int = 0) -> BigUInt { + var result = self + result.subtractWord(word, shiftedBy: shift) + return result + } + + /// Subtract `other` from this integer in place, and return a flag indicating if the operation caused an + /// arithmetic overflow. `other` is shifted `shift` digits to the left before being subtracted. + /// + /// - Note: If the result indicates an overflow, then `self` becomes the twos' complement of the absolute difference. + /// - Complexity: O(count) + public mutating func subtractReportingOverflow(_ b: BigUInt, shiftedBy shift: Int = 0) -> Bool { + precondition(shift >= 0) + var carry = false + var bi = 0 + let bc = b.count + let count = self.count + while bi < bc || (shift + bi < count && carry) { + let ai = shift + bi + let (d, c) = self[ai].subtractingReportingOverflow(b[bi]) + if carry { + let (d2, c2) = d.subtractingReportingOverflow(1) + self[ai] = d2 + carry = c || c2 + } + else { + self[ai] = d + carry = c + } + bi += 1 + } + return carry + } + + /// Subtract `other` from this integer, returning the difference and a flag indicating arithmetic overflow. + /// `other` is shifted `shift` digits to the left before being subtracted. + /// + /// - Note: If `overflow` is true, then the result value is the twos' complement of the absolute value of the difference. + /// - Complexity: O(count) + public func subtractingReportingOverflow(_ other: BigUInt, shiftedBy shift: Int) -> (partialValue: BigUInt, overflow: Bool) { + var result = self + let overflow = result.subtractReportingOverflow(other, shiftedBy: shift) + return (result, overflow) + } + + /// Subtracts `other` from `self`, returning the result and a flag indicating arithmetic overflow. + /// + /// - Note: When the operation overflows, then `partialValue` is the twos' complement of the absolute value of the difference. + /// - Complexity: O(count) + public func subtractingReportingOverflow(_ other: BigUInt) -> (partialValue: BigUInt, overflow: Bool) { + return self.subtractingReportingOverflow(other, shiftedBy: 0) + } + + /// Subtract `other` from this integer in place. + /// `other` is shifted `shift` digits to the left before being subtracted. + /// + /// - Requires: self >= other * 2^shift + /// - Complexity: O(count) + public mutating func subtract(_ other: BigUInt, shiftedBy shift: Int = 0) { + let overflow = subtractReportingOverflow(other, shiftedBy: shift) + precondition(!overflow) + } + + /// Subtract `b` from this integer, and return the difference. + /// `b` is shifted `shift` digits to the left before being subtracted. + /// + /// - Requires: self >= b * 2^shift + /// - Complexity: O(count) + public func subtracting(_ other: BigUInt, shiftedBy shift: Int = 0) -> BigUInt { + var result = self + result.subtract(other, shiftedBy: shift) + return result + } + + /// Decrement this integer by one. + /// + /// - Requires: !isZero + /// - Complexity: O(count) + public mutating func decrement(shiftedBy shift: Int = 0) { + self.subtract(1, shiftedBy: shift) + } + + /// Subtract `b` from `a` and return the result. + /// + /// - Requires: a >= b + /// - Complexity: O(a.count) + public static func -(a: BigUInt, b: BigUInt) -> BigUInt { + return a.subtracting(b) + } + + /// Subtract `b` from `a` and store the result in `a`. + /// + /// - Requires: a >= b + /// - Complexity: O(a.count) + public static func -=(a: inout BigUInt, b: BigUInt) { + a.subtract(b) + } +} + +extension BigInt { + public mutating func negate() { + guard !magnitude.isZero else { return } + self.sign = self.sign == .plus ? .minus : .plus + } + + /// Subtract `b` from `a` and return the result. + public static func -(a: BigInt, b: BigInt) -> BigInt { + return a + -b + } + + /// Subtract `b` from `a` in place. + public static func -=(a: inout BigInt, b: BigInt) { a = a - b } +} diff --git a/dydx/Pods/BigInt/Sources/Words and Bits.swift b/dydx/Pods/BigInt/Sources/Words and Bits.swift new file mode 100644 index 000000000..4543c1bc8 --- /dev/null +++ b/dydx/Pods/BigInt/Sources/Words and Bits.swift @@ -0,0 +1,202 @@ +// +// Words and Bits.swift +// BigInt +// +// Created by Károly Lőrentey on 2017-08-11. +// Copyright © 2016-2017 Károly Lőrentey. +// + +extension Array where Element == UInt { + mutating func twosComplement() { + var increment = true + for i in 0 ..< self.count { + if increment { + (self[i], increment) = (~self[i]).addingReportingOverflow(1) + } + else { + self[i] = ~self[i] + } + } + } +} + +extension BigUInt { + public subscript(bitAt index: Int) -> Bool { + get { + precondition(index >= 0) + let (i, j) = index.quotientAndRemainder(dividingBy: Word.bitWidth) + return self[i] & (1 << j) != 0 + } + set { + precondition(index >= 0) + let (i, j) = index.quotientAndRemainder(dividingBy: Word.bitWidth) + if newValue { + self[i] |= 1 << j + } + else { + self[i] &= ~(1 << j) + } + } + } +} + +extension BigUInt { + /// The minimum number of bits required to represent this integer in binary. + /// + /// - Returns: floor(log2(2 * self + 1)) + /// - Complexity: O(1) + public var bitWidth: Int { + guard count > 0 else { return 0 } + return count * Word.bitWidth - self[count - 1].leadingZeroBitCount + } + + /// The number of leading zero bits in the binary representation of this integer in base `2^(Word.bitWidth)`. + /// This is useful when you need to normalize a `BigUInt` such that the top bit of its most significant word is 1. + /// + /// - Note: 0 is considered to have zero leading zero bits. + /// - Returns: A value in `0...(Word.bitWidth - 1)`. + /// - SeeAlso: width + /// - Complexity: O(1) + public var leadingZeroBitCount: Int { + guard count > 0 else { return 0 } + return self[count - 1].leadingZeroBitCount + } + + /// The number of trailing zero bits in the binary representation of this integer. + /// + /// - Note: 0 is considered to have zero trailing zero bits. + /// - Returns: A value in `0...width`. + /// - Complexity: O(count) + public var trailingZeroBitCount: Int { + guard count > 0 else { return 0 } + let i = self.words.firstIndex { $0 != 0 }! + return i * Word.bitWidth + self[i].trailingZeroBitCount + } +} + +extension BigInt { + public var bitWidth: Int { + guard !magnitude.isZero else { return 0 } + return magnitude.bitWidth + 1 + } + + public var trailingZeroBitCount: Int { + // Amazingly, this works fine for negative numbers + return magnitude.trailingZeroBitCount + } +} + +extension BigUInt { + public struct Words: RandomAccessCollection { + private let value: BigUInt + + fileprivate init(_ value: BigUInt) { self.value = value } + + public var startIndex: Int { return 0 } + public var endIndex: Int { return value.count } + + public subscript(_ index: Int) -> Word { + return value[index] + } + } + + public var words: Words { return Words(self) } + + public init(words: Words) where Words.Element == Word { + let uc = words.underestimatedCount + if uc > 2 { + self.init(words: Array(words)) + } + else { + var it = words.makeIterator() + guard let w0 = it.next() else { + self.init() + return + } + guard let w1 = it.next() else { + self.init(word: w0) + return + } + if let w2 = it.next() { + var words: [UInt] = [] + words.reserveCapacity(Swift.max(3, uc)) + words.append(w0) + words.append(w1) + words.append(w2) + while let word = it.next() { + words.append(word) + } + self.init(words: words) + } + else { + self.init(low: w0, high: w1) + } + } + } +} + +extension BigInt { + public struct Words: RandomAccessCollection { + public typealias Indices = CountableRange + + private let value: BigInt + private let decrementLimit: Int + + fileprivate init(_ value: BigInt) { + self.value = value + switch value.sign { + case .plus: + self.decrementLimit = 0 + case .minus: + assert(!value.magnitude.isZero) + self.decrementLimit = value.magnitude.words.firstIndex(where: { $0 != 0 })! + } + } + + public var count: Int { + switch value.sign { + case .plus: + if let high = value.magnitude.words.last, high >> (Word.bitWidth - 1) != 0 { + return value.magnitude.count + 1 + } + return value.magnitude.count + case .minus: + let high = value.magnitude.words.last! + if high >> (Word.bitWidth - 1) != 0 { + return value.magnitude.count + 1 + } + return value.magnitude.count + } + } + + public var indices: Indices { return 0 ..< count } + public var startIndex: Int { return 0 } + public var endIndex: Int { return count } + + public subscript(_ index: Int) -> UInt { + // Note that indices above `endIndex` are accepted. + if value.sign == .plus { + return value.magnitude[index] + } + if index <= decrementLimit { + return ~(value.magnitude[index] &- 1) + } + return ~value.magnitude[index] + } + } + + public var words: Words { + return Words(self) + } + + public init(words: S) where S.Element == Word { + var words = Array(words) + if (words.last ?? 0) >> (Word.bitWidth - 1) == 0 { + self.init(sign: .plus, magnitude: BigUInt(words: words)) + } + else { + words.twosComplement() + self.init(sign: .minus, magnitude: BigUInt(words: words)) + } + } +} diff --git a/dydx/Pods/COSTouchVisualizer/Classes/COSTouchVisualizerWindow.h b/dydx/Pods/COSTouchVisualizer/Classes/COSTouchVisualizerWindow.h new file mode 100755 index 000000000..a216590f4 --- /dev/null +++ b/dydx/Pods/COSTouchVisualizer/Classes/COSTouchVisualizerWindow.h @@ -0,0 +1,42 @@ +// +// COSTouchVisualizerWindow.h +// TouchVisualizer +// +// Created by Joe Blau on 3/22/14. +// Copyright (c) 2014 conopsys. All rights reserved. +// +#include + +@protocol COSTouchVisualizerWindowDelegate; + +@interface COSTouchVisualizerWindow : UIWindow + +@property (nonatomic, readonly, getter=isActive) BOOL active; +@property (nonatomic, weak) id touchVisualizerWindowDelegate; + +// Touch effects +@property (nonatomic) UIImage *touchImage; +@property (nonatomic) CGFloat touchAlpha; +@property (nonatomic) NSTimeInterval fadeDuration; +@property (nonatomic) UIColor *strokeColor; +@property (nonatomic) UIColor *fillColor; + +// Ripple Effects +@property (nonatomic) UIImage *rippleImage; +@property (nonatomic) CGFloat rippleAlpha; +@property (nonatomic) NSTimeInterval rippleFadeDuration; +@property (nonatomic) UIColor *rippleStrokeColor; +@property (nonatomic) UIColor *rippleFillColor; + +@property (nonatomic) BOOL stationaryMorphEnabled; // default: YES + +@end + +@protocol COSTouchVisualizerWindowDelegate + +@optional + +- (BOOL)touchVisualizerWindowShouldShowFingertip:(COSTouchVisualizerWindow *)window; +- (BOOL)touchVisualizerWindowShouldAlwaysShowFingertip:(COSTouchVisualizerWindow *)window; + +@end diff --git a/dydx/Pods/COSTouchVisualizer/Classes/COSTouchVisualizerWindow.m b/dydx/Pods/COSTouchVisualizer/Classes/COSTouchVisualizerWindow.m new file mode 100755 index 000000000..966471652 --- /dev/null +++ b/dydx/Pods/COSTouchVisualizer/Classes/COSTouchVisualizerWindow.m @@ -0,0 +1,414 @@ +// +// COSTouchVisualizerWindow.m +// TouchVisualizer +// +// Created by Joe Blau on 3/22/14. +// Copyright (c) 2014 conopsys. All rights reserved. +// + +#import "COSTouchVisualizerWindow.h" + +#pragma mark - Touch Visualizer Window + +@interface TouchVisualizerWindow : UIWindow +@end + +@implementation TouchVisualizerWindow + +// UIKit tries to get the rootViewController from the overlay window. +// Instead, try to find the rootViewController on some other +// application window. +// Fixes problems with status bar hiding, because it considers the +// overlay window a candidate for controlling the status bar. +- (UIViewController *)rootViewController { + for (UIWindow *window in [[UIApplication sharedApplication] windows]) { + if (self == window) + continue; + UIViewController *realRootViewController = window.rootViewController; + if (realRootViewController != nil) + return realRootViewController; + } + return [super rootViewController]; +} + +@end + +#pragma mark - Conopsys Touch Spot View + +@interface COSTouchSpotView : UIImageView + +@property (nonatomic) NSTimeInterval timestamp; +@property (nonatomic) BOOL shouldAutomaticallyRemoveAfterTimeout; +@property (nonatomic, getter=isFadingOut) BOOL fadingOut; + +@end + +@implementation COSTouchSpotView +@end + +#pragma mark - Conopsys Touch Visualizer Window + +@interface COSTouchVisualizerWindow () + +@property (nonatomic) UIWindow *overlayWindow; +@property (nonatomic) UIViewController *overlayWindowViewController; +@property (nonatomic) BOOL fingerTipRemovalScheduled; +@property (nonatomic) NSTimer *timer; +@property (nonatomic) NSSet *allTouches; + +- (void)COSTouchVisualizerWindow_commonInit; +- (void)scheduleFingerTipRemoval; +- (void)cancelScheduledFingerTipRemoval; +- (void)removeInactiveFingerTips; +- (void)removeFingerTipWithHash:(NSUInteger)hash animated:(BOOL)animated; +- (BOOL)shouldAutomaticallyRemoveFingerTipForTouch:(UITouch *)touch; + +@end + +@implementation COSTouchVisualizerWindow + +- (id)initWithCoder:(NSCoder *)decoder { + // This covers NIB-loaded windows. + if (self = [super initWithCoder:decoder]) + [self COSTouchVisualizerWindow_commonInit]; + return self; +} + +- (id)initWithFrame:(CGRect)rect { + // This covers programmatically-created windows. + if (self = [super initWithFrame:rect]) + [self COSTouchVisualizerWindow_commonInit]; + return self; +} + +- (void)COSTouchVisualizerWindow_commonInit { + self.strokeColor = [UIColor blackColor]; + self.fillColor = [UIColor whiteColor]; + self.rippleStrokeColor = [UIColor whiteColor]; + self.rippleFillColor = [UIColor blueColor]; + self.touchAlpha = 0.5; + self.fadeDuration = 0.3; + self.rippleAlpha = 0.2; + self.rippleFadeDuration = 0.2; + self.stationaryMorphEnabled = YES; +} + +#pragma mark - Touch / Ripple and Images + +- (UIImage *)touchImage { + if (!_touchImage) { + UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 50.0, 50.0)]; + + UIGraphicsBeginImageContextWithOptions(clipPath.bounds.size, NO, 0); + + UIBezierPath *drawPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(25.0, 25.0) + radius:22.0 + startAngle:0 + endAngle:2 * M_PI + clockwise:YES]; + + drawPath.lineWidth = 2.0; + + [self.strokeColor setStroke]; + [self.fillColor setFill]; + + [drawPath stroke]; + [drawPath fill]; + + [clipPath addClip]; + + _touchImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + return _touchImage; +} + +- (UIImage *)rippleImage { + if (!_rippleImage) { + UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 50.0, 50.0)]; + + UIGraphicsBeginImageContextWithOptions(clipPath.bounds.size, NO, 0); + + UIBezierPath *drawPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(25.0, 25.0) + radius:22.0 + startAngle:0 + endAngle:2 * M_PI + clockwise:YES]; + + drawPath.lineWidth = 2.0; + + [self.rippleStrokeColor setStroke]; + [self.rippleFillColor setFill]; + + [drawPath stroke]; + [drawPath fill]; + + [clipPath addClip]; + + _rippleImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + return _rippleImage; +} + +#pragma mark - Active + +- (BOOL)anyScreenIsMirrored { + if (![UIScreen instancesRespondToSelector:@selector(mirroredScreen)]) + return NO; + + for (UIScreen *screen in [UIScreen screens]) { + if ([screen mirroredScreen] != nil) { + return YES; + } + } + return NO; +} + +- (BOOL)isActive { + // should show fingertip or not + if (![self.touchVisualizerWindowDelegate respondsToSelector:@selector(touchVisualizerWindowShouldShowFingertip:)] || + [self.touchVisualizerWindowDelegate touchVisualizerWindowShouldShowFingertip:self]) { + // should always show or only when any screen is mirrored. + return (([self.touchVisualizerWindowDelegate respondsToSelector:@selector(touchVisualizerWindowShouldAlwaysShowFingertip:)] && + [self.touchVisualizerWindowDelegate touchVisualizerWindowShouldAlwaysShowFingertip:self]) || + [self anyScreenIsMirrored]); + } else { + return NO; + } +} + +#pragma mark - UIWindow overrides + +- (void)sendEvent:(UIEvent *)event { + if (self.active) { + self.allTouches = [event allTouches]; + for (UITouch *touch in [self.allTouches allObjects]) { + switch (touch.phase) { + case UITouchPhaseBegan: + case UITouchPhaseMoved: { + // Generate ripples + COSTouchSpotView *rippleView = [[COSTouchSpotView alloc] initWithImage:self.rippleImage]; + [self.overlayWindow addSubview:rippleView]; + + rippleView.alpha = self.rippleAlpha; + rippleView.center = [touch locationInView:self.overlayWindow]; + + [UIView animateWithDuration:self.rippleFadeDuration + delay:0.0 + options:UIViewAnimationOptionCurveEaseIn // See other + // options + animations:^{ + [rippleView setAlpha:0.0]; + [rippleView setFrame:CGRectInset(rippleView.frame, 25, 25)]; + } completion:^(BOOL finished) { + [rippleView removeFromSuperview]; + }]; + } + case UITouchPhaseStationary: { + COSTouchSpotView *touchView = (COSTouchSpotView *)[self.overlayWindow viewWithTag:touch.hash]; + + if (touch.phase != UITouchPhaseStationary && touchView != nil && [touchView isFadingOut]) { + [self.timer invalidate]; + [touchView removeFromSuperview]; + touchView = nil; + } + + if (touchView == nil && touch.phase != UITouchPhaseStationary) { + touchView = [[COSTouchSpotView alloc] initWithImage:self.touchImage]; + [self.overlayWindow addSubview:touchView]; + + if (self.stationaryMorphEnabled) { + self.timer = [NSTimer scheduledTimerWithTimeInterval:0.6 + target:self + selector:@selector(performMorph:) + userInfo:touchView + repeats:YES]; + } + } + if (![touchView isFadingOut]) { + touchView.alpha = self.touchAlpha; + touchView.center = [touch locationInView:self.overlayWindow]; + touchView.tag = touch.hash; + touchView.timestamp = touch.timestamp; + touchView.shouldAutomaticallyRemoveAfterTimeout = [self shouldAutomaticallyRemoveFingerTipForTouch:touch]; + } + break; + } + case UITouchPhaseEnded: + case UITouchPhaseCancelled: { + [self removeFingerTipWithHash:touch.hash animated:YES]; + break; + } + } + } + } + + [super sendEvent:event]; + [self scheduleFingerTipRemoval]; // We may not see all UITouchPhaseEnded/UITouchPhaseCancelled events. +} + +#pragma mark - Private + +- (UIWindow *)overlayWindow { + if (!_overlayWindow) { + _overlayWindow = [[TouchVisualizerWindow alloc] initWithFrame:self.frame]; + _overlayWindow.userInteractionEnabled = NO; + _overlayWindow.windowLevel = UIWindowLevelStatusBar; + _overlayWindow.backgroundColor = [UIColor clearColor]; + _overlayWindow.hidden = NO; + } + return _overlayWindow; +} + +- (void)scheduleFingerTipRemoval { + + if (self.fingerTipRemovalScheduled) + return; + self.fingerTipRemovalScheduled = YES; + [self performSelector:@selector(removeInactiveFingerTips) + withObject:nil + afterDelay:0.1]; +} + +- (void)cancelScheduledFingerTipRemoval { + self.fingerTipRemovalScheduled = YES; + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(removeInactiveFingerTips) + object:nil]; +} + +- (void)removeInactiveFingerTips { + self.fingerTipRemovalScheduled = NO; + + NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime]; + const CGFloat REMOVAL_DELAY = 0.2; + for (COSTouchSpotView *touchView in [self.overlayWindow subviews]) { + if (![touchView isKindOfClass:[COSTouchSpotView class]]) + continue; + + if (touchView.shouldAutomaticallyRemoveAfterTimeout && now > touchView.timestamp + REMOVAL_DELAY) + [self removeFingerTipWithHash:touchView.tag animated:YES]; + } + + if ([[self.overlayWindow subviews] count]) + [self scheduleFingerTipRemoval]; +} + +- (void)removeFingerTipWithHash:(NSUInteger)hash animated:(BOOL)animated { + COSTouchSpotView *touchView = (COSTouchSpotView *)[self.overlayWindow viewWithTag:hash]; + if (touchView == nil) + return; + + if ([touchView isFadingOut]) + return; + + BOOL animationsWereEnabled = [UIView areAnimationsEnabled]; + + if (animated) { + [UIView setAnimationsEnabled:YES]; + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:self.fadeDuration]; + } + + touchView.frame = CGRectMake(touchView.center.x - touchView.frame.size.width, + touchView.center.y - touchView.frame.size.height, + touchView.frame.size.width * 2, touchView.frame.size.height * 2); + + touchView.alpha = 0.0; + + if (animated) { + [UIView commitAnimations]; + [UIView setAnimationsEnabled:animationsWereEnabled]; + } + + touchView.fadingOut = YES; + [touchView performSelector:@selector(removeFromSuperview) + withObject:nil + afterDelay:self.fadeDuration]; +} + +- (BOOL)shouldAutomaticallyRemoveFingerTipForTouch:(UITouch *)touch; +{ + // We don't reliably get UITouchPhaseEnded or UITouchPhaseCancelled + // events via -sendEvent: for certain touch events. Known cases + // include swipe-to-delete on a table view row, and tap-to-cancel + // swipe to delete. We automatically remove their associated + // fingertips after a suitable timeout. + // + // It would be much nicer if we could remove all touch events after + // a suitable time out, but then we'll prematurely remove touch and + // hold events that are picked up by gesture recognizers (since we + // don't use UITouchPhaseStationary touches for those. *sigh*). So we + // end up with this more complicated setup. + + UIView *view = [touch view]; + view = [view hitTest:[touch locationInView:view] withEvent:nil]; + + while (view != nil) { + if ([view isKindOfClass:[UITableViewCell class]]) { + for (UIGestureRecognizer *recognizer in [touch gestureRecognizers]) { + if ([recognizer isKindOfClass:[UISwipeGestureRecognizer class]]) + return YES; + } + } + + if ([view isKindOfClass:[UITableView class]]) { + if ([[touch gestureRecognizers] count] == 0) + return YES; + } + view = view.superview; + } + + return NO; +} + +- (void)performMorph:(NSTimer *)theTimer { + UIView *view = (UIView *)[theTimer userInfo]; + NSTimeInterval duration = .4; + NSTimeInterval delay = 0; + // Start + view.alpha = self.touchAlpha; + view.transform = CGAffineTransformMakeScale(1, 1); + [UIView animateWithDuration:duration / 4 + delay:delay + options:0 + animations:^{ + // End + view.transform = CGAffineTransformMakeScale(1, 1.2); + } + completion:^(BOOL finished) { + [UIView animateWithDuration:duration / 4 + delay:0 + options:0 + animations:^{ + // End + view.transform = CGAffineTransformMakeScale(1.2, 0.9); + } + completion:^(BOOL finished) { + [UIView animateWithDuration:duration / 4 + delay:0 + options:0 + animations:^{ + // End + view.transform = CGAffineTransformMakeScale(0.9, 0.9); + } + completion:^(BOOL finished) { + [UIView animateWithDuration:duration / 4 + delay:0 + options:0 + animations:^{ + // End + view.transform = CGAffineTransformMakeScale(1, 1); + } + completion:^(BOOL finished){ + // If there are no touches, remove this morping touch + if (self.allTouches.count == 0) + [view removeFromSuperview]; + }]; + }]; + }]; + }]; +} + +@end \ No newline at end of file diff --git a/dydx/Pods/COSTouchVisualizer/LICENSE b/dydx/Pods/COSTouchVisualizer/LICENSE new file mode 100644 index 000000000..07ab71e7f --- /dev/null +++ b/dydx/Pods/COSTouchVisualizer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Joe Blau + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/dydx/Pods/COSTouchVisualizer/README.md b/dydx/Pods/COSTouchVisualizer/README.md new file mode 100644 index 000000000..28ce4787d --- /dev/null +++ b/dydx/Pods/COSTouchVisualizer/README.md @@ -0,0 +1,135 @@ +# COSTouchVisualizer + +![COSTouchVisualizer](https://raw.githubusercontent.com/conopsys/COSTouchVisualizer/master/touchvisdemo.gif "COSTouchVisualizer iOS") + +[![Version](http://cocoapod-badges.herokuapp.com/v/COSTouchVisualizer/badge.png)](http://cocoadocs.org/docsets/COSTouchVisualizer) +[![Platform](http://cocoapod-badges.herokuapp.com/p/COSTouchVisualizer/badge.png)](http://cocoadocs.org/docsets/COSTouchVisualizer) + +## Swift Usage + +Using COSTouchVisualizer is possible with Swift. Inside your AppDelegate, redefine your window and declare a visualizer window with storyboards. + +**With Storyboards** +```swift +class AppDelegate: UIResponder { + lazy var window: COSTouchVisualizerWindow? = { + COSTouchVisualizerWindow(frame: UIScreen.mainScreen().bounds) + }() +... +} +``` +**Without Storyboards** + +## Objective-C Usage + +To run the example project; clone the repo, and run `pod update` from the Example directory first. By default, this project has `Debug Mode` disabled. If you want to see the gestures while you're testing, follow the **Debugging Mode** instructions. + +**With Storyboards** + in your `AppDelegate` implementation simply add the following getter + +```objective-c +#import + +... + +// Add this method to your AppDelegate method +- (COSTouchVisualizerWindow *)window { + static COSTouchVisualizerWindow *visWindow = nil; + if (!visWindow) visWindow = [[COSTouchVisualizerWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + return visWindow; +} +``` + +**Without Storyboards** +```objective-c +#import + +... + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Setup window + self.window = [[COSTouchVisualizerWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + + ... + +} +``` + +**Delegate** + +To make the window change active status dynamically or to enable debugging mode, you could make an object +implements the ```COSTouchVisualizerWindowDelegate``` protocol. + +Here are 2 optional methods in this delegate protocol: +```objective-c +- (BOOL)touchVisualizerWindowShouldShowFingertip:(COSTouchVisualizerWindow *)window; +- (BOOL)touchVisualizerWindowShouldAlwaysShowFingertip:(COSTouchVisualizerWindow *)window; +``` + +By default, the window only shows fingertip when there is a mirrored window. + +The first delegate method (```-touchVisualizerWindowShouldShowFingertip:```) tells the window to enable +fingertip or not. You should return ```YES``` to enable the fingertip feature, or ```NO``` if you want to close this +feature. + +The second method (```-touchVisualizerWindowShouldAlwaysShowFingertip:```) tells the window to always show the +fingertip even if there's no any mirrored screens (when returning YES). If this method returns NO, the window +only show fingertip when connected to a mirrored screen. + +```objective-c +- (COSTouchVisualizerWindow *)window { + if (!_customWindow) { + _customWindow = [[COSTouchVisualizerWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + + // ... other setup code + + _customWindow.touchVisualizerWindowDelegate = self; + } + return _customWindow; +} + +- (BOOL)touchVisualizerWindowShouldAlwaysShowFingertip:(COSTouchVisualizerWindow *)window { + return YES; // Return YES to make the fingertip always display even if there's no any mirrored screen. + // Return NO or don't implement this method if you want to keep the fingertip display only when + // the device is connected to a mirrored screen. +} + +- (BOOL)touchVisualizerWindowShouldShowFingertip:(COSTouchVisualizerWindow *)window { + return YES; // Return YES or don't implement this method to make this window show fingertip when necessary. + // Return NO to make this window not to show fingertip. +} +``` + +**Customization** + +```objective-c +// Add these lines after the windows is initialized +// Touch Color +[visWindow setFillColor:[UIColor yellowColor]]; +[visWindow setStrokeColor:[UIColor purpleColor]]; +[visWindow setTouchAlpha:0.4]; +// Ripple Color +[visWindow setRippleFillColor:[UIColor yellowColor]]; +[visWindow setRippleStrokeColor:[UIColor purpleColor]]; +[visWindow setRippleAlpha:0.1]; +``` + +## Requirements + +This project requires ARC. + +## Installation + +COSTouchVisualizer is available through [CocoaPods](http://cocoapods.org), to install +it simply add the following line to your Podfile: + + pod "COSTouchVisualizer" + +## Author + +Joe Blau, josephblau@gmail.com + +## License + +COSTouchVisualizer is available under the MIT license. See the LICENSE file for more info. diff --git a/dydx/Pods/Charts/LICENSE b/dydx/Pods/Charts/LICENSE new file mode 100644 index 000000000..8198c7a0c --- /dev/null +++ b/dydx/Pods/Charts/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016-2019 Daniel Cohen Gindi & Philipp Jahoda + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/dydx/Pods/Charts/README.md b/dydx/Pods/Charts/README.md new file mode 100644 index 000000000..a469fbd5b --- /dev/null +++ b/dydx/Pods/Charts/README.md @@ -0,0 +1,227 @@ +**Version 3.5.0**, synced to [MPAndroidChart #f6a398b](https://github.com/PhilJay/MPAndroidChart/commit/f6a398b) + +![alt tag](https://raw.github.com/danielgindi/Charts/master/Assets/feature_graphic.png) + ![Supported Platforms](https://img.shields.io/cocoapods/p/Charts.svg) [![Releases](https://img.shields.io/github/release/danielgindi/Charts.svg)](https://github.com/danielgindi/Charts/releases) [![Latest pod release](https://img.shields.io/cocoapods/v/Charts.svg)](http://cocoapods.org/pods/charts) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Build Status](https://travis-ci.org/danielgindi/Charts.svg?branch=master)](https://travis-ci.org/danielgindi/Charts) [![codecov](https://codecov.io/gh/danielgindi/Charts/branch/master/graph/badge.svg)](https://codecov.io/gh/danielgindi/Charts) +[![Join the chat at https://gitter.im/danielgindi/Charts](https://badges.gitter.im/danielgindi/Charts.svg)](https://gitter.im/danielgindi/Charts?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +### Just a heads up: Charts 3.0 has some breaking changes. Please read [the release/migration notes](https://github.com/danielgindi/Charts/releases/tag/v3.0.0). +### Another heads up: ChartsRealm is now in a [separate repo](https://github.com/danielgindi/ChartsRealm). Pods is also now `Charts` and `ChartsRealm`, instead of ~`Charts/Core`~ and ~`Charts/Realm`~ +### One more heads up: As Swift evolves, if you are not using the latest Swift compiler, you shouldn't check out the master branch. Instead, you should go to the release page and pick up whatever suits you. + +* Xcode 11 / Swift 5 (master branch) +* iOS >= 8.0 (Use as an **Embedded** Framework) +* tvOS >= 9.0 +* macOS >= 10.11 + +Okay so there's this beautiful library called [MPAndroidChart](https://github.com/PhilJay/MPAndroidChart) by [Philipp Jahoda](https://www.linkedin.com/in/philippjahoda) which has become very popular amongst Android developers, but there was no decent solution to create charts for iOS. + +I've chosen to write it in `Swift` as it can be highly optimized by the compiler, and can be used in both `Swift` and `ObjC` project. The demo project is written in `ObjC` to demonstrate how it works. + +**An amazing feature** of this library now, for Android, iOS, tvOS and macOS, is the time it saves you when developing for both platforms, as the learning curve is singleton- it happens only once, and the code stays very similar so developers don't have to go around and re-invent the app to produce the same output with a different library. (And that's not even considering the fact that there's not really another good choice out there currently...) + +## Having trouble running the demo? + +* `ChartsDemo/ChartsDemo.xcodeproj` is the demo project for iOS/tvOS +* `ChartsDemo-OSX/ChartsDemo-OSX.xcodeproj` is the demo project for macOS +* Make sure you are running a supported version of Xcode. + * Usually it is specified here a few lines above. + * In most cases it will be the latest Xcode version. +* Make sure that your project supports Swift 5.0 +* Optional: Run `carthage checkout` in the project folder, to fetch dependencies (i.e testing dependencies). + * If you don't have Carthage - you can get it [here](https://github.com/Carthage/Carthage/releases). + + +## Usage + +In order to correctly compile: + +1. Drag the `Charts.xcodeproj` to your project +2. Go to your target's settings, hit the "+" under the "Embedded Binaries" section, and select the Charts.framework +3. `@import Charts` +4. When using Swift in an ObjC project: + - You need to import your Bridging Header. Usually it is "*YourProject-Swift.h*", so in ChartsDemo it's "*ChartsDemo-Swift.h*". Do not try to actually include "*ChartsDemo-Swift.h*" in your project :-) + - (Xcode 8.1 and earlier) Under "Build Options", mark "Embedded Content Contains Swift Code" + - (Xcode 8.2+) Under "Build Options", mark "Always Embed Swift Standard Libraries" +5. When using [Realm.io](https://realm.io/): + - Note that the Realm framework is not linked with Charts - it is only there for *optional* bindings. Which means that you need to have the framework in your project, and in a compatible version to whatever is compiled with Charts. We will do our best to always compile against the latest version. + - You'll need to add `ChartsRealm` as a dependency too. + +## 3rd party tutorials +#### Video tutorials + +* [Chart in Swift - Setting Up a Basic Line Chart Using iOS Charts(Alex Nagy)](https://www.youtube.com/watch?v=mWhwe_tLNE8&list=PL_csAAO9PQ8bjzg-wxEff1Fr0Y5W1hrum&index=5) + +#### Blog posts +* [Using Realm and Charts with Swift 3 in iOS 10 (Sami Korpela)](https://medium.com/@skoli/using-realm-and-charts-with-swift-3-in-ios-10-40c42e3838c0#.2gyymwfh8) +* [Creating a Line Chart in Swift 3 and iOS 10 (Osian Smith)](https://medium.com/@OsianSmith/creating-a-line-chart-in-swift-3-and-ios-10-2f647c95392e) +* [Beginning Set-up and Example Using Charts with Swift 3](https://github.com/annalizhaz/ChartsForSwiftBasic) +* [Creating a Radar Chart in Swift (David Piper)](https://medium.com/@HeyDaveTheDev/creating-a-radar-chart-in-swift-5791afcf92f0) +* [Plotting in IOS using Charts framework with SwiftUI (Evgeny Basisty)](https://medium.com/@zzzzbh/plotting-in-ios-using-charts-framework-with-swiftui-222034a2bea6) + +Want your tutorial to show here? Create a PR! + +## Troubleshooting + +#### Can't compile? + +* Please note the difference between installing a compiled framework from CocoaPods or Carthage, and copying the source code. +* Please read the **Usage** section again. +* Search in the issues +* Try to politely ask in the issues section + +#### Other problems / feature requests + +* Search in the issues +* Try to politely ask in the issues section + +## CocoaPods Install + +Add `pod 'Charts'` to your Podfile. "Charts" is the name of the library. +For [Realm](https://realm.io/) support, please add `pod 'ChartsRealm'` too. + +**Note:** ~~`pod 'ios-charts'`~~ is not the correct library, and refers to a different project by someone else. + +## Carthage Install + +Charts now include Carthage prebuilt binaries. + +```carthage +github "danielgindi/Charts" == 3.5.0 +github "danielgindi/Charts" ~> 3.5.0 +``` + +In order to build the binaries for a new release, use `carthage build --no-skip-current && carthage archive Charts`. + +## 3rd party bindings + +Xamarin (by @Flash3001): *iOS* - [GitHub](https://github.com/Flash3001/iOSCharts.Xamarin)/[NuGet](https://www.nuget.org/packages/iOSCharts/). *Android* - [GitHub](https://github.com/Flash3001/MPAndroidChart.Xamarin)/[NuGet](https://www.nuget.org/packages/MPAndroidChart/). + +## Help + +If you like what you see here, and want to support the work being done in this repository, you could: +* Contribute code, issues and pull requests +* Let people know this library exists (:fire: spread the word :fire:) +* [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=68UL6Y8KUPS96) (You can buy me a beer, or you can buy me dinner :-) + +**Note:** The author of [MPAndroidChart](https://github.com/PhilJay/MPAndroidChart) is the reason that this library exists, and is accepting [donations](https://github.com/PhilJay/MPAndroidChart#donations) on his page. He deserves them! + +Questions & Issues +----- + +If you are having questions or problems, you should: + + - Make sure you are using the latest version of the library. Check the [**release-section**](https://github.com/danielgindi/Charts/releases). + - Study the Android version's [**Documentation-Wiki**](https://github.com/PhilJay/MPAndroidChart/wiki) + - Study the (Still incomplete [![Doc-Percent](https://img.shields.io/cocoapods/metrics/doc-percent/Charts.svg)](http://cocoadocs.org/docsets/Charts/)) [**Pod-Documentation**](http://cocoadocs.org/docsets/Charts/) + - Search or open questions on [**stackoverflow**](http://stackoverflow.com/questions/tagged/ios-charts) with the `ios-charts` tag + - Search [**known issues**](https://github.com/danielgindi/Charts/issues) for your problem (open and closed) + - Create new issues (please :fire: **search known issues before** :fire:, do not create duplicate issues) + + +Features +======= + +**Core features:** + - 8 different chart types + - Scaling on both axes (with touch-gesture, axes separately or pinch-zoom) + - Dragging / Panning (with touch-gesture) + - Combined-Charts (line-, bar-, scatter-, candle-stick-, bubble-) + - Dual (separate) Axes + - Customizable Axes (both x- and y-axis) + - Highlighting values (with customizable popup-views) + - Save chart to camera-roll / export to PNG/JPEG + - Predefined color templates + - Legends (generated automatically, customizable) + - Animations (build up animations, on both x- and y-axis) + - Limit lines (providing additional information, maximums, ...) + - Fully customizable (paints, typefaces, legends, colors, background, gestures, dashed lines, ...) + - Plotting data directly from [**Realm.io**](https://realm.io) mobile database ([here](https://github.com/danielgindi/ChartsRealm)) + +**Chart types:** + +*Screenshots are currently taken from the original repository, as they render exactly the same :-)* + + + - **LineChart (with legend, simple design)** +![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/simpledesign_linechart4.png) + - **LineChart (with legend, simple design)** +![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/simpledesign_linechart3.png) + + - **LineChart (cubic lines)** +![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/cubiclinechart.png) + + - **LineChart (gradient fill)** +![alt tag](https://raw.github.com/PhilJay/MPAndroidChart/master/screenshots/line_chart_gradient.png) + + - **Combined-Chart (bar- and linechart in this case)** +![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/combined_chart.png) + + - **BarChart (with legend, simple design)** + +![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/simpledesign_barchart3.png) + + - **BarChart (grouped DataSets)** + +![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/groupedbarchart.png) + + - **Horizontal-BarChart** + +![alt tag](https://raw.github.com/PhilJay/MPChart/master/screenshots/horizontal_barchart.png) + + + - **PieChart (with selection, ...)** + +![alt tag](https://raw.github.com/PhilJay/MPAndroidChart/master/screenshots/simpledesign_piechart1.png) + + - **ScatterChart** (with squares, triangles, circles, ... and more) + +![alt tag](https://raw.github.com/PhilJay/MPAndroidChart/master/screenshots/scatterchart.png) + + - **CandleStickChart** (for financial data) + +![alt tag](https://raw.github.com/PhilJay/MPAndroidChart/master/screenshots/candlestickchart.png) + + - **BubbleChart** (area covered by bubbles indicates the value) + +![alt tag](https://raw.github.com/PhilJay/MPAndroidChart/master/screenshots/bubblechart.png) + + - **RadarChart** (spider web chart) + +![alt tag](https://raw.github.com/PhilJay/MPAndroidChart/master/screenshots/radarchart.png) + + +Documentation +======= +Currently there's no need for documentation for the iOS/tvOS/macOS version, as the API is **95% the same** as on Android. +You can read the official [MPAndroidChart](https://github.com/PhilJay/MPAndroidChart) documentation here: [**Wiki**](https://github.com/PhilJay/MPAndroidChart/wiki) + +Or you can see the Charts Demo project in both Objective-C and Swift ([**ChartsDemo-iOS**](https://github.com/danielgindi/Charts/tree/master/ChartsDemo-iOS), as well as macOS [**ChartsDemo-macOS**](https://github.com/danielgindi/Charts/tree/master/ChartsDemo-macOS)) and learn the how-tos from it. + + +Special Thanks +======= + +Goes to [@liuxuan30](https://github.com/liuxuan30), [@petester42](https://github.com/petester42) and [@AlBirdie](https://github.com/AlBirdie) for new features, bugfixes, and lots and lots of involvement in our open-sourced community! You guys are a huge help to all of those coming here with questions and issues, and I couldn't respond to all of those without you. + +### Our amazing sponsors + +[Debricked](https://debricked.com/): Use open source securely + +[![debricked](https://user-images.githubusercontent.com/4375169/73585544-25bfa800-44dd-11ea-9661-82519a125302.jpg)](https://debricked.com/) + + +License +======= +Copyright 2016 Daniel Cohen Gindi & Philipp Jahoda + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/dydx/Pods/Charts/Source/Charts/Animation/Animator.swift b/dydx/Pods/Charts/Source/Charts/Animation/Animator.swift new file mode 100644 index 000000000..7d0afa122 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Animation/Animator.swift @@ -0,0 +1,281 @@ +// +// Animator.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics +import QuartzCore + +@objc(ChartAnimatorDelegate) +public protocol AnimatorDelegate +{ + /// Called when the Animator has stepped. + func animatorUpdated(_ animator: Animator) + + /// Called when the Animator has stopped. + func animatorStopped(_ animator: Animator) +} + +@objc(ChartAnimator) +open class Animator: NSObject +{ + @objc open weak var delegate: AnimatorDelegate? + @objc open var updateBlock: (() -> Void)? + @objc open var stopBlock: (() -> Void)? + + /// the phase that is animated and influences the drawn values on the x-axis + @objc open var phaseX: Double = 1.0 + + /// the phase that is animated and influences the drawn values on the y-axis + @objc open var phaseY: Double = 1.0 + + private var _startTimeX: TimeInterval = 0.0 + private var _startTimeY: TimeInterval = 0.0 + private var _displayLink: NSUIDisplayLink? + + private var _durationX: TimeInterval = 0.0 + private var _durationY: TimeInterval = 0.0 + + private var _endTimeX: TimeInterval = 0.0 + private var _endTimeY: TimeInterval = 0.0 + private var _endTime: TimeInterval = 0.0 + + private var _enabledX: Bool = false + private var _enabledY: Bool = false + + private var _easingX: ChartEasingFunctionBlock? + private var _easingY: ChartEasingFunctionBlock? + + public override init() + { + super.init() + } + + deinit + { + stop() + } + + @objc open func stop() + { + guard _displayLink != nil else { return } + + _displayLink?.remove(from: .main, forMode: RunLoop.Mode.common) + _displayLink = nil + + _enabledX = false + _enabledY = false + + // If we stopped an animation in the middle, we do not want to leave it like this + if phaseX != 1.0 || phaseY != 1.0 + { + phaseX = 1.0 + phaseY = 1.0 + + delegate?.animatorUpdated(self) + updateBlock?() + } + + delegate?.animatorStopped(self) + stopBlock?() + } + + private func updateAnimationPhases(_ currentTime: TimeInterval) + { + if _enabledX + { + let elapsedTime: TimeInterval = currentTime - _startTimeX + let duration: TimeInterval = _durationX + var elapsed: TimeInterval = elapsedTime + if elapsed > duration + { + elapsed = duration + } + + phaseX = _easingX?(elapsed, duration) ?? elapsed / duration + } + + if _enabledY + { + let elapsedTime: TimeInterval = currentTime - _startTimeY + let duration: TimeInterval = _durationY + var elapsed: TimeInterval = elapsedTime + if elapsed > duration + { + elapsed = duration + } + + phaseY = _easingY?(elapsed, duration) ?? elapsed / duration + } + } + + @objc private func animationLoop() + { + let currentTime: TimeInterval = CACurrentMediaTime() + + updateAnimationPhases(currentTime) + + delegate?.animatorUpdated(self) + updateBlock?() + + if currentTime >= _endTime + { + stop() + } + } + + /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - easingX: an easing function for the animation on the x axis + /// - easingY: an easing function for the animation on the y axis + @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingX: ChartEasingFunctionBlock?, easingY: ChartEasingFunctionBlock?) + { + stop() + + _startTimeX = CACurrentMediaTime() + _startTimeY = _startTimeX + _durationX = xAxisDuration + _durationY = yAxisDuration + _endTimeX = _startTimeX + xAxisDuration + _endTimeY = _startTimeY + yAxisDuration + _endTime = _endTimeX > _endTimeY ? _endTimeX : _endTimeY + _enabledX = xAxisDuration > 0.0 + _enabledY = yAxisDuration > 0.0 + + _easingX = easingX + _easingY = easingY + + // Take care of the first frame if rendering is already scheduled... + updateAnimationPhases(_startTimeX) + + if _enabledX || _enabledY + { + _displayLink = NSUIDisplayLink(target: self, selector: #selector(animationLoop)) + _displayLink?.add(to: RunLoop.main, forMode: RunLoop.Mode.common) + } + } + + /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - easingOptionX: the easing function for the animation on the x axis + /// - easingOptionY: the easing function for the animation on the y axis + @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingOptionX: ChartEasingOption, easingOptionY: ChartEasingOption) + { + animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easingFunctionFromOption(easingOptionX), easingY: easingFunctionFromOption(easingOptionY)) + } + + /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - easing: an easing function for the animation + @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?) + { + animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easing, easingY: easing) + } + + /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - easingOption: the easing function for the animation + @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingOption: ChartEasingOption = .easeInOutSine) + { + animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easing: easingFunctionFromOption(easingOption)) + } + + /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - easing: an easing function for the animation + @objc open func animate(xAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?) + { + _startTimeX = CACurrentMediaTime() + _durationX = xAxisDuration + _endTimeX = _startTimeX + xAxisDuration + _endTime = _endTimeX > _endTimeY ? _endTimeX : _endTimeY + _enabledX = xAxisDuration > 0.0 + + _easingX = easing + + // Take care of the first frame if rendering is already scheduled... + updateAnimationPhases(_startTimeX) + + if _enabledX || _enabledY, + _displayLink == nil + { + _displayLink = NSUIDisplayLink(target: self, selector: #selector(animationLoop)) + _displayLink?.add(to: .main, forMode: RunLoop.Mode.common) + } + } + + /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - easingOption: the easing function for the animation + @objc open func animate(xAxisDuration: TimeInterval, easingOption: ChartEasingOption = .easeInOutSine) + { + animate(xAxisDuration: xAxisDuration, easing: easingFunctionFromOption(easingOption)) + } + + /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - yAxisDuration: duration for animating the y axis + /// - easing: an easing function for the animation + @objc open func animate(yAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?) + { + _startTimeY = CACurrentMediaTime() + _durationY = yAxisDuration + _endTimeY = _startTimeY + yAxisDuration + _endTime = _endTimeX > _endTimeY ? _endTimeX : _endTimeY + _enabledY = yAxisDuration > 0.0 + + _easingY = easing + + // Take care of the first frame if rendering is already scheduled... + updateAnimationPhases(_startTimeY) + + if _enabledX || _enabledY, + _displayLink == nil + { + _displayLink = NSUIDisplayLink(target: self, selector: #selector(animationLoop)) + _displayLink?.add(to: .main, forMode: RunLoop.Mode.common) + } + } + + /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - yAxisDuration: duration for animating the y axis + /// - easingOption: the easing function for the animation + @objc open func animate(yAxisDuration: TimeInterval, easingOption: ChartEasingOption = .easeInOutSine) + { + animate(yAxisDuration: yAxisDuration, easing: easingFunctionFromOption(easingOption)) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Animation/ChartAnimationEasing.swift b/dydx/Pods/Charts/Source/Charts/Animation/ChartAnimationEasing.swift new file mode 100644 index 000000000..8436f2151 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Animation/ChartAnimationEasing.swift @@ -0,0 +1,394 @@ +// +// ChartAnimationUtils.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public enum ChartEasingOption: Int +{ + case linear + case easeInQuad + case easeOutQuad + case easeInOutQuad + case easeInCubic + case easeOutCubic + case easeInOutCubic + case easeInQuart + case easeOutQuart + case easeInOutQuart + case easeInQuint + case easeOutQuint + case easeInOutQuint + case easeInSine + case easeOutSine + case easeInOutSine + case easeInExpo + case easeOutExpo + case easeInOutExpo + case easeInCirc + case easeOutCirc + case easeInOutCirc + case easeInElastic + case easeOutElastic + case easeInOutElastic + case easeInBack + case easeOutBack + case easeInOutBack + case easeInBounce + case easeOutBounce + case easeInOutBounce +} + +public typealias ChartEasingFunctionBlock = ((_ elapsed: TimeInterval, _ duration: TimeInterval) -> Double) + +internal func easingFunctionFromOption(_ easing: ChartEasingOption) -> ChartEasingFunctionBlock +{ + switch easing + { + case .linear: + return EasingFunctions.Linear + case .easeInQuad: + return EasingFunctions.EaseInQuad + case .easeOutQuad: + return EasingFunctions.EaseOutQuad + case .easeInOutQuad: + return EasingFunctions.EaseInOutQuad + case .easeInCubic: + return EasingFunctions.EaseInCubic + case .easeOutCubic: + return EasingFunctions.EaseOutCubic + case .easeInOutCubic: + return EasingFunctions.EaseInOutCubic + case .easeInQuart: + return EasingFunctions.EaseInQuart + case .easeOutQuart: + return EasingFunctions.EaseOutQuart + case .easeInOutQuart: + return EasingFunctions.EaseInOutQuart + case .easeInQuint: + return EasingFunctions.EaseInQuint + case .easeOutQuint: + return EasingFunctions.EaseOutQuint + case .easeInOutQuint: + return EasingFunctions.EaseInOutQuint + case .easeInSine: + return EasingFunctions.EaseInSine + case .easeOutSine: + return EasingFunctions.EaseOutSine + case .easeInOutSine: + return EasingFunctions.EaseInOutSine + case .easeInExpo: + return EasingFunctions.EaseInExpo + case .easeOutExpo: + return EasingFunctions.EaseOutExpo + case .easeInOutExpo: + return EasingFunctions.EaseInOutExpo + case .easeInCirc: + return EasingFunctions.EaseInCirc + case .easeOutCirc: + return EasingFunctions.EaseOutCirc + case .easeInOutCirc: + return EasingFunctions.EaseInOutCirc + case .easeInElastic: + return EasingFunctions.EaseInElastic + case .easeOutElastic: + return EasingFunctions.EaseOutElastic + case .easeInOutElastic: + return EasingFunctions.EaseInOutElastic + case .easeInBack: + return EasingFunctions.EaseInBack + case .easeOutBack: + return EasingFunctions.EaseOutBack + case .easeInOutBack: + return EasingFunctions.EaseInOutBack + case .easeInBounce: + return EasingFunctions.EaseInBounce + case .easeOutBounce: + return EasingFunctions.EaseOutBounce + case .easeInOutBounce: + return EasingFunctions.EaseInOutBounce + } +} + +internal struct EasingFunctions +{ + internal static let Linear = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in return Double(elapsed / duration) } + + internal static let EaseInQuad = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / duration) + return position * position + } + + internal static let EaseOutQuad = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / duration) + return -position * (position - 2.0) + } + + internal static let EaseInOutQuad = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / (duration / 2.0)) + if position < 1.0 + { + return 0.5 * position * position + } + + return -0.5 * ((position - 1.0) * (position - 3.0) - 1.0) + } + + internal static let EaseInCubic = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / duration) + return position * position * position + } + + internal static let EaseOutCubic = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / duration) + position -= 1.0 + return (position * position * position + 1.0) + } + + internal static let EaseInOutCubic = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / (duration / 2.0)) + if position < 1.0 + { + return 0.5 * position * position * position + } + position -= 2.0 + return 0.5 * (position * position * position + 2.0) + } + + internal static let EaseInQuart = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / duration) + return position * position * position * position + } + + internal static let EaseOutQuart = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / duration) + position -= 1.0 + return -(position * position * position * position - 1.0) + } + + internal static let EaseInOutQuart = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / (duration / 2.0)) + if position < 1.0 + { + return 0.5 * position * position * position * position + } + position -= 2.0 + return -0.5 * (position * position * position * position - 2.0) + } + + internal static let EaseInQuint = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / duration) + return position * position * position * position * position + } + + internal static let EaseOutQuint = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / duration) + position -= 1.0 + return (position * position * position * position * position + 1.0) + } + + internal static let EaseInOutQuint = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / (duration / 2.0)) + if position < 1.0 + { + return 0.5 * position * position * position * position * position + } + else + { + position -= 2.0 + return 0.5 * (position * position * position * position * position + 2.0) + } + } + + internal static let EaseInSine = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position: TimeInterval = elapsed / duration + return Double( -cos(position * Double.pi / 2) + 1.0 ) + } + + internal static let EaseOutSine = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position: TimeInterval = elapsed / duration + return Double( sin(position * Double.pi / 2) ) + } + + internal static let EaseInOutSine = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position: TimeInterval = elapsed / duration + return Double( -0.5 * (cos(Double.pi * position) - 1.0) ) + } + + internal static let EaseInExpo = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + return (elapsed == 0) ? 0.0 : Double(pow(2.0, 10.0 * (elapsed / duration - 1.0))) + } + + internal static let EaseOutExpo = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + return (elapsed == duration) ? 1.0 : (-Double(pow(2.0, -10.0 * elapsed / duration)) + 1.0) + } + + internal static let EaseInOutExpo = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + if elapsed == 0 + { + return 0.0 + } + if elapsed == duration + { + return 1.0 + } + + var position: TimeInterval = elapsed / (duration / 2.0) + if position < 1.0 + { + return Double( 0.5 * pow(2.0, 10.0 * (position - 1.0)) ) + } + + position = position - 1.0 + return Double( 0.5 * (-pow(2.0, -10.0 * position) + 2.0) ) + } + + internal static let EaseInCirc = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / duration) + return -(Double(sqrt(1.0 - position * position)) - 1.0) + } + + internal static let EaseOutCirc = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position = Double(elapsed / duration) + position -= 1.0 + return Double( sqrt(1 - position * position) ) + } + + internal static let EaseInOutCirc = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position: TimeInterval = elapsed / (duration / 2.0) + if position < 1.0 + { + return Double( -0.5 * (sqrt(1.0 - position * position) - 1.0) ) + } + position -= 2.0 + return Double( 0.5 * (sqrt(1.0 - position * position) + 1.0) ) + } + + internal static let EaseInElastic = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + if elapsed == 0.0 + { + return 0.0 + } + + var position: TimeInterval = elapsed / duration + if position == 1.0 + { + return 1.0 + } + + var p = duration * 0.3 + var s = p / (2.0 * Double.pi) * asin(1.0) + position -= 1.0 + return Double( -(pow(2.0, 10.0 * position) * sin((position * duration - s) * (2.0 * Double.pi) / p)) ) + } + + internal static let EaseOutElastic = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + if elapsed == 0.0 + { + return 0.0 + } + + var position: TimeInterval = elapsed / duration + if position == 1.0 + { + return 1.0 + } + + var p = duration * 0.3 + var s = p / (2.0 * Double.pi) * asin(1.0) + return Double( pow(2.0, -10.0 * position) * sin((position * duration - s) * (2.0 * Double.pi) / p) + 1.0 ) + } + + internal static let EaseInOutElastic = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + if elapsed == 0.0 + { + return 0.0 + } + + var position: TimeInterval = elapsed / (duration / 2.0) + if position == 2.0 + { + return 1.0 + } + + var p = duration * (0.3 * 1.5) + var s = p / (2.0 * Double.pi) * asin(1.0) + if position < 1.0 + { + position -= 1.0 + return Double( -0.5 * (pow(2.0, 10.0 * position) * sin((position * duration - s) * (2.0 * Double.pi) / p)) ) + } + position -= 1.0 + return Double( pow(2.0, -10.0 * position) * sin((position * duration - s) * (2.0 * Double.pi) / p) * 0.5 + 1.0 ) + } + + internal static let EaseInBack = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + let s: TimeInterval = 1.70158 + var position: TimeInterval = elapsed / duration + return Double( position * position * ((s + 1.0) * position - s) ) + } + + internal static func EaseOutBack(elapsed: TimeInterval, duration: TimeInterval) -> Double { + let s: TimeInterval = 1.70158 + var position: TimeInterval = elapsed / duration + position -= 1.0 + return Double( position * position * ((s + 1.0) * position + s) + 1.0 ) + } + + internal static let EaseInOutBack = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var s: TimeInterval = 1.70158 + var position: TimeInterval = elapsed / (duration / 2.0) + if position < 1.0 + { + s *= 1.525 + return Double( 0.5 * (position * position * ((s + 1.0) * position - s)) ) + } + s *= 1.525 + position -= 2.0 + return Double( 0.5 * (position * position * ((s + 1.0) * position + s) + 2.0) ) + } + + internal static let EaseInBounce = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + return 1.0 - EaseOutBounce(duration - elapsed, duration) + } + + internal static let EaseOutBounce = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + var position: TimeInterval = elapsed / duration + if position < (1.0 / 2.75) + { + return Double( 7.5625 * position * position ) + } + else if position < (2.0 / 2.75) + { + position -= (1.5 / 2.75) + return Double( 7.5625 * position * position + 0.75 ) + } + else if position < (2.5 / 2.75) + { + position -= (2.25 / 2.75) + return Double( 7.5625 * position * position + 0.9375 ) + } + else + { + position -= (2.625 / 2.75) + return Double( 7.5625 * position * position + 0.984375 ) + } + } + + internal static let EaseInOutBounce = { (elapsed: TimeInterval, duration: TimeInterval) -> Double in + if elapsed < (duration / 2.0) + { + return EaseInBounce(elapsed * 2.0, duration) * 0.5 + } + return EaseOutBounce(elapsed * 2.0 - duration, duration) * 0.5 + 0.5 + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Charts/BarChartView.swift b/dydx/Pods/Charts/Source/Charts/Charts/BarChartView.swift new file mode 100644 index 000000000..4f9aaa0bc --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Charts/BarChartView.swift @@ -0,0 +1,186 @@ +// +// BarChartView.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +/// Chart that draws bars. +open class BarChartView: BarLineChartViewBase, BarChartDataProvider +{ + /// if set to true, all values are drawn above their bars, instead of below their top + private var _drawValueAboveBarEnabled = true + + /// if set to true, a grey area is drawn behind each bar that indicates the maximum value + private var _drawBarShadowEnabled = false + + internal override func initialize() + { + super.initialize() + + renderer = BarChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler) + + self.highlighter = BarHighlighter(chart: self) + + self.xAxis.spaceMin = 0.5 + self.xAxis.spaceMax = 0.5 + } + + internal override func calcMinMax() + { + guard let data = self.data as? BarChartData + else { return } + + if fitBars + { + _xAxis.calculate( + min: data.xMin - data.barWidth / 2.0, + max: data.xMax + data.barWidth / 2.0) + } + else + { + _xAxis.calculate(min: data.xMin, max: data.xMax) + } + + // calculate axis range (min / max) according to provided data + leftAxis.calculate( + min: data.getYMin(axis: .left), + max: data.getYMax(axis: .left)) + rightAxis.calculate( + min: data.getYMin(axis: .right), + max: data.getYMax(axis: .right)) + } + + /// - Returns: The Highlight object (contains x-index and DataSet index) of the selected value at the given touch point inside the BarChart. + open override func getHighlightByTouchPoint(_ pt: CGPoint) -> Highlight? + { + if _data === nil + { + Swift.print("Can't select by touch. No data set.") + return nil + } + + guard let h = self.highlighter?.getHighlight(x: pt.x, y: pt.y) + else { return nil } + + if !isHighlightFullBarEnabled { return h } + + // For isHighlightFullBarEnabled, remove stackIndex + return Highlight( + x: h.x, y: h.y, + xPx: h.xPx, yPx: h.yPx, + dataIndex: h.dataIndex, + dataSetIndex: h.dataSetIndex, + stackIndex: -1, + axis: h.axis) + } + + /// - Returns: The bounding box of the specified Entry in the specified DataSet. Returns null if the Entry could not be found in the charts data. + @objc open func getBarBounds(entry e: BarChartDataEntry) -> CGRect + { + guard let + data = _data as? BarChartData, + let set = data.getDataSetForEntry(e) as? IBarChartDataSet + else { return CGRect.null } + + let y = e.y + let x = e.x + + let barWidth = data.barWidth + + let left = x - barWidth / 2.0 + let right = x + barWidth / 2.0 + let top = y >= 0.0 ? y : 0.0 + let bottom = y <= 0.0 ? y : 0.0 + + var bounds = CGRect(x: left, y: top, width: right - left, height: bottom - top) + + getTransformer(forAxis: set.axisDependency).rectValueToPixel(&bounds) + + return bounds + } + + /// Groups all BarDataSet objects this data object holds together by modifying the x-value of their entries. + /// Previously set x-values of entries will be overwritten. Leaves space between bars and groups as specified by the parameters. + /// Calls `notifyDataSetChanged()` afterwards. + /// + /// - Parameters: + /// - fromX: the starting point on the x-axis where the grouping should begin + /// - groupSpace: the space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f + /// - barSpace: the space between individual bars in values (not pixels) e.g. 0.1f for bar width 1f + @objc open func groupBars(fromX: Double, groupSpace: Double, barSpace: Double) + { + guard let barData = self.barData + else + { + Swift.print("You need to set data for the chart before grouping bars.", terminator: "\n") + return + } + + barData.groupBars(fromX: fromX, groupSpace: groupSpace, barSpace: barSpace) + notifyDataSetChanged() + } + + /// Highlights the value at the given x-value in the given DataSet. Provide -1 as the dataSetIndex to undo all highlighting. + /// + /// - Parameters: + /// - x: + /// - dataSetIndex: + /// - stackIndex: the index inside the stack - only relevant for stacked entries + @objc open func highlightValue(x: Double, dataSetIndex: Int, stackIndex: Int) + { + highlightValue(Highlight(x: x, dataSetIndex: dataSetIndex, stackIndex: stackIndex)) + } + + // MARK: Accessors + + /// if set to true, all values are drawn above their bars, instead of below their top + @objc open var drawValueAboveBarEnabled: Bool + { + get { return _drawValueAboveBarEnabled } + set + { + _drawValueAboveBarEnabled = newValue + setNeedsDisplay() + } + } + + /// if set to true, a grey area is drawn behind each bar that indicates the maximum value + @objc open var drawBarShadowEnabled: Bool + { + get { return _drawBarShadowEnabled } + set + { + _drawBarShadowEnabled = newValue + setNeedsDisplay() + } + } + + /// Adds half of the bar width to each side of the x-axis range in order to allow the bars of the barchart to be fully displayed. + /// **default**: false + @objc open var fitBars = false + + /// Set this to `true` to make the highlight operation full-bar oriented, `false` to make it highlight single values (relevant only for stacked). + /// If enabled, highlighting operations will highlight the whole bar, even if only a single stack entry was tapped. + @objc open var highlightFullBarEnabled: Bool = false + + /// `true` the highlight is be full-bar oriented, `false` ifsingle-value + open var isHighlightFullBarEnabled: Bool { return highlightFullBarEnabled } + + // MARK: - BarChartDataProvider + + open var barData: BarChartData? { return _data as? BarChartData } + + /// `true` if drawing values above bars is enabled, `false` ifnot + open var isDrawValueAboveBarEnabled: Bool { return drawValueAboveBarEnabled } + + /// `true` if drawing shadows (maxvalue) for each bar is enabled, `false` ifnot + open var isDrawBarShadowEnabled: Bool { return drawBarShadowEnabled } +} diff --git a/dydx/Pods/Charts/Source/Charts/Charts/BarLineChartViewBase.swift b/dydx/Pods/Charts/Source/Charts/Charts/BarLineChartViewBase.swift new file mode 100644 index 000000000..c33752493 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Charts/BarLineChartViewBase.swift @@ -0,0 +1,2036 @@ +// +// BarLineChartViewBase.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + +/// Base-class of LineChart, BarChart, ScatterChart and CandleStickChart. +open class BarLineChartViewBase: ChartViewBase, BarLineScatterCandleBubbleChartDataProvider, NSUIGestureRecognizerDelegate +{ + /// the maximum number of entries to which values will be drawn + /// (entry numbers greater than this value will cause value-labels to disappear) + internal var _maxVisibleCount = 100 + + /// flag that indicates if auto scaling on the y axis is enabled + private var _autoScaleMinMaxEnabled = false + + private var _pinchZoomEnabled = false + private var _doubleTapToZoomEnabled = true + private var _dragXEnabled = true + private var _dragYEnabled = true + + private var _scaleXEnabled = true + private var _scaleYEnabled = true + + /// the color for the background of the chart-drawing area (everything behind the grid lines). + @objc open var gridBackgroundColor = NSUIColor(red: 240/255.0, green: 240/255.0, blue: 240/255.0, alpha: 1.0) + + @objc open var borderColor = NSUIColor.black + @objc open var borderLineWidth: CGFloat = 1.0 + + /// flag indicating if the grid background should be drawn or not + @objc open var drawGridBackgroundEnabled = false + + /// When enabled, the borders rectangle will be rendered. + /// If this is enabled, there is no point drawing the axis-lines of x- and y-axis. + @objc open var drawBordersEnabled = false + + /// When enabled, the values will be clipped to contentRect, otherwise they can bleed outside the content rect. + @objc open var clipValuesToContentEnabled: Bool = false + + /// When disabled, the data and/or highlights will not be clipped to contentRect. Disabling this option can + /// be useful, when the data lies fully within the content rect, but is drawn in such a way (such as thick lines) + /// that there is unwanted clipping. + @objc open var clipDataToContentEnabled: Bool = true + + /// Sets the minimum offset (padding) around the chart, defaults to 10 + @objc open var minOffset = CGFloat(10.0) + + /// Sets whether the chart should keep its position (zoom / scroll) after a rotation (orientation change) + /// **default**: false + @objc open var keepPositionOnRotation: Bool = false + + /// The left y-axis object. In the horizontal bar-chart, this is the + /// top axis. + @objc open internal(set) var leftAxis = YAxis(position: .left) + + /// The right y-axis object. In the horizontal bar-chart, this is the + /// bottom axis. + @objc open internal(set) var rightAxis = YAxis(position: .right) + + /// The left Y axis renderer. This is a read-write property so you can set your own custom renderer here. + /// **default**: An instance of YAxisRenderer + @objc open lazy var leftYAxisRenderer = YAxisRenderer(viewPortHandler: _viewPortHandler, yAxis: leftAxis, transformer: _leftAxisTransformer) + + /// The right Y axis renderer. This is a read-write property so you can set your own custom renderer here. + /// **default**: An instance of YAxisRenderer + @objc open lazy var rightYAxisRenderer = YAxisRenderer(viewPortHandler: _viewPortHandler, yAxis: rightAxis, transformer: _rightAxisTransformer) + + internal var _leftAxisTransformer: Transformer! + internal var _rightAxisTransformer: Transformer! + + /// The X axis renderer. This is a read-write property so you can set your own custom renderer here. + /// **default**: An instance of XAxisRenderer + @objc open lazy var xAxisRenderer = XAxisRenderer(viewPortHandler: _viewPortHandler, xAxis: _xAxis, transformer: _leftAxisTransformer) + + internal var _longPressGestureRecognizer: UILongPressGestureRecognizer! + internal var _tapGestureRecognizer: NSUITapGestureRecognizer! + internal var _doubleTapGestureRecognizer: NSUITapGestureRecognizer! + #if !os(tvOS) + internal var _pinchGestureRecognizer: NSUIPinchGestureRecognizer! + #endif + internal var _panGestureRecognizer: NSUIPanGestureRecognizer! + + /// flag that indicates if a custom viewport offset has been set + private var _customViewPortEnabled = false + + private var longPressAndPanning: Bool = false + override open var maxHighlightDistance: CGFloat { + get { + return longPressAndPanning ? 9999 : super.maxHighlightDistance + } + set { + if !longPressAndPanning { + super.maxHighlightDistance = newValue + } + } + } + + public override init(frame: CGRect) + { + super.init(frame: frame) + } + + public required init?(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder) + } + + deinit + { + stopDeceleration() + } + + internal override func initialize() + { + super.initialize() + + _leftAxisTransformer = Transformer(viewPortHandler: _viewPortHandler) + _rightAxisTransformer = Transformer(viewPortHandler: _viewPortHandler) + + self.highlighter = ChartHighlighter(chart: self) + + _longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressGestureRecognized(_:))) + _longPressGestureRecognizer.minimumPressDuration = 0.2 + _tapGestureRecognizer = NSUITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:))) + _doubleTapGestureRecognizer = NSUITapGestureRecognizer(target: self, action: #selector(doubleTapGestureRecognized(_:))) + _doubleTapGestureRecognizer.nsuiNumberOfTapsRequired = 2 + _panGestureRecognizer = NSUIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized(_:))) + + _panGestureRecognizer.delegate = self + + self.addGestureRecognizer(_longPressGestureRecognizer) + self.addGestureRecognizer(_tapGestureRecognizer) + self.addGestureRecognizer(_doubleTapGestureRecognizer) + self.addGestureRecognizer(_panGestureRecognizer) + + _doubleTapGestureRecognizer.isEnabled = _doubleTapToZoomEnabled + _panGestureRecognizer.isEnabled = _dragXEnabled || _dragYEnabled + + #if !os(tvOS) + _pinchGestureRecognizer = NSUIPinchGestureRecognizer(target: self, action: #selector(BarLineChartViewBase.pinchGestureRecognized(_:))) + _pinchGestureRecognizer.delegate = self + self.addGestureRecognizer(_pinchGestureRecognizer) + _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled + #endif + } + + open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) + { + // Saving current position of chart. + var oldPoint: CGPoint? + if (keepPositionOnRotation && (keyPath == "frame" || keyPath == "bounds")) + { + oldPoint = viewPortHandler.contentRect.origin + getTransformer(forAxis: .left).pixelToValues(&oldPoint!) + } + + // Superclass transforms chart. + super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) + + // Restoring old position of chart + if var newPoint = oldPoint , keepPositionOnRotation + { + getTransformer(forAxis: .left).pointValueToPixel(&newPoint) + viewPortHandler.centerViewPort(pt: newPoint, chart: self) + } + else + { + viewPortHandler.refresh(newMatrix: viewPortHandler.touchMatrix, chart: self, invalidate: true) + } + } + + open override func draw(_ rect: CGRect) + { + super.draw(rect) + + guard data != nil, let renderer = renderer else { return } + + let optionalContext = NSUIGraphicsGetCurrentContext() + guard let context = optionalContext else { return } + + // execute all drawing commands + drawGridBackground(context: context) + + + if _autoScaleMinMaxEnabled + { + autoScale() + } + + if leftAxis.isEnabled + { + leftYAxisRenderer.computeAxis(min: leftAxis._axisMinimum, max: leftAxis._axisMaximum, inverted: leftAxis.isInverted) + } + + if rightAxis.isEnabled + { + rightYAxisRenderer.computeAxis(min: rightAxis._axisMinimum, max: rightAxis._axisMaximum, inverted: rightAxis.isInverted) + } + + if _xAxis.isEnabled + { + xAxisRenderer.computeAxis(min: _xAxis._axisMinimum, max: _xAxis._axisMaximum, inverted: false) + } + + xAxisRenderer.renderAxisLine(context: context) + leftYAxisRenderer.renderAxisLine(context: context) + rightYAxisRenderer.renderAxisLine(context: context) + + // The renderers are responsible for clipping, to account for line-width center etc. + if xAxis.drawGridLinesBehindDataEnabled + { + xAxisRenderer.renderGridLines(context: context) + leftYAxisRenderer.renderGridLines(context: context) + rightYAxisRenderer.renderGridLines(context: context) + } + + if _xAxis.isEnabled && _xAxis.isDrawLimitLinesBehindDataEnabled + { + xAxisRenderer.renderLimitLines(context: context) + } + + if leftAxis.isEnabled && leftAxis.isDrawLimitLinesBehindDataEnabled + { + leftYAxisRenderer.renderLimitLines(context: context) + } + + if rightAxis.isEnabled && rightAxis.isDrawLimitLinesBehindDataEnabled + { + rightYAxisRenderer.renderLimitLines(context: context) + } + + context.saveGState() + // make sure the data cannot be drawn outside the content-rect + if clipDataToContentEnabled { + context.clip(to: _viewPortHandler.contentRect) + } + renderer.drawData(context: context) + + // The renderers are responsible for clipping, to account for line-width center etc. + if !xAxis.drawGridLinesBehindDataEnabled + { + xAxisRenderer.renderGridLines(context: context) + leftYAxisRenderer.renderGridLines(context: context) + rightYAxisRenderer.renderGridLines(context: context) + } + + // if highlighting is enabled + if (valuesToHighlight()) + { + renderer.drawHighlighted(context: context, indices: _indicesToHighlight) + } + + context.restoreGState() + + renderer.drawExtras(context: context) + + if _xAxis.isEnabled && !_xAxis.isDrawLimitLinesBehindDataEnabled + { + xAxisRenderer.renderLimitLines(context: context) + } + + if leftAxis.isEnabled && !leftAxis.isDrawLimitLinesBehindDataEnabled + { + leftYAxisRenderer.renderLimitLines(context: context) + } + + if rightAxis.isEnabled && !rightAxis.isDrawLimitLinesBehindDataEnabled + { + rightYAxisRenderer.renderLimitLines(context: context) + } + + xAxisRenderer.renderAxisLabels(context: context) + leftYAxisRenderer.renderAxisLabels(context: context) + rightYAxisRenderer.renderAxisLabels(context: context) + + if clipValuesToContentEnabled + { + context.saveGState() + context.clip(to: _viewPortHandler.contentRect) + + renderer.drawValues(context: context) + + context.restoreGState() + } + else + { + renderer.drawValues(context: context) + } + + _legendRenderer.renderLegend(context: context) + + drawDescription(context: context) + + drawMarkers(context: context) + } + + private var _autoScaleLastLowestVisibleX: Double? + private var _autoScaleLastHighestVisibleX: Double? + + /// Performs auto scaling of the axis by recalculating the minimum and maximum y-values based on the entries currently in view. + open func autoScale() + { + guard let data = _data + else { return } + + data.calcMinMaxY(fromX: self.lowestVisibleX, toX: self.highestVisibleX) + + _xAxis.calculate(min: data.xMin, max: data.xMax) + + // calculate axis range (min / max) according to provided data + + if leftAxis.isEnabled + { + leftAxis.calculate(min: data.getYMin(axis: .left), max: data.getYMax(axis: .left)) + } + + if rightAxis.isEnabled + { + rightAxis.calculate(min: data.getYMin(axis: .right), max: data.getYMax(axis: .right)) + } + + calculateOffsets() + } + + internal func prepareValuePxMatrix() + { + _rightAxisTransformer.prepareMatrixValuePx(chartXMin: _xAxis._axisMinimum, deltaX: CGFloat(xAxis.axisRange), deltaY: CGFloat(rightAxis.axisRange), chartYMin: rightAxis._axisMinimum) + _leftAxisTransformer.prepareMatrixValuePx(chartXMin: xAxis._axisMinimum, deltaX: CGFloat(xAxis.axisRange), deltaY: CGFloat(leftAxis.axisRange), chartYMin: leftAxis._axisMinimum) + } + + internal func prepareOffsetMatrix() + { + _rightAxisTransformer.prepareMatrixOffset(inverted: rightAxis.isInverted) + _leftAxisTransformer.prepareMatrixOffset(inverted: leftAxis.isInverted) + } + + open override func notifyDataSetChanged() + { + renderer?.initBuffers() + + calcMinMax() + + leftYAxisRenderer.computeAxis(min: leftAxis._axisMinimum, max: leftAxis._axisMaximum, inverted: leftAxis.isInverted) + rightYAxisRenderer.computeAxis(min: rightAxis._axisMinimum, max: rightAxis._axisMaximum, inverted: rightAxis.isInverted) + + if let data = _data + { + xAxisRenderer.computeAxis( + min: _xAxis._axisMinimum, + max: _xAxis._axisMaximum, + inverted: false) + + if _legend !== nil + { + legendRenderer?.computeLegend(data: data) + } + } + + calculateOffsets() + + setNeedsDisplay() + } + + internal override func calcMinMax() + { + // calculate / set x-axis range + _xAxis.calculate(min: _data?.xMin ?? 0.0, max: _data?.xMax ?? 0.0) + + // calculate axis range (min / max) according to provided data + leftAxis.calculate(min: _data?.getYMin(axis: .left) ?? 0.0, max: _data?.getYMax(axis: .left) ?? 0.0) + rightAxis.calculate(min: _data?.getYMin(axis: .right) ?? 0.0, max: _data?.getYMax(axis: .right) ?? 0.0) + } + + internal func calculateLegendOffsets(offsetLeft: inout CGFloat, offsetTop: inout CGFloat, offsetRight: inout CGFloat, offsetBottom: inout CGFloat) + { + // setup offsets for legend + if _legend !== nil && _legend.isEnabled && !_legend.drawInside + { + switch _legend.orientation + { + case .vertical: + + switch _legend.horizontalAlignment + { + case .left: + offsetLeft += min(_legend.neededWidth, _viewPortHandler.chartWidth * _legend.maxSizePercent) + _legend.xOffset + + case .right: + offsetRight += min(_legend.neededWidth, _viewPortHandler.chartWidth * _legend.maxSizePercent) + _legend.xOffset + + case .center: + + switch _legend.verticalAlignment + { + case .top: + offsetTop += min(_legend.neededHeight, _viewPortHandler.chartHeight * _legend.maxSizePercent) + _legend.yOffset + + case .bottom: + offsetBottom += min(_legend.neededHeight, _viewPortHandler.chartHeight * _legend.maxSizePercent) + _legend.yOffset + + default: + break + } + } + + case .horizontal: + + switch _legend.verticalAlignment + { + case .top: + offsetTop += min(_legend.neededHeight, _viewPortHandler.chartHeight * _legend.maxSizePercent) + _legend.yOffset + + case .bottom: + offsetBottom += min(_legend.neededHeight, _viewPortHandler.chartHeight * _legend.maxSizePercent) + _legend.yOffset + + default: + break + } + } + } + } + + internal override func calculateOffsets() + { + if !_customViewPortEnabled + { + var offsetLeft = CGFloat(0.0) + var offsetRight = CGFloat(0.0) + var offsetTop = CGFloat(0.0) + var offsetBottom = CGFloat(0.0) + + calculateLegendOffsets(offsetLeft: &offsetLeft, + offsetTop: &offsetTop, + offsetRight: &offsetRight, + offsetBottom: &offsetBottom) + + // offsets for y-labels + if leftAxis.needsOffset + { + offsetLeft += leftAxis.requiredSize().width + } + + if rightAxis.needsOffset + { + offsetRight += rightAxis.requiredSize().width + } + + if xAxis.isEnabled && xAxis.isDrawLabelsEnabled + { + let xlabelheight = xAxis.labelRotatedHeight + xAxis.yOffset + + // offsets for x-labels + if xAxis.labelPosition == .bottom + { + offsetBottom += xlabelheight + } + else if xAxis.labelPosition == .top + { + offsetTop += xlabelheight + } + else if xAxis.labelPosition == .bothSided + { + offsetBottom += xlabelheight + offsetTop += xlabelheight + } + } + + offsetTop += self.extraTopOffset + offsetRight += self.extraRightOffset + offsetBottom += self.extraBottomOffset + offsetLeft += self.extraLeftOffset + + _viewPortHandler.restrainViewPort( + offsetLeft: max(self.minOffset, offsetLeft), + offsetTop: max(self.minOffset, offsetTop), + offsetRight: max(self.minOffset, offsetRight), + offsetBottom: max(self.minOffset, offsetBottom)) + } + + prepareOffsetMatrix() + prepareValuePxMatrix() + } + + /// draws the grid background + internal func drawGridBackground(context: CGContext) + { + if drawGridBackgroundEnabled || drawBordersEnabled + { + context.saveGState() + } + + if drawGridBackgroundEnabled + { + // draw the grid background + context.setFillColor(gridBackgroundColor.cgColor) + context.fill(_viewPortHandler.contentRect) + } + + if drawBordersEnabled + { + context.setLineWidth(borderLineWidth) + context.setStrokeColor(borderColor.cgColor) + context.stroke(_viewPortHandler.contentRect) + } + + if drawGridBackgroundEnabled || drawBordersEnabled + { + context.restoreGState() + } + } + + // MARK: - Gestures + + private enum GestureScaleAxis + { + case both + case x + case y + } + + private var _isDragging = false + private var _isScaling = false + private var _gestureScaleAxis = GestureScaleAxis.both + private var _closestDataSetToTouch: IChartDataSet! + private var _panGestureReachedEdge: Bool = false + private weak var _outerScrollView: NSUIScrollView? + + private var _lastPanPoint = CGPoint() /// This is to prevent using setTranslation which resets velocity + + private var _decelerationLastTime: TimeInterval = 0.0 + private var _decelerationDisplayLink: NSUIDisplayLink! + private var _decelerationVelocity = CGPoint() + + @objc private func longPressGestureRecognized(_ recognizer: UILongPressGestureRecognizer) + { + if _data === nil + { + return + } + + if recognizer.state == NSUIGestureRecognizerState.began + { + longPressAndPanning = true + let h = getHighlightByTouchPoint(recognizer.location(in: self)) + + lastHighlighted = h + highlightValue(h, callDelegate: true) + if lastHighlighted === nil { + longPressAndPanning = false + } + } else if recognizer.state == .ended || recognizer.state == .cancelled { + longPressAndPanning = false + lastHighlighted = nil + highlightValue(nil, callDelegate: true) + } + } + + @objc private func tapGestureRecognized(_ recognizer: NSUITapGestureRecognizer) + { + if _data === nil + { + return + } + + if recognizer.state == NSUIGestureRecognizerState.ended + { + if !isHighLightPerTapEnabled { return } + + let h = getHighlightByTouchPoint(recognizer.location(in: self)) + + if h === nil || h == self.lastHighlighted + { + lastHighlighted = nil + highlightValue(nil, callDelegate: true) + } + else + { + lastHighlighted = h + highlightValue(h, callDelegate: true) + } + } + } + + @objc private func doubleTapGestureRecognized(_ recognizer: NSUITapGestureRecognizer) + { + if _data === nil + { + return + } + + if recognizer.state == NSUIGestureRecognizerState.ended + { + if _data !== nil && _doubleTapToZoomEnabled && (data?.entryCount ?? 0) > 0 + { + var location = recognizer.location(in: self) + location.x = location.x - _viewPortHandler.offsetLeft + + if isTouchInverted() + { + location.y = -(location.y - _viewPortHandler.offsetTop) + } + else + { + location.y = -(self.bounds.size.height - location.y - _viewPortHandler.offsetBottom) + } + + let scaleX: CGFloat = isScaleXEnabled ? 1.4 : 1.0 + let scaleY: CGFloat = isScaleYEnabled ? 1.4 : 1.0 + + self.zoom(scaleX: scaleX, scaleY: scaleY, x: location.x, y: location.y) + delegate?.chartScaled?(self, scaleX: scaleX, scaleY: scaleY) + } + } + } + + #if !os(tvOS) + @objc private func pinchGestureRecognized(_ recognizer: NSUIPinchGestureRecognizer) + { + if recognizer.state == NSUIGestureRecognizerState.began + { + stopDeceleration() + + if _data !== nil && + (_pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled) + { + _isScaling = true + + if _pinchZoomEnabled + { + _gestureScaleAxis = .both + } + else + { + let x = abs(recognizer.location(in: self).x - recognizer.nsuiLocationOfTouch(1, inView: self).x) + let y = abs(recognizer.location(in: self).y - recognizer.nsuiLocationOfTouch(1, inView: self).y) + + if _scaleXEnabled != _scaleYEnabled + { + _gestureScaleAxis = _scaleXEnabled ? .x : .y + } + else + { + _gestureScaleAxis = x > y ? .x : .y + } + } + } + } + else if recognizer.state == NSUIGestureRecognizerState.ended || + recognizer.state == NSUIGestureRecognizerState.cancelled + { + if _isScaling + { + _isScaling = false + + // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. + calculateOffsets() + setNeedsDisplay() + } + } + else if recognizer.state == NSUIGestureRecognizerState.changed + { + let isZoomingOut = (recognizer.nsuiScale < 1) + var canZoomMoreX = isZoomingOut ? _viewPortHandler.canZoomOutMoreX : _viewPortHandler.canZoomInMoreX + var canZoomMoreY = isZoomingOut ? _viewPortHandler.canZoomOutMoreY : _viewPortHandler.canZoomInMoreY + + if _isScaling + { + canZoomMoreX = canZoomMoreX && _scaleXEnabled && (_gestureScaleAxis == .both || _gestureScaleAxis == .x) + canZoomMoreY = canZoomMoreY && _scaleYEnabled && (_gestureScaleAxis == .both || _gestureScaleAxis == .y) + if canZoomMoreX || canZoomMoreY + { + var location = recognizer.location(in: self) + location.x = location.x - _viewPortHandler.offsetLeft + + if isTouchInverted() + { + location.y = -(location.y - _viewPortHandler.offsetTop) + } + else + { + location.y = -(_viewPortHandler.chartHeight - location.y - _viewPortHandler.offsetBottom) + } + + let scaleX = canZoomMoreX ? recognizer.nsuiScale : 1.0 + let scaleY = canZoomMoreY ? recognizer.nsuiScale : 1.0 + + var matrix = CGAffineTransform(translationX: location.x, y: location.y) + matrix = matrix.scaledBy(x: scaleX, y: scaleY) + matrix = matrix.translatedBy(x: -location.x, y: -location.y) + + matrix = _viewPortHandler.touchMatrix.concatenating(matrix) + + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true) + + if delegate !== nil + { + delegate?.chartScaled?(self, scaleX: scaleX, scaleY: scaleY) + } + } + + recognizer.nsuiScale = 1.0 + } + } + } + #endif + + @objc private func panGestureRecognized(_ recognizer: NSUIPanGestureRecognizer) + { + if recognizer.state == NSUIGestureRecognizerState.began && recognizer.nsuiNumberOfTouches() > 0 + { + stopDeceleration() + if self.isHighlightPerDragEnabled + { + let h = getHighlightByTouchPoint(recognizer.location(in: self)) + + let lastHighlighted = self.lastHighlighted + + if h != lastHighlighted + { + self.lastHighlighted = h + self.highlightValue(h, callDelegate: true) + } + // We will only handle highlights on NSUIGestureRecognizerState.Changed + if self.lastHighlighted !== nil { + _isDragging = false + return + } + } + + if _data === nil || !self.isDragEnabled + { // If we have no data, we have nothing to pan and no data to highlight + return + } + + // If drag is enabled and we are in a position where there's something to drag: + // * If we're zoomed in, then obviously we have something to drag. + // * If we have a drag offset - we always have something to drag + if !self.hasNoDragOffset || !self.isFullyZoomedOut + { + _isDragging = true + + _closestDataSetToTouch = getDataSetByTouchPoint(point: recognizer.nsuiLocationOfTouch(0, inView: self)) + + var translation = recognizer.translation(in: self) + if !self.dragXEnabled + { + translation.x = 0.0 + } + else if !self.dragYEnabled + { + translation.y = 0.0 + } + + let didUserDrag = translation.x != 0.0 || translation.y != 0.0 + + // Check to see if user dragged at all and if so, can the chart be dragged by the given amount + if didUserDrag && !performPanChange(translation: translation) + { + if _outerScrollView !== nil + { + // We can stop dragging right now, and let the scroll view take control + _outerScrollView = nil + _isDragging = false + } + } + else + { + if _outerScrollView !== nil + { + // Prevent the parent scroll view from scrolling + _outerScrollView?.nsuiIsScrollEnabled = false + } + } + + _lastPanPoint = recognizer.translation(in: self) + } + } + else if recognizer.state == NSUIGestureRecognizerState.changed + { + if _isDragging + { + let originalTranslation = recognizer.translation(in: self) + var translation = CGPoint(x: originalTranslation.x - _lastPanPoint.x, y: originalTranslation.y - _lastPanPoint.y) + + if !self.dragXEnabled + { + translation.x = 0.0 + } + else if !self.dragYEnabled + { + translation.y = 0.0 + } + + let _ = performPanChange(translation: translation) + + _lastPanPoint = originalTranslation + } + else if isHighlightPerDragEnabled + { + let h = getHighlightByTouchPoint(recognizer.location(in: self)) + + let lastHighlighted = self.lastHighlighted + + if h != lastHighlighted + { + self.lastHighlighted = h + self.highlightValue(h, callDelegate: true) + } + } + } + else if recognizer.state == NSUIGestureRecognizerState.ended || recognizer.state == NSUIGestureRecognizerState.cancelled + { + longPressAndPanning = false + if _isDragging + { + if recognizer.state == NSUIGestureRecognizerState.ended && isDragDecelerationEnabled + { + stopDeceleration() + + _decelerationLastTime = CACurrentMediaTime() + _decelerationVelocity = recognizer.velocity(in: self) + + _decelerationDisplayLink = NSUIDisplayLink(target: self, selector: #selector(BarLineChartViewBase.decelerationLoop)) + _decelerationDisplayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common) + } + + _isDragging = false + + delegate?.chartViewDidEndPanning?(self) + } + else if isHighlightPerDragEnabled + { + if lastHighlighted != nil + { + self.lastHighlighted = nil + self.highlightValue(nil, callDelegate: true) + } + } + + if _outerScrollView !== nil + { + _outerScrollView?.nsuiIsScrollEnabled = true + _outerScrollView = nil + } + } + } + + private func performPanChange(translation: CGPoint) -> Bool + { + var translation = translation + + if isTouchInverted() + { + if self is HorizontalBarChartView + { + translation.x = -translation.x + } + else + { + translation.y = -translation.y + } + } + + let originalMatrix = _viewPortHandler.touchMatrix + + var matrix = CGAffineTransform(translationX: translation.x, y: translation.y) + matrix = originalMatrix.concatenating(matrix) + + matrix = _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true) + + if matrix != originalMatrix + { + delegate?.chartTranslated?(self, dX: translation.x, dY: translation.y) + } + + // Did we managed to actually drag or did we reach the edge? + return matrix.tx != originalMatrix.tx || matrix.ty != originalMatrix.ty + } + + private func isTouchInverted() -> Bool + { + return isAnyAxisInverted && + _closestDataSetToTouch !== nil && + getAxis(_closestDataSetToTouch.axisDependency).isInverted + } + + @objc open func stopDeceleration() + { + if _decelerationDisplayLink !== nil + { + _decelerationDisplayLink.remove(from: RunLoop.main, forMode: RunLoop.Mode.common) + _decelerationDisplayLink = nil + } + } + + @objc private func decelerationLoop() + { + let currentTime = CACurrentMediaTime() + + _decelerationVelocity.x *= self.dragDecelerationFrictionCoef + _decelerationVelocity.y *= self.dragDecelerationFrictionCoef + + let timeInterval = CGFloat(currentTime - _decelerationLastTime) + + let distance = CGPoint( + x: _decelerationVelocity.x * timeInterval, + y: _decelerationVelocity.y * timeInterval + ) + + if !performPanChange(translation: distance) + { + // We reached the edge, stop + _decelerationVelocity.x = 0.0 + _decelerationVelocity.y = 0.0 + } + + _decelerationLastTime = currentTime + + if abs(_decelerationVelocity.x) < 0.001 && abs(_decelerationVelocity.y) < 0.001 + { + stopDeceleration() + + // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. + calculateOffsets() + setNeedsDisplay() + } + } + + private func nsuiGestureRecognizerShouldBegin(_ gestureRecognizer: NSUIGestureRecognizer) -> Bool + { + if gestureRecognizer == _panGestureRecognizer + { + let velocity = _panGestureRecognizer.velocity(in: self) + if _data === nil || !isDragEnabled || + (self.hasNoDragOffset && self.isFullyZoomedOut && !self.isHighlightPerDragEnabled) || + (!_dragYEnabled && abs(velocity.y) > abs(velocity.x)) || + (!_dragXEnabled && abs(velocity.y) < abs(velocity.x)) + { + return false + } + } + else + { + #if !os(tvOS) + if gestureRecognizer == _pinchGestureRecognizer + { + if _data === nil || (!_pinchZoomEnabled && !_scaleXEnabled && !_scaleYEnabled) + { + return false + } + } + #endif + } + + return true + } + + #if !os(OSX) + open override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool + { + if !super.gestureRecognizerShouldBegin(gestureRecognizer) + { + return false + } + + return nsuiGestureRecognizerShouldBegin(gestureRecognizer) + } + #endif + + #if os(OSX) + public func gestureRecognizerShouldBegin(gestureRecognizer: NSGestureRecognizer) -> Bool + { + return nsuiGestureRecognizerShouldBegin(gestureRecognizer) + } + #endif + + open func gestureRecognizer(_ gestureRecognizer: NSUIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: NSUIGestureRecognizer) -> Bool + { + #if !os(tvOS) + if ((gestureRecognizer is NSUIPinchGestureRecognizer && otherGestureRecognizer is NSUIPanGestureRecognizer) || + (gestureRecognizer is NSUIPanGestureRecognizer && otherGestureRecognizer is NSUIPinchGestureRecognizer)) + { + return true + } + #endif + + if gestureRecognizer is NSUIPanGestureRecognizer, + otherGestureRecognizer is NSUIPanGestureRecognizer, + gestureRecognizer == _panGestureRecognizer + { + var scrollView = self.superview + while scrollView != nil && !(scrollView is NSUIScrollView) + { + scrollView = scrollView?.superview + } + + // If there is two scrollview together, we pick the superview of the inner scrollview. + // In the case of UITableViewWrepperView, the superview will be UITableView + if let superViewOfScrollView = scrollView?.superview, + superViewOfScrollView is NSUIScrollView + { + scrollView = superViewOfScrollView + } + + var foundScrollView = scrollView as? NSUIScrollView + + if !(foundScrollView?.nsuiIsScrollEnabled ?? true) + { + foundScrollView = nil + } + + let scrollViewPanGestureRecognizer = foundScrollView?.nsuiGestureRecognizers?.first { + $0 is NSUIPanGestureRecognizer + } + + if otherGestureRecognizer === scrollViewPanGestureRecognizer + { + _outerScrollView = foundScrollView + + return true + } + } + + if gestureRecognizer is NSUIPanGestureRecognizer, + otherGestureRecognizer is UILongPressGestureRecognizer, + gestureRecognizer == _panGestureRecognizer + { + return true + } + + return false + } + + /// MARK: Viewport modifiers + + /// Zooms in by 1.4, into the charts center. + @objc open func zoomIn() + { + let center = _viewPortHandler.contentCenter + + let matrix = _viewPortHandler.zoomIn(x: center.x, y: -center.y) + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) + + // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. + calculateOffsets() + setNeedsDisplay() + } + + /// Zooms out by 0.7, from the charts center. + @objc open func zoomOut() + { + let center = _viewPortHandler.contentCenter + + let matrix = _viewPortHandler.zoomOut(x: center.x, y: -center.y) + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) + + // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. + calculateOffsets() + setNeedsDisplay() + } + + /// Zooms out to original size. + @objc open func resetZoom() + { + let matrix = _viewPortHandler.resetZoom() + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) + + // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. + calculateOffsets() + setNeedsDisplay() + } + + /// Zooms in or out by the given scale factor. x and y are the coordinates + /// (in pixels) of the zoom center. + /// + /// - Parameters: + /// - scaleX: if < 1 --> zoom out, if > 1 --> zoom in + /// - scaleY: if < 1 --> zoom out, if > 1 --> zoom in + /// - x: + /// - y: + @objc open func zoom( + scaleX: CGFloat, + scaleY: CGFloat, + x: CGFloat, + y: CGFloat) + { + let matrix = _viewPortHandler.zoom(scaleX: scaleX, scaleY: scaleY, x: x, y: -y) + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) + + // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. + calculateOffsets() + setNeedsDisplay() + } + + /// Zooms in or out by the given scale factor. + /// x and y are the values (**not pixels**) of the zoom center. + /// + /// - Parameters: + /// - scaleX: if < 1 --> zoom out, if > 1 --> zoom in + /// - scaleY: if < 1 --> zoom out, if > 1 --> zoom in + /// - xValue: + /// - yValue: + /// - axis: + @objc open func zoom( + scaleX: CGFloat, + scaleY: CGFloat, + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency) + { + let job = ZoomViewJob( + viewPortHandler: viewPortHandler, + scaleX: scaleX, + scaleY: scaleY, + xValue: xValue, + yValue: yValue, + transformer: getTransformer(forAxis: axis), + axis: axis, + view: self) + addViewportJob(job) + } + + /// Zooms to the center of the chart with the given scale factor. + /// + /// - Parameters: + /// - scaleX: if < 1 --> zoom out, if > 1 --> zoom in + /// - scaleY: if < 1 --> zoom out, if > 1 --> zoom in + /// - xValue: + /// - yValue: + /// - axis: + @objc open func zoomToCenter( + scaleX: CGFloat, + scaleY: CGFloat) + { + let center = centerOffsets + let matrix = viewPortHandler.zoom( + scaleX: scaleX, + scaleY: scaleY, + x: center.x, + y: -center.y) + viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) + } + + /// Zooms by the specified scale factor to the specified values on the specified axis. + /// + /// - Parameters: + /// - scaleX: + /// - scaleY: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func zoomAndCenterViewAnimated( + scaleX: CGFloat, + scaleY: CGFloat, + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval, + easing: ChartEasingFunctionBlock?) + { + let origin = valueForTouchPoint( + point: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop), + axis: axis) + + let job = AnimatedZoomViewJob( + viewPortHandler: viewPortHandler, + transformer: getTransformer(forAxis: axis), + view: self, + yAxis: getAxis(axis), + xAxisRange: _xAxis.axisRange, + scaleX: scaleX, + scaleY: scaleY, + xOrigin: viewPortHandler.scaleX, + yOrigin: viewPortHandler.scaleY, + zoomCenterX: CGFloat(xValue), + zoomCenterY: CGFloat(yValue), + zoomOriginX: origin.x, + zoomOriginY: origin.y, + duration: duration, + easing: easing) + + addViewportJob(job) + } + + /// Zooms by the specified scale factor to the specified values on the specified axis. + /// + /// - Parameters: + /// - scaleX: + /// - scaleY: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func zoomAndCenterViewAnimated( + scaleX: CGFloat, + scaleY: CGFloat, + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval, + easingOption: ChartEasingOption) + { + zoomAndCenterViewAnimated(scaleX: scaleX, scaleY: scaleY, xValue: xValue, yValue: yValue, axis: axis, duration: duration, easing: easingFunctionFromOption(easingOption)) + } + + /// Zooms by the specified scale factor to the specified values on the specified axis. + /// + /// - Parameters: + /// - scaleX: + /// - scaleY: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func zoomAndCenterViewAnimated( + scaleX: CGFloat, + scaleY: CGFloat, + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval) + { + zoomAndCenterViewAnimated(scaleX: scaleX, scaleY: scaleY, xValue: xValue, yValue: yValue, axis: axis, duration: duration, easingOption: .easeInOutSine) + } + + /// Resets all zooming and dragging and makes the chart fit exactly it's bounds. + @objc open func fitScreen() + { + let matrix = _viewPortHandler.fitScreen() + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) + + calculateOffsets() + setNeedsDisplay() + } + + /// Sets the minimum scale value to which can be zoomed out. 1 = fitScreen + @objc open func setScaleMinima(_ scaleX: CGFloat, scaleY: CGFloat) + { + _viewPortHandler.setMinimumScaleX(scaleX) + _viewPortHandler.setMinimumScaleY(scaleY) + } + + @objc open var visibleXRange: Double + { + return abs(highestVisibleX - lowestVisibleX) + } + + /// Sets the size of the area (range on the x-axis) that should be maximum visible at once (no further zooming out allowed). + /// + /// If this is e.g. set to 10, no more than a range of 10 values on the x-axis can be viewed at once without scrolling. + /// + /// If you call this method, chart must have data or it has no effect. + @objc open func setVisibleXRangeMaximum(_ maxXRange: Double) + { + let xScale = _xAxis.axisRange / maxXRange + _viewPortHandler.setMinimumScaleX(CGFloat(xScale)) + } + + /// Sets the size of the area (range on the x-axis) that should be minimum visible at once (no further zooming in allowed). + /// + /// If this is e.g. set to 10, no less than a range of 10 values on the x-axis can be viewed at once without scrolling. + /// + /// If you call this method, chart must have data or it has no effect. + @objc open func setVisibleXRangeMinimum(_ minXRange: Double) + { + let xScale = _xAxis.axisRange / minXRange + _viewPortHandler.setMaximumScaleX(CGFloat(xScale)) + } + + /// Limits the maximum and minimum value count that can be visible by pinching and zooming. + /// + /// e.g. minRange=10, maxRange=100 no less than 10 values and no more that 100 values can be viewed + /// at once without scrolling. + /// + /// If you call this method, chart must have data or it has no effect. + @objc open func setVisibleXRange(minXRange: Double, maxXRange: Double) + { + let minScale = _xAxis.axisRange / maxXRange + let maxScale = _xAxis.axisRange / minXRange + _viewPortHandler.setMinMaxScaleX( + minScaleX: CGFloat(minScale), + maxScaleX: CGFloat(maxScale)) + } + + /// Sets the size of the area (range on the y-axis) that should be maximum visible at once. + /// + /// - Parameters: + /// - yRange: + /// - axis: - the axis for which this limit should apply + @objc open func setVisibleYRangeMaximum(_ maxYRange: Double, axis: YAxis.AxisDependency) + { + let yScale = getAxisRange(axis: axis) / maxYRange + _viewPortHandler.setMinimumScaleY(CGFloat(yScale)) + } + + /// Sets the size of the area (range on the y-axis) that should be minimum visible at once, no further zooming in possible. + /// + /// - Parameters: + /// - yRange: + /// - axis: - the axis for which this limit should apply + @objc open func setVisibleYRangeMinimum(_ minYRange: Double, axis: YAxis.AxisDependency) + { + let yScale = getAxisRange(axis: axis) / minYRange + _viewPortHandler.setMaximumScaleY(CGFloat(yScale)) + } + + /// Limits the maximum and minimum y range that can be visible by pinching and zooming. + /// + /// - Parameters: + /// - minYRange: + /// - maxYRange: + /// - axis: + @objc open func setVisibleYRange(minYRange: Double, maxYRange: Double, axis: YAxis.AxisDependency) + { + let minScale = getAxisRange(axis: axis) / minYRange + let maxScale = getAxisRange(axis: axis) / maxYRange + _viewPortHandler.setMinMaxScaleY(minScaleY: CGFloat(minScale), maxScaleY: CGFloat(maxScale)) + } + + /// Moves the left side of the current viewport to the specified x-value. + /// This also refreshes the chart by calling setNeedsDisplay(). + @objc open func moveViewToX(_ xValue: Double) + { + let job = MoveViewJob( + viewPortHandler: viewPortHandler, + xValue: xValue, + yValue: 0.0, + transformer: getTransformer(forAxis: .left), + view: self) + + addViewportJob(job) + } + + /// Centers the viewport to the specified y-value on the y-axis. + /// This also refreshes the chart by calling setNeedsDisplay(). + /// + /// - Parameters: + /// - yValue: + /// - axis: - which axis should be used as a reference for the y-axis + @objc open func moveViewToY(_ yValue: Double, axis: YAxis.AxisDependency) + { + let yInView = getAxisRange(axis: axis) / Double(_viewPortHandler.scaleY) + + let job = MoveViewJob( + viewPortHandler: viewPortHandler, + xValue: 0.0, + yValue: yValue + yInView / 2.0, + transformer: getTransformer(forAxis: axis), + view: self) + + addViewportJob(job) + } + + /// This will move the left side of the current viewport to the specified x-value on the x-axis, and center the viewport to the specified y-value on the y-axis. + /// This also refreshes the chart by calling setNeedsDisplay(). + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: - which axis should be used as a reference for the y-axis + @objc open func moveViewTo(xValue: Double, yValue: Double, axis: YAxis.AxisDependency) + { + let yInView = getAxisRange(axis: axis) / Double(_viewPortHandler.scaleY) + + let job = MoveViewJob( + viewPortHandler: viewPortHandler, + xValue: xValue, + yValue: yValue + yInView / 2.0, + transformer: getTransformer(forAxis: axis), + view: self) + + addViewportJob(job) + } + + /// This will move the left side of the current viewport to the specified x-position and center the viewport to the specified y-position animated. + /// This also refreshes the chart by calling setNeedsDisplay(). + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func moveViewToAnimated( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval, + easing: ChartEasingFunctionBlock?) + { + let bounds = valueForTouchPoint( + point: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop), + axis: axis) + + let yInView = getAxisRange(axis: axis) / Double(_viewPortHandler.scaleY) + + let job = AnimatedMoveViewJob( + viewPortHandler: viewPortHandler, + xValue: xValue, + yValue: yValue + yInView / 2.0, + transformer: getTransformer(forAxis: axis), + view: self, + xOrigin: bounds.x, + yOrigin: bounds.y, + duration: duration, + easing: easing) + + addViewportJob(job) + } + + /// This will move the left side of the current viewport to the specified x-position and center the viewport to the specified y-position animated. + /// This also refreshes the chart by calling setNeedsDisplay(). + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func moveViewToAnimated( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval, + easingOption: ChartEasingOption) + { + moveViewToAnimated(xValue: xValue, yValue: yValue, axis: axis, duration: duration, easing: easingFunctionFromOption(easingOption)) + } + + /// This will move the left side of the current viewport to the specified x-position and center the viewport to the specified y-position animated. + /// This also refreshes the chart by calling setNeedsDisplay(). + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func moveViewToAnimated( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval) + { + moveViewToAnimated(xValue: xValue, yValue: yValue, axis: axis, duration: duration, easingOption: .easeInOutSine) + } + + /// This will move the center of the current viewport to the specified x-value and y-value. + /// This also refreshes the chart by calling setNeedsDisplay(). + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: - which axis should be used as a reference for the y-axis + @objc open func centerViewTo( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency) + { + let yInView = getAxisRange(axis: axis) / Double(_viewPortHandler.scaleY) + let xInView = xAxis.axisRange / Double(_viewPortHandler.scaleX) + + let job = MoveViewJob( + viewPortHandler: viewPortHandler, + xValue: xValue - xInView / 2.0, + yValue: yValue + yInView / 2.0, + transformer: getTransformer(forAxis: axis), + view: self) + + addViewportJob(job) + } + + /// This will move the center of the current viewport to the specified x-value and y-value animated. + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func centerViewToAnimated( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval, + easing: ChartEasingFunctionBlock?) + { + let bounds = valueForTouchPoint( + point: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop), + axis: axis) + + let yInView = getAxisRange(axis: axis) / Double(_viewPortHandler.scaleY) + let xInView = xAxis.axisRange / Double(_viewPortHandler.scaleX) + + let job = AnimatedMoveViewJob( + viewPortHandler: viewPortHandler, + xValue: xValue - xInView / 2.0, + yValue: yValue + yInView / 2.0, + transformer: getTransformer(forAxis: axis), + view: self, + xOrigin: bounds.x, + yOrigin: bounds.y, + duration: duration, + easing: easing) + + addViewportJob(job) + } + + /// This will move the center of the current viewport to the specified x-value and y-value animated. + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func centerViewToAnimated( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval, + easingOption: ChartEasingOption) + { + centerViewToAnimated(xValue: xValue, yValue: yValue, axis: axis, duration: duration, easing: easingFunctionFromOption(easingOption)) + } + + /// This will move the center of the current viewport to the specified x-value and y-value animated. + /// + /// - Parameters: + /// - xValue: + /// - yValue: + /// - axis: which axis should be used as a reference for the y-axis + /// - duration: the duration of the animation in seconds + /// - easing: + @objc open func centerViewToAnimated( + xValue: Double, + yValue: Double, + axis: YAxis.AxisDependency, + duration: TimeInterval) + { + centerViewToAnimated(xValue: xValue, yValue: yValue, axis: axis, duration: duration, easingOption: .easeInOutSine) + } + + /// Sets custom offsets for the current `ChartViewPort` (the offsets on the sides of the actual chart window). Setting this will prevent the chart from automatically calculating it's offsets. Use `resetViewPortOffsets()` to undo this. + /// ONLY USE THIS WHEN YOU KNOW WHAT YOU ARE DOING, else use `setExtraOffsets(...)`. + @objc open func setViewPortOffsets(left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat) + { + _customViewPortEnabled = true + + if Thread.isMainThread + { + self._viewPortHandler.restrainViewPort(offsetLeft: left, offsetTop: top, offsetRight: right, offsetBottom: bottom) + prepareOffsetMatrix() + prepareValuePxMatrix() + } + else + { + DispatchQueue.main.async(execute: { + self.setViewPortOffsets(left: left, top: top, right: right, bottom: bottom) + }) + } + } + + /// Resets all custom offsets set via `setViewPortOffsets(...)` method. Allows the chart to again calculate all offsets automatically. + @objc open func resetViewPortOffsets() + { + _customViewPortEnabled = false + calculateOffsets() + } + + // MARK: - Accessors + + /// - Returns: The range of the specified axis. + @objc open func getAxisRange(axis: YAxis.AxisDependency) -> Double + { + if axis == .left + { + return leftAxis.axisRange + } + else + { + return rightAxis.axisRange + } + } + + /// - Returns: The position (in pixels) the provided Entry has inside the chart view + @objc open func getPosition(entry e: ChartDataEntry, axis: YAxis.AxisDependency) -> CGPoint + { + var vals = CGPoint(x: CGFloat(e.x), y: CGFloat(e.y)) + + getTransformer(forAxis: axis).pointValueToPixel(&vals) + + return vals + } + + /// is dragging enabled? (moving the chart with the finger) for the chart (this does not affect scaling). + @objc open var dragEnabled: Bool + { + get + { + return _dragXEnabled || _dragYEnabled + } + set + { + _dragYEnabled = newValue + _dragXEnabled = newValue + } + } + + /// is dragging enabled? (moving the chart with the finger) for the chart (this does not affect scaling). + @objc open var isDragEnabled: Bool + { + return dragEnabled + } + + /// is dragging on the X axis enabled? + @objc open var dragXEnabled: Bool + { + get + { + return _dragXEnabled + } + set + { + _dragXEnabled = newValue + } + } + + /// is dragging on the Y axis enabled? + @objc open var dragYEnabled: Bool + { + get + { + return _dragYEnabled + } + set + { + _dragYEnabled = newValue + } + } + + /// is scaling enabled? (zooming in and out by gesture) for the chart (this does not affect dragging). + @objc open func setScaleEnabled(_ enabled: Bool) + { + if _scaleXEnabled != enabled || _scaleYEnabled != enabled + { + _scaleXEnabled = enabled + _scaleYEnabled = enabled + #if !os(tvOS) + _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled + #endif + } + } + + @objc open var scaleXEnabled: Bool + { + get + { + return _scaleXEnabled + } + set + { + if _scaleXEnabled != newValue + { + _scaleXEnabled = newValue + #if !os(tvOS) + _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled + #endif + } + } + } + + @objc open var scaleYEnabled: Bool + { + get + { + return _scaleYEnabled + } + set + { + if _scaleYEnabled != newValue + { + _scaleYEnabled = newValue + #if !os(tvOS) + _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled + #endif + } + } + } + + @objc open var isScaleXEnabled: Bool { return scaleXEnabled } + @objc open var isScaleYEnabled: Bool { return scaleYEnabled } + + /// flag that indicates if double tap zoom is enabled or not + @objc open var doubleTapToZoomEnabled: Bool + { + get + { + return _doubleTapToZoomEnabled + } + set + { + if _doubleTapToZoomEnabled != newValue + { + _doubleTapToZoomEnabled = newValue + _doubleTapGestureRecognizer.isEnabled = _doubleTapToZoomEnabled + } + } + } + + /// **default**: true + /// `true` if zooming via double-tap is enabled `false` ifnot. + @objc open var isDoubleTapToZoomEnabled: Bool + { + return doubleTapToZoomEnabled + } + + /// flag that indicates if highlighting per dragging over a fully zoomed out chart is enabled + @objc open var highlightPerDragEnabled = true + + /// If set to true, highlighting per dragging over a fully zoomed out chart is enabled + /// You might want to disable this when using inside a `NSUIScrollView` + /// + /// **default**: true + @objc open var isHighlightPerDragEnabled: Bool + { + return highlightPerDragEnabled + } + + /// **default**: true + /// `true` if drawing the grid background is enabled, `false` ifnot. + @objc open var isDrawGridBackgroundEnabled: Bool + { + return drawGridBackgroundEnabled + } + + /// **default**: false + /// `true` if drawing the borders rectangle is enabled, `false` ifnot. + @objc open var isDrawBordersEnabled: Bool + { + return drawBordersEnabled + } + + /// - Returns: The x and y values in the chart at the given touch point + /// (encapsulated in a `CGPoint`). This method transforms pixel coordinates to + /// coordinates / values in the chart. This is the opposite method to + /// `getPixelsForValues(...)`. + @objc open func valueForTouchPoint(point pt: CGPoint, axis: YAxis.AxisDependency) -> CGPoint + { + return getTransformer(forAxis: axis).valueForTouchPoint(pt) + } + + /// Transforms the given chart values into pixels. This is the opposite + /// method to `valueForTouchPoint(...)`. + @objc open func pixelForValues(x: Double, y: Double, axis: YAxis.AxisDependency) -> CGPoint + { + return getTransformer(forAxis: axis).pixelForValues(x: x, y: y) + } + + /// - Returns: The Entry object displayed at the touched position of the chart + @objc open func getEntryByTouchPoint(point pt: CGPoint) -> ChartDataEntry! + { + if let h = getHighlightByTouchPoint(pt) + { + return _data!.entryForHighlight(h) + } + return nil + } + + /// - Returns: The DataSet object displayed at the touched position of the chart + @objc open func getDataSetByTouchPoint(point pt: CGPoint) -> IBarLineScatterCandleBubbleChartDataSet? + { + let h = getHighlightByTouchPoint(pt) + if h !== nil + { + return _data?.getDataSetByIndex(h!.dataSetIndex) as? IBarLineScatterCandleBubbleChartDataSet + } + return nil + } + + /// The current x-scale factor + @objc open var scaleX: CGFloat + { + if _viewPortHandler === nil + { + return 1.0 + } + return _viewPortHandler.scaleX + } + + /// The current y-scale factor + @objc open var scaleY: CGFloat + { + if _viewPortHandler === nil + { + return 1.0 + } + return _viewPortHandler.scaleY + } + + /// if the chart is fully zoomed out, return true + @objc open var isFullyZoomedOut: Bool { return _viewPortHandler.isFullyZoomedOut } + + /// - Returns: The y-axis object to the corresponding AxisDependency. In the + /// horizontal bar-chart, LEFT == top, RIGHT == BOTTOM + @objc open func getAxis(_ axis: YAxis.AxisDependency) -> YAxis + { + if axis == .left + { + return leftAxis + } + else + { + return rightAxis + } + } + + /// flag that indicates if pinch-zoom is enabled. if true, both x and y axis can be scaled simultaneously with 2 fingers, if false, x and y axis can be scaled separately + @objc open var pinchZoomEnabled: Bool + { + get + { + return _pinchZoomEnabled + } + set + { + if _pinchZoomEnabled != newValue + { + _pinchZoomEnabled = newValue + #if !os(tvOS) + _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled + #endif + } + } + } + + /// **default**: false + /// `true` if pinch-zoom is enabled, `false` ifnot + @objc open var isPinchZoomEnabled: Bool { return pinchZoomEnabled } + + /// Set an offset in dp that allows the user to drag the chart over it's + /// bounds on the x-axis. + @objc open func setDragOffsetX(_ offset: CGFloat) + { + _viewPortHandler.setDragOffsetX(offset) + } + + /// Set an offset in dp that allows the user to drag the chart over it's + /// bounds on the y-axis. + @objc open func setDragOffsetY(_ offset: CGFloat) + { + _viewPortHandler.setDragOffsetY(offset) + } + + /// `true` if both drag offsets (x and y) are zero or smaller. + @objc open var hasNoDragOffset: Bool { return _viewPortHandler.hasNoDragOffset } + + open override var chartYMax: Double + { + return max(leftAxis._axisMaximum, rightAxis._axisMaximum) + } + + open override var chartYMin: Double + { + return min(leftAxis._axisMinimum, rightAxis._axisMinimum) + } + + /// `true` if either the left or the right or both axes are inverted. + @objc open var isAnyAxisInverted: Bool + { + return leftAxis.isInverted || rightAxis.isInverted + } + + /// flag that indicates if auto scaling on the y axis is enabled. + /// if yes, the y axis automatically adjusts to the min and max y values of the current x axis range whenever the viewport changes + @objc open var autoScaleMinMaxEnabled: Bool + { + get { return _autoScaleMinMaxEnabled } + set { _autoScaleMinMaxEnabled = newValue } + } + + /// **default**: false + /// `true` if auto scaling on the y axis is enabled. + @objc open var isAutoScaleMinMaxEnabled : Bool { return autoScaleMinMaxEnabled } + + /// Sets a minimum width to the specified y axis. + @objc open func setYAxisMinWidth(_ axis: YAxis.AxisDependency, width: CGFloat) + { + if axis == .left + { + leftAxis.minWidth = width + } + else + { + rightAxis.minWidth = width + } + } + + /// **default**: 0.0 + /// + /// - Returns: The (custom) minimum width of the specified Y axis. + @objc open func getYAxisMinWidth(_ axis: YAxis.AxisDependency) -> CGFloat + { + if axis == .left + { + return leftAxis.minWidth + } + else + { + return rightAxis.minWidth + } + } + /// Sets a maximum width to the specified y axis. + /// Zero (0.0) means there's no maximum width + @objc open func setYAxisMaxWidth(_ axis: YAxis.AxisDependency, width: CGFloat) + { + if axis == .left + { + leftAxis.maxWidth = width + } + else + { + rightAxis.maxWidth = width + } + } + + /// Zero (0.0) means there's no maximum width + /// + /// **default**: 0.0 (no maximum specified) + /// + /// - Returns: The (custom) maximum width of the specified Y axis. + @objc open func getYAxisMaxWidth(_ axis: YAxis.AxisDependency) -> CGFloat + { + if axis == .left + { + return leftAxis.maxWidth + } + else + { + return rightAxis.maxWidth + } + } + + /// - Returns the width of the specified y axis. + @objc open func getYAxisWidth(_ axis: YAxis.AxisDependency) -> CGFloat + { + if axis == .left + { + return leftAxis.requiredSize().width + } + else + { + return rightAxis.requiredSize().width + } + } + + // MARK: - BarLineScatterCandleBubbleChartDataProvider + + /// - Returns: The Transformer class that contains all matrices and is + /// responsible for transforming values into pixels on the screen and + /// backwards. + open func getTransformer(forAxis axis: YAxis.AxisDependency) -> Transformer + { + if axis == .left + { + return _leftAxisTransformer + } + else + { + return _rightAxisTransformer + } + } + + /// the number of maximum visible drawn values on the chart only active when `drawValuesEnabled` is enabled + open override var maxVisibleCount: Int + { + get + { + return _maxVisibleCount + } + set + { + _maxVisibleCount = newValue + } + } + + open func isInverted(axis: YAxis.AxisDependency) -> Bool + { + return getAxis(axis).isInverted + } + + /// The lowest x-index (value on the x-axis) that is still visible on he chart. + open var lowestVisibleX: Double + { + var pt = CGPoint( + x: viewPortHandler.contentLeft, + y: viewPortHandler.contentBottom) + + getTransformer(forAxis: .left).pixelToValues(&pt) + + return max(xAxis._axisMinimum, Double(pt.x)) + } + + /// The highest x-index (value on the x-axis) that is still visible on the chart. + open var highestVisibleX: Double + { + var pt = CGPoint( + x: viewPortHandler.contentRight, + y: viewPortHandler.contentBottom) + + getTransformer(forAxis: .left).pixelToValues(&pt) + + return min(xAxis._axisMaximum, Double(pt.x)) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Charts/BubbleChartView.swift b/dydx/Pods/Charts/Source/Charts/Charts/BubbleChartView.swift new file mode 100644 index 000000000..1b1367756 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Charts/BubbleChartView.swift @@ -0,0 +1,27 @@ +// +// BubbleChartView.swift +// Charts +// +// Bubble chart implementation: +// Copyright 2015 Pierre-Marc Airoldi +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class BubbleChartView: BarLineChartViewBase, BubbleChartDataProvider +{ + open override func initialize() + { + super.initialize() + + renderer = BubbleChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler) + } + + // MARK: - BubbleChartDataProvider + + open var bubbleData: BubbleChartData? { return _data as? BubbleChartData } +} diff --git a/dydx/Pods/Charts/Source/Charts/Charts/CandleStickChartView.swift b/dydx/Pods/Charts/Source/Charts/Charts/CandleStickChartView.swift new file mode 100644 index 000000000..0366e8b87 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Charts/CandleStickChartView.swift @@ -0,0 +1,34 @@ +// +// CandleStickChartView.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +/// Financial chart type that draws candle-sticks. +open class CandleStickChartView: BarLineChartViewBase, CandleChartDataProvider +{ + internal override func initialize() + { + super.initialize() + + renderer = CandleStickChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler) + + self.xAxis.spaceMin = 0.5 + self.xAxis.spaceMax = 0.5 + } + + // MARK: - CandleChartDataProvider + + open var candleData: CandleChartData? + { + return _data as? CandleChartData + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Charts/ChartViewBase.swift b/dydx/Pods/Charts/Source/Charts/Charts/ChartViewBase.swift new file mode 100644 index 000000000..aeec25e91 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Charts/ChartViewBase.swift @@ -0,0 +1,1051 @@ +// +// ChartViewBase.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// +// Based on https://github.com/PhilJay/MPAndroidChart/commit/c42b880 + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + +@objc +public protocol ChartViewDelegate +{ + /// Called when a value has been selected inside the chart. + /// + /// - Parameters: + /// - entry: The selected Entry. + /// - highlight: The corresponding highlight object that contains information about the highlighted position such as dataSetIndex etc. + @objc optional func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) + + /// Called when a user stops panning between values on the chart + @objc optional func chartViewDidEndPanning(_ chartView: ChartViewBase) + + // Called when nothing has been selected or an "un-select" has been made. + @objc optional func chartValueNothingSelected(_ chartView: ChartViewBase) + + // Callbacks when the chart is scaled / zoomed via pinch zoom gesture. + @objc optional func chartScaled(_ chartView: ChartViewBase, scaleX: CGFloat, scaleY: CGFloat) + + // Callbacks when the chart is moved / translated via drag gesture. + @objc optional func chartTranslated(_ chartView: ChartViewBase, dX: CGFloat, dY: CGFloat) + + // Callbacks when Animator stops animating + @objc optional func chartView(_ chartView: ChartViewBase, animatorDidStop animator: Animator) +} + +open class ChartViewBase: NSUIView, ChartDataProvider, AnimatorDelegate +{ + // MARK: - Properties + + /// - Returns: The object representing all x-labels, this method can be used to + /// acquire the XAxis object and modify it (e.g. change the position of the + /// labels) + @objc open var xAxis: XAxis + { + return _xAxis + } + + /// The default IValueFormatter that has been determined by the chart considering the provided minimum and maximum values. + internal var _defaultValueFormatter: IValueFormatter? = DefaultValueFormatter(decimals: 0) + + /// object that holds all data that was originally set for the chart, before it was modified or any filtering algorithms had been applied + internal var _data: ChartData? + + /// Flag that indicates if highlighting per tap (touch) is enabled + private var _highlightPerTapEnabled = true + + /// If set to true, chart continues to scroll after touch up + @objc open var dragDecelerationEnabled = true + + /// Deceleration friction coefficient in [0 ; 1] interval, higher values indicate that speed will decrease slowly, for example if it set to 0, it will stop immediately. + /// 1 is an invalid value, and will be converted to 0.999 automatically. + private var _dragDecelerationFrictionCoef: CGFloat = 0.9 + + /// if true, units are drawn next to the values in the chart + internal var _drawUnitInChart = false + + /// The object representing the labels on the x-axis + internal var _xAxis: XAxis! + + /// The `Description` object of the chart. + /// This should have been called just "description", but + @objc open var chartDescription: Description? + + /// The legend object containing all data associated with the legend + internal var _legend: Legend! + + /// delegate to receive chart events + @objc open weak var delegate: ChartViewDelegate? + + /// text that is displayed when the chart is empty + @objc open var noDataText = "No chart data available." + + /// Font to be used for the no data text. + @objc open var noDataFont = NSUIFont.systemFont(ofSize: 12) + + /// color of the no data text + @objc open var noDataTextColor: NSUIColor = .labelOrBlack + + /// alignment of the no data text + @objc open var noDataTextAlignment: NSTextAlignment = .left + + internal var _legendRenderer: LegendRenderer! + + /// object responsible for rendering the data + @objc open var renderer: DataRenderer? + + @objc open var highlighter: IHighlighter? + + /// object that manages the bounds and drawing constraints of the chart + internal var _viewPortHandler: ViewPortHandler! + + /// object responsible for animations + internal var _animator: Animator! + + /// flag that indicates if offsets calculation has already been done or not + private var _offsetsCalculated = false + + /// array of Highlight objects that reference the highlighted slices in the chart + internal var _indicesToHighlight = [Highlight]() + + /// `true` if drawing the marker is enabled when tapping on values + /// (use the `marker` property to specify a marker) + @objc open var drawMarkers = true + + /// - Returns: `true` if drawing the marker is enabled when tapping on values + /// (use the `marker` property to specify a marker) + @objc open var isDrawMarkersEnabled: Bool { return drawMarkers } + + /// The marker that is displayed when a value is clicked on the chart + @objc open var marker: IMarker? + + private var _interceptTouchEvents = false + + /// An extra offset to be appended to the viewport's top + @objc open var extraTopOffset: CGFloat = 0.0 + + /// An extra offset to be appended to the viewport's right + @objc open var extraRightOffset: CGFloat = 0.0 + + /// An extra offset to be appended to the viewport's bottom + @objc open var extraBottomOffset: CGFloat = 0.0 + + /// An extra offset to be appended to the viewport's left + @objc open var extraLeftOffset: CGFloat = 0.0 + + @objc open func setExtraOffsets(left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat) + { + extraLeftOffset = left + extraTopOffset = top + extraRightOffset = right + extraBottomOffset = bottom + } + + // MARK: - Initializers + + public override init(frame: CGRect) + { + super.init(frame: frame) + initialize() + } + + public required init?(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder) + initialize() + } + + deinit + { + self.removeObserver(self, forKeyPath: "bounds") + self.removeObserver(self, forKeyPath: "frame") + } + + internal func initialize() + { + #if os(iOS) + self.backgroundColor = NSUIColor.clear + #endif + + _animator = Animator() + _animator.delegate = self + + _viewPortHandler = ViewPortHandler(width: bounds.size.width, height: bounds.size.height) + + chartDescription = Description() + + _legend = Legend() + _legendRenderer = LegendRenderer(viewPortHandler: _viewPortHandler, legend: _legend) + + _xAxis = XAxis() + + self.addObserver(self, forKeyPath: "bounds", options: .new, context: nil) + self.addObserver(self, forKeyPath: "frame", options: .new, context: nil) + } + + // MARK: - ChartViewBase + + /// The data for the chart + open var data: ChartData? + { + get + { + return _data + } + set + { + _data = newValue + _offsetsCalculated = false + + guard let _data = _data else + { + setNeedsDisplay() + return + } + + // calculate how many digits are needed + setupDefaultFormatter(min: _data.getYMin(), max: _data.getYMax()) + + for set in _data.dataSets + { + if set.needsFormatter || set.valueFormatter === _defaultValueFormatter + { + set.valueFormatter = _defaultValueFormatter + } + } + + // let the chart know there is new data + notifyDataSetChanged() + } + } + + /// Clears the chart from all data (sets it to null) and refreshes it (by calling setNeedsDisplay()). + @objc open func clear() + { + _data = nil + _offsetsCalculated = false + _indicesToHighlight.removeAll() + lastHighlighted = nil + + setNeedsDisplay() + } + + /// Removes all DataSets (and thereby Entries) from the chart. Does not set the data object to nil. Also refreshes the chart by calling setNeedsDisplay(). + @objc open func clearValues() + { + _data?.clearValues() + setNeedsDisplay() + } + + /// - Returns: `true` if the chart is empty (meaning it's data object is either null or contains no entries). + @objc open func isEmpty() -> Bool + { + guard let data = _data else { return true } + + if data.entryCount <= 0 + { + return true + } + else + { + return false + } + } + + /// Lets the chart know its underlying data has changed and should perform all necessary recalculations. + /// It is crucial that this method is called everytime data is changed dynamically. Not calling this method can lead to crashes or unexpected behaviour. + @objc open func notifyDataSetChanged() + { + fatalError("notifyDataSetChanged() cannot be called on ChartViewBase") + } + + /// Calculates the offsets of the chart to the border depending on the position of an eventual legend or depending on the length of the y-axis and x-axis labels and their position + internal func calculateOffsets() + { + fatalError("calculateOffsets() cannot be called on ChartViewBase") + } + + /// calcualtes the y-min and y-max value and the y-delta and x-delta value + internal func calcMinMax() + { + fatalError("calcMinMax() cannot be called on ChartViewBase") + } + + /// calculates the required number of digits for the values that might be drawn in the chart (if enabled), and creates the default value formatter + internal func setupDefaultFormatter(min: Double, max: Double) + { + // check if a custom formatter is set or not + var reference = Double(0.0) + + if let data = _data , data.entryCount >= 2 + { + reference = fabs(max - min) + } + else + { + let absMin = fabs(min) + let absMax = fabs(max) + reference = absMin > absMax ? absMin : absMax + } + + + if _defaultValueFormatter is DefaultValueFormatter + { + // setup the formatter with a new number of digits + let digits = reference.decimalPlaces + + (_defaultValueFormatter as? DefaultValueFormatter)?.decimals + = digits + } + } + + open override func draw(_ rect: CGRect) + { + let optionalContext = NSUIGraphicsGetCurrentContext() + guard let context = optionalContext else { return } + + let frame = self.bounds + + if _data === nil && noDataText.count > 0 + { + context.saveGState() + defer { context.restoreGState() } + + let paragraphStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + paragraphStyle.minimumLineHeight = noDataFont.lineHeight + paragraphStyle.lineBreakMode = .byWordWrapping + paragraphStyle.alignment = noDataTextAlignment + + ChartUtils.drawMultilineText( + context: context, + text: noDataText, + point: CGPoint(x: frame.width / 2.0, y: frame.height / 2.0), + attributes: + [.font: noDataFont, + .foregroundColor: noDataTextColor, + .paragraphStyle: paragraphStyle], + constrainedToSize: self.bounds.size, + anchor: CGPoint(x: 0.5, y: 0.5), + angleRadians: 0.0) + + return + } + + if !_offsetsCalculated + { + calculateOffsets() + _offsetsCalculated = true + } + } + + /// Draws the description text in the bottom right corner of the chart (per default) + internal func drawDescription(context: CGContext) + { + // check if description should be drawn + guard + let description = chartDescription, + description.isEnabled, + let descriptionText = description.text, + descriptionText.count > 0 + else { return } + + let position = description.position ?? CGPoint(x: bounds.width - _viewPortHandler.offsetRight - description.xOffset, + y: bounds.height - _viewPortHandler.offsetBottom - description.yOffset - description.font.lineHeight) + + var attrs = [NSAttributedString.Key : Any]() + + attrs[NSAttributedString.Key.font] = description.font + attrs[NSAttributedString.Key.foregroundColor] = description.textColor + + ChartUtils.drawText( + context: context, + text: descriptionText, + point: position, + align: description.textAlign, + attributes: attrs) + } + + // MARK: - Accessibility + + open override func accessibilityChildren() -> [Any]? { + return renderer?.accessibleChartElements + } + + // MARK: - Highlighting + + /// The array of currently highlighted values. This might an empty if nothing is highlighted. + @objc open var highlighted: [Highlight] + { + return _indicesToHighlight + } + + /// Set this to false to prevent values from being highlighted by tap gesture. + /// Values can still be highlighted via drag or programmatically. + /// **default**: true + @objc open var highlightPerTapEnabled: Bool + { + get { return _highlightPerTapEnabled } + set { _highlightPerTapEnabled = newValue } + } + + /// `true` if values can be highlighted via tap gesture, `false` ifnot. + @objc open var isHighLightPerTapEnabled: Bool + { + return highlightPerTapEnabled + } + + /// Checks if the highlight array is null, has a length of zero or if the first object is null. + /// + /// - Returns: `true` if there are values to highlight, `false` ifthere are no values to highlight. + @objc open func valuesToHighlight() -> Bool + { + return !_indicesToHighlight.isEmpty + } + + /// Highlights the values at the given indices in the given DataSets. Provide + /// null or an empty array to undo all highlighting. + /// This should be used to programmatically highlight values. + /// This method *will not* call the delegate. + @objc open func highlightValues(_ highs: [Highlight]?) + { + // set the indices to highlight + _indicesToHighlight = highs ?? [Highlight]() + + if _indicesToHighlight.isEmpty + { + self.lastHighlighted = nil + } + else + { + self.lastHighlighted = _indicesToHighlight[0] + } + + // redraw the chart + setNeedsDisplay() + } + + /// Highlights any y-value at the given x-value in the given DataSet. + /// Provide -1 as the dataSetIndex to undo all highlighting. + /// This method will call the delegate. + /// + /// - Parameters: + /// - x: The x-value to highlight + /// - dataSetIndex: The dataset index to search in + /// - dataIndex: The data index to search in (only used in CombinedChartView currently) + @objc open func highlightValue(x: Double, dataSetIndex: Int, dataIndex: Int = -1) + { + highlightValue(x: x, dataSetIndex: dataSetIndex, dataIndex: dataIndex, callDelegate: true) + } + + /// Highlights the value at the given x-value and y-value in the given DataSet. + /// Provide -1 as the dataSetIndex to undo all highlighting. + /// This method will call the delegate. + /// + /// - Parameters: + /// - x: The x-value to highlight + /// - y: The y-value to highlight. Supply `NaN` for "any" + /// - dataSetIndex: The dataset index to search in + /// - dataIndex: The data index to search in (only used in CombinedChartView currently) + @objc open func highlightValue(x: Double, y: Double, dataSetIndex: Int, dataIndex: Int = -1) + { + highlightValue(x: x, y: y, dataSetIndex: dataSetIndex, dataIndex: dataIndex, callDelegate: true) + } + + /// Highlights any y-value at the given x-value in the given DataSet. + /// Provide -1 as the dataSetIndex to undo all highlighting. + /// + /// - Parameters: + /// - x: The x-value to highlight + /// - dataSetIndex: The dataset index to search in + /// - dataIndex: The data index to search in (only used in CombinedChartView currently) + /// - callDelegate: Should the delegate be called for this change + @objc open func highlightValue(x: Double, dataSetIndex: Int, dataIndex: Int = -1, callDelegate: Bool) + { + highlightValue(x: x, y: .nan, dataSetIndex: dataSetIndex, dataIndex: dataIndex, callDelegate: callDelegate) + } + + /// Highlights the value at the given x-value and y-value in the given DataSet. + /// Provide -1 as the dataSetIndex to undo all highlighting. + /// + /// - Parameters: + /// - x: The x-value to highlight + /// - y: The y-value to highlight. Supply `NaN` for "any" + /// - dataSetIndex: The dataset index to search in + /// - dataIndex: The data index to search in (only used in CombinedChartView currently) + /// - callDelegate: Should the delegate be called for this change + @objc open func highlightValue(x: Double, y: Double, dataSetIndex: Int, dataIndex: Int = -1, callDelegate: Bool) + { + guard let data = _data else + { + Swift.print("Value not highlighted because data is nil") + return + } + + if dataSetIndex < 0 || dataSetIndex >= data.dataSetCount + { + highlightValue(nil, callDelegate: callDelegate) + } + else + { + highlightValue(Highlight(x: x, y: y, dataSetIndex: dataSetIndex, dataIndex: dataIndex), callDelegate: callDelegate) + } + } + + /// Highlights the values represented by the provided Highlight object + /// This method *will not* call the delegate. + /// + /// - Parameters: + /// - highlight: contains information about which entry should be highlighted + @objc open func highlightValue(_ highlight: Highlight?) + { + highlightValue(highlight, callDelegate: false) + } + + /// Highlights the value selected by touch gesture. + @objc open func highlightValue(_ highlight: Highlight?, callDelegate: Bool) + { + var entry: ChartDataEntry? + var h = highlight + + if h == nil + { + self.lastHighlighted = nil + _indicesToHighlight.removeAll(keepingCapacity: false) + } + else + { + // set the indices to highlight + entry = _data?.entryForHighlight(h!) + if entry == nil + { + h = nil + _indicesToHighlight.removeAll(keepingCapacity: false) + } + else + { + _indicesToHighlight = [h!] + } + } + + if callDelegate, let delegate = delegate + { + if let h = h + { + // notify the listener + delegate.chartValueSelected?(self, entry: entry!, highlight: h) + } + else + { + delegate.chartValueNothingSelected?(self) + } + } + + // redraw the chart + setNeedsDisplay() + } + + /// - Returns: The Highlight object (contains x-index and DataSet index) of the + /// selected value at the given touch point inside the Line-, Scatter-, or + /// CandleStick-Chart. + @objc open func getHighlightByTouchPoint(_ pt: CGPoint) -> Highlight? + { + if _data === nil + { + Swift.print("Can't select by touch. No data set.") + return nil + } + + return self.highlighter?.getHighlight(x: pt.x, y: pt.y) + } + + /// The last value that was highlighted via touch. + @objc open var lastHighlighted: Highlight? + + // MARK: - Markers + + /// draws all MarkerViews on the highlighted positions + internal func drawMarkers(context: CGContext) + { + // if there is no marker view or drawing marker is disabled + guard + let marker = marker + , isDrawMarkersEnabled && + valuesToHighlight() + else { return } + + for i in 0 ..< _indicesToHighlight.count + { + let highlight = _indicesToHighlight[i] + + guard let + set = data?.getDataSetByIndex(highlight.dataSetIndex), + let e = _data?.entryForHighlight(highlight) + else { continue } + + let entryIndex = set.entryIndex(entry: e) + if entryIndex > Int(Double(set.entryCount) * _animator.phaseX) + { + continue + } + + let pos = getMarkerPosition(highlight: highlight) + + // check bounds + if !_viewPortHandler.isInBounds(x: pos.x, y: pos.y) + { + continue + } + + // callbacks to update the content + marker.refreshContent(entry: e, highlight: highlight) + + // draw the marker + marker.draw(context: context, point: pos) + } + } + + /// - Returns: The actual position in pixels of the MarkerView for the given Entry in the given DataSet. + @objc open func getMarkerPosition(highlight: Highlight) -> CGPoint + { + return CGPoint(x: highlight.drawX, y: highlight.drawY) + } + + // MARK: - Animation + + /// The animator responsible for animating chart values. + @objc open var chartAnimator: Animator! + { + return _animator + } + + /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - easingX: an easing function for the animation on the x axis + /// - easingY: an easing function for the animation on the y axis + @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingX: ChartEasingFunctionBlock?, easingY: ChartEasingFunctionBlock?) + { + _animator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easingX, easingY: easingY) + } + + /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - easingOptionX: the easing function for the animation on the x axis + /// - easingOptionY: the easing function for the animation on the y axis + @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingOptionX: ChartEasingOption, easingOptionY: ChartEasingOption) + { + _animator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingOptionX: easingOptionX, easingOptionY: easingOptionY) + } + + /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - easing: an easing function for the animation + @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?) + { + _animator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easing: easing) + } + + /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + /// - easingOption: the easing function for the animation + @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingOption: ChartEasingOption) + { + _animator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingOption: easingOption) + } + + /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - yAxisDuration: duration for animating the y axis + @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval) + { + _animator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration) + } + + /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - easing: an easing function for the animation + @objc open func animate(xAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?) + { + _animator.animate(xAxisDuration: xAxisDuration, easing: easing) + } + + /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + /// - easingOption: the easing function for the animation + @objc open func animate(xAxisDuration: TimeInterval, easingOption: ChartEasingOption) + { + _animator.animate(xAxisDuration: xAxisDuration, easingOption: easingOption) + } + + /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - xAxisDuration: duration for animating the x axis + @objc open func animate(xAxisDuration: TimeInterval) + { + _animator.animate(xAxisDuration: xAxisDuration) + } + + /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - yAxisDuration: duration for animating the y axis + /// - easing: an easing function for the animation + @objc open func animate(yAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?) + { + _animator.animate(yAxisDuration: yAxisDuration, easing: easing) + } + + /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - yAxisDuration: duration for animating the y axis + /// - easingOption: the easing function for the animation + @objc open func animate(yAxisDuration: TimeInterval, easingOption: ChartEasingOption) + { + _animator.animate(yAxisDuration: yAxisDuration, easingOption: easingOption) + } + + /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. + /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. + /// + /// - Parameters: + /// - yAxisDuration: duration for animating the y axis + @objc open func animate(yAxisDuration: TimeInterval) + { + _animator.animate(yAxisDuration: yAxisDuration) + } + + // MARK: - Accessors + + /// The current y-max value across all DataSets + open var chartYMax: Double + { + return _data?.yMax ?? 0.0 + } + + /// The current y-min value across all DataSets + open var chartYMin: Double + { + return _data?.yMin ?? 0.0 + } + + open var chartXMax: Double + { + return _xAxis._axisMaximum + } + + open var chartXMin: Double + { + return _xAxis._axisMinimum + } + + open var xRange: Double + { + return _xAxis.axisRange + } + + /// - Note: (Equivalent of getCenter() in MPAndroidChart, as center is already a standard in iOS that returns the center point relative to superview, and MPAndroidChart returns relative to self)* + /// The center point of the chart (the whole View) in pixels. + @objc open var midPoint: CGPoint + { + let bounds = self.bounds + return CGPoint(x: bounds.origin.x + bounds.size.width / 2.0, y: bounds.origin.y + bounds.size.height / 2.0) + } + + /// The center of the chart taking offsets under consideration. (returns the center of the content rectangle) + open var centerOffsets: CGPoint + { + return _viewPortHandler.contentCenter + } + + /// The Legend object of the chart. This method can be used to get an instance of the legend in order to customize the automatically generated Legend. + @objc open var legend: Legend + { + return _legend + } + + /// The renderer object responsible for rendering / drawing the Legend. + @objc open var legendRenderer: LegendRenderer! + { + return _legendRenderer + } + + /// The rectangle that defines the borders of the chart-value surface (into which the actual values are drawn). + @objc open var contentRect: CGRect + { + return _viewPortHandler.contentRect + } + + /// - Returns: The ViewPortHandler of the chart that is responsible for the + /// content area of the chart and its offsets and dimensions. + @objc open var viewPortHandler: ViewPortHandler! + { + return _viewPortHandler + } + + /// - Returns: The bitmap that represents the chart. + @objc open func getChartImage(transparent: Bool) -> NSUIImage? + { + NSUIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque || !transparent, NSUIScreen.nsuiMain?.nsuiScale ?? 1.0) + + guard let context = NSUIGraphicsGetCurrentContext() + else { return nil } + + let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: bounds.size) + + if isOpaque || !transparent + { + // Background color may be partially transparent, we must fill with white if we want to output an opaque image + context.setFillColor(NSUIColor.white.cgColor) + context.fill(rect) + + if let backgroundColor = self.backgroundColor + { + context.setFillColor(backgroundColor.cgColor) + context.fill(rect) + } + } + + nsuiLayer?.render(in: context) + + let image = NSUIGraphicsGetImageFromCurrentImageContext() + + NSUIGraphicsEndImageContext() + + return image + } + + public enum ImageFormat + { + case jpeg + case png + } + + /// Saves the current chart state with the given name to the given path on + /// the sdcard leaving the path empty "" will put the saved file directly on + /// the SD card chart is saved as a PNG image, example: + /// saveToPath("myfilename", "foldername1/foldername2") + /// + /// - Parameters: + /// - to: path to the image to save + /// - format: the format to save + /// - compressionQuality: compression quality for lossless formats (JPEG) + /// - Returns: `true` if the image was saved successfully + open func save(to path: String, format: ImageFormat, compressionQuality: Double) -> Bool + { + guard let image = getChartImage(transparent: format != .jpeg) else { return false } + + let imageData: Data? + switch (format) + { + case .png: imageData = NSUIImagePNGRepresentation(image) + case .jpeg: imageData = NSUIImageJPEGRepresentation(image, CGFloat(compressionQuality)) + } + + guard let data = imageData else { return false } + + do + { + try data.write(to: URL(fileURLWithPath: path), options: .atomic) + } + catch + { + return false + } + + return true + } + + internal var _viewportJobs = [ViewPortJob]() + + open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) + { + if keyPath == "bounds" || keyPath == "frame" + { + let bounds = self.bounds + + if (_viewPortHandler !== nil && + (bounds.size.width != _viewPortHandler.chartWidth || + bounds.size.height != _viewPortHandler.chartHeight)) + { + _viewPortHandler.setChartDimens(width: bounds.size.width, height: bounds.size.height) + + // This may cause the chart view to mutate properties affecting the view port -- lets do this + // before we try to run any pending jobs on the view port itself + notifyDataSetChanged() + + // Finish any pending viewport changes + while (!_viewportJobs.isEmpty) + { + let job = _viewportJobs.remove(at: 0) + job.doJob() + } + } + } + } + + @objc open func removeViewportJob(_ job: ViewPortJob) + { + if let index = _viewportJobs.firstIndex(where: { $0 === job }) + { + _viewportJobs.remove(at: index) + } + } + + @objc open func clearAllViewportJobs() + { + _viewportJobs.removeAll(keepingCapacity: false) + } + + @objc open func addViewportJob(_ job: ViewPortJob) + { + if _viewPortHandler.hasChartDimens + { + job.doJob() + } + else + { + _viewportJobs.append(job) + } + } + + /// **default**: true + /// `true` if chart continues to scroll after touch up, `false` ifnot. + @objc open var isDragDecelerationEnabled: Bool + { + return dragDecelerationEnabled + } + + /// Deceleration friction coefficient in [0 ; 1] interval, higher values indicate that speed will decrease slowly, for example if it set to 0, it will stop immediately. + /// 1 is an invalid value, and will be converted to 0.999 automatically. + /// + /// **default**: true + @objc open var dragDecelerationFrictionCoef: CGFloat + { + get + { + return _dragDecelerationFrictionCoef + } + set + { + var val = newValue + if val < 0.0 + { + val = 0.0 + } + if val >= 1.0 + { + val = 0.999 + } + + _dragDecelerationFrictionCoef = val + } + } + + /// The maximum distance in screen pixels away from an entry causing it to highlight. + /// **default**: 500.0 + /// + open var _maxHighlightDistance: CGFloat = 500.0 + open var maxHighlightDistance: CGFloat { + get { + return _maxHighlightDistance + } + set { + _maxHighlightDistance = newValue + } + } + + /// the number of maximum visible drawn values on the chart only active when `drawValuesEnabled` is enabled + open var maxVisibleCount: Int + { + return Int(INT_MAX) + } + + // MARK: - AnimatorDelegate + + open func animatorUpdated(_ chartAnimator: Animator) + { + setNeedsDisplay() + } + + open func animatorStopped(_ chartAnimator: Animator) + { + delegate?.chartView?(self, animatorDidStop: chartAnimator) + } + + // MARK: - Touches + + open override func nsuiTouchesBegan(_ touches: Set, withEvent event: NSUIEvent?) + { + if !_interceptTouchEvents + { + super.nsuiTouchesBegan(touches, withEvent: event) + } + } + + open override func nsuiTouchesMoved(_ touches: Set, withEvent event: NSUIEvent?) + { + if !_interceptTouchEvents + { + super.nsuiTouchesMoved(touches, withEvent: event) + } + } + + open override func nsuiTouchesEnded(_ touches: Set, withEvent event: NSUIEvent?) + { + if !_interceptTouchEvents + { + super.nsuiTouchesEnded(touches, withEvent: event) + } + } + + open override func nsuiTouchesCancelled(_ touches: Set?, withEvent event: NSUIEvent?) + { + if !_interceptTouchEvents + { + super.nsuiTouchesCancelled(touches, withEvent: event) + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Charts/CombinedChartView.swift b/dydx/Pods/Charts/Source/Charts/Charts/CombinedChartView.swift new file mode 100644 index 000000000..47eebd6c7 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Charts/CombinedChartView.swift @@ -0,0 +1,246 @@ +// +// CombinedChartView.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +/// This chart class allows the combination of lines, bars, scatter and candle data all displayed in one chart area. +open class CombinedChartView: BarLineChartViewBase, CombinedChartDataProvider +{ + /// the fill-formatter used for determining the position of the fill-line + internal var _fillFormatter: IFillFormatter! + + /// enum that allows to specify the order in which the different data objects for the combined-chart are drawn + @objc(CombinedChartDrawOrder) + public enum DrawOrder: Int + { + case bar + case bubble + case line + case candle + case scatter + } + + open override func initialize() + { + super.initialize() + + self.highlighter = CombinedHighlighter(chart: self, barDataProvider: self) + + // Old default behaviour + self.highlightFullBarEnabled = true + + _fillFormatter = DefaultFillFormatter() + + renderer = CombinedChartRenderer(chart: self, animator: _animator, viewPortHandler: _viewPortHandler) + } + + open override var data: ChartData? + { + get + { + return super.data + } + set + { + super.data = newValue + + self.highlighter = CombinedHighlighter(chart: self, barDataProvider: self) + + (renderer as? CombinedChartRenderer)?.createRenderers() + renderer?.initBuffers() + } + } + + @objc open var fillFormatter: IFillFormatter + { + get + { + return _fillFormatter + } + set + { + _fillFormatter = newValue + if _fillFormatter == nil + { + _fillFormatter = DefaultFillFormatter() + } + } + } + + /// - Returns: The Highlight object (contains x-index and DataSet index) of the selected value at the given touch point inside the CombinedChart. + open override func getHighlightByTouchPoint(_ pt: CGPoint) -> Highlight? + { + if _data === nil + { + Swift.print("Can't select by touch. No data set.") + return nil + } + + guard let h = self.highlighter?.getHighlight(x: pt.x, y: pt.y) + else { return nil } + + if !isHighlightFullBarEnabled { return h } + + // For isHighlightFullBarEnabled, remove stackIndex + return Highlight( + x: h.x, y: h.y, + xPx: h.xPx, yPx: h.yPx, + dataIndex: h.dataIndex, + dataSetIndex: h.dataSetIndex, + stackIndex: -1, + axis: h.axis) + } + + // MARK: - CombinedChartDataProvider + + open var combinedData: CombinedChartData? + { + get + { + return _data as? CombinedChartData + } + } + + // MARK: - LineChartDataProvider + + open var lineData: LineChartData? + { + get + { + return combinedData?.lineData + } + } + + // MARK: - BarChartDataProvider + + open var barData: BarChartData? + { + get + { + return combinedData?.barData + } + } + + // MARK: - ScatterChartDataProvider + + open var scatterData: ScatterChartData? + { + get + { + return combinedData?.scatterData + } + } + + // MARK: - CandleChartDataProvider + + open var candleData: CandleChartData? + { + get + { + return combinedData?.candleData + } + } + + // MARK: - BubbleChartDataProvider + + open var bubbleData: BubbleChartData? + { + get + { + return combinedData?.bubbleData + } + } + + // MARK: - Accessors + + /// if set to true, all values are drawn above their bars, instead of below their top + @objc open var drawValueAboveBarEnabled: Bool + { + get { return (renderer as! CombinedChartRenderer).drawValueAboveBarEnabled } + set { (renderer as! CombinedChartRenderer).drawValueAboveBarEnabled = newValue } + } + + /// if set to true, a grey area is drawn behind each bar that indicates the maximum value + @objc open var drawBarShadowEnabled: Bool + { + get { return (renderer as! CombinedChartRenderer).drawBarShadowEnabled } + set { (renderer as! CombinedChartRenderer).drawBarShadowEnabled = newValue } + } + + /// `true` if drawing values above bars is enabled, `false` ifnot + open var isDrawValueAboveBarEnabled: Bool { return (renderer as! CombinedChartRenderer).drawValueAboveBarEnabled } + + /// `true` if drawing shadows (maxvalue) for each bar is enabled, `false` ifnot + open var isDrawBarShadowEnabled: Bool { return (renderer as! CombinedChartRenderer).drawBarShadowEnabled } + + /// the order in which the provided data objects should be drawn. + /// The earlier you place them in the provided array, the further they will be in the background. + /// e.g. if you provide [DrawOrder.Bar, DrawOrder.Line], the bars will be drawn behind the lines. + @objc open var drawOrder: [Int] + { + get + { + return (renderer as! CombinedChartRenderer).drawOrder.map { $0.rawValue } + } + set + { + (renderer as! CombinedChartRenderer).drawOrder = newValue.map { DrawOrder(rawValue: $0)! } + } + } + + /// Set this to `true` to make the highlight operation full-bar oriented, `false` to make it highlight single values + @objc open var highlightFullBarEnabled: Bool = false + + /// `true` the highlight is be full-bar oriented, `false` ifsingle-value + open var isHighlightFullBarEnabled: Bool { return highlightFullBarEnabled } + + // MARK: - ChartViewBase + + /// draws all MarkerViews on the highlighted positions + override func drawMarkers(context: CGContext) + { + guard + let marker = marker, + isDrawMarkersEnabled && valuesToHighlight() + else { return } + + for i in 0 ..< _indicesToHighlight.count + { + let highlight = _indicesToHighlight[i] + + guard + let set = combinedData?.getDataSetByHighlight(highlight), + let e = _data?.entryForHighlight(highlight) + else { continue } + + let entryIndex = set.entryIndex(entry: e) + if entryIndex > Int(Double(set.entryCount) * _animator.phaseX) + { + continue + } + + let pos = getMarkerPosition(highlight: highlight) + + // check bounds + if !_viewPortHandler.isInBounds(x: pos.x, y: pos.y) + { + continue + } + + // callbacks to update the content + marker.refreshContent(entry: e, highlight: highlight) + + // draw the marker + marker.draw(context: context, point: pos) + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Charts/HorizontalBarChartView.swift b/dydx/Pods/Charts/Source/Charts/Charts/HorizontalBarChartView.swift new file mode 100644 index 000000000..7b6163c8c --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Charts/HorizontalBarChartView.swift @@ -0,0 +1,270 @@ +// +// HorizontalBarChartView.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +/// BarChart with horizontal bar orientation. In this implementation, x- and y-axis are switched. +open class HorizontalBarChartView: BarChartView +{ + internal override func initialize() + { + super.initialize() + + _leftAxisTransformer = TransformerHorizontalBarChart(viewPortHandler: _viewPortHandler) + _rightAxisTransformer = TransformerHorizontalBarChart(viewPortHandler: _viewPortHandler) + + renderer = HorizontalBarChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler) + leftYAxisRenderer = YAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, yAxis: leftAxis, transformer: _leftAxisTransformer) + rightYAxisRenderer = YAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, yAxis: rightAxis, transformer: _rightAxisTransformer) + xAxisRenderer = XAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, xAxis: _xAxis, transformer: _leftAxisTransformer, chart: self) + + self.highlighter = HorizontalBarHighlighter(chart: self) + } + + internal override func calculateLegendOffsets(offsetLeft: inout CGFloat, offsetTop: inout CGFloat, offsetRight: inout CGFloat, offsetBottom: inout CGFloat) + { + guard + let legend = _legend, + legend.isEnabled, + !legend.drawInside + else { return } + + // setup offsets for legend + switch legend.orientation + { + case .vertical: + switch legend.horizontalAlignment + { + case .left: + offsetLeft += min(legend.neededWidth, _viewPortHandler.chartWidth * legend.maxSizePercent) + legend.xOffset + + case .right: + offsetRight += min(legend.neededWidth, _viewPortHandler.chartWidth * legend.maxSizePercent) + legend.xOffset + + case .center: + + switch legend.verticalAlignment + { + case .top: + offsetTop += min(legend.neededHeight, _viewPortHandler.chartHeight * legend.maxSizePercent) + legend.yOffset + + case .bottom: + offsetBottom += min(legend.neededHeight, _viewPortHandler.chartHeight * legend.maxSizePercent) + legend.yOffset + + default: + break + } + } + + case .horizontal: + switch legend.verticalAlignment + { + case .top: + offsetTop += min(legend.neededHeight, _viewPortHandler.chartHeight * legend.maxSizePercent) + legend.yOffset + + // left axis equals the top x-axis in a horizontal chart + if leftAxis.isEnabled && leftAxis.isDrawLabelsEnabled + { + offsetTop += leftAxis.getRequiredHeightSpace() + } + + case .bottom: + offsetBottom += min(legend.neededHeight, _viewPortHandler.chartHeight * legend.maxSizePercent) + legend.yOffset + + // right axis equals the bottom x-axis in a horizontal chart + if rightAxis.isEnabled && rightAxis.isDrawLabelsEnabled + { + offsetBottom += rightAxis.getRequiredHeightSpace() + } + default: + break + } + } + } + + internal override func calculateOffsets() + { + var offsetLeft: CGFloat = 0.0, + offsetRight: CGFloat = 0.0, + offsetTop: CGFloat = 0.0, + offsetBottom: CGFloat = 0.0 + + calculateLegendOffsets(offsetLeft: &offsetLeft, + offsetTop: &offsetTop, + offsetRight: &offsetRight, + offsetBottom: &offsetBottom) + + // offsets for y-labels + if leftAxis.needsOffset + { + offsetTop += leftAxis.getRequiredHeightSpace() + } + + if rightAxis.needsOffset + { + offsetBottom += rightAxis.getRequiredHeightSpace() + } + + let xlabelwidth = _xAxis.labelRotatedWidth + + if _xAxis.isEnabled + { + // offsets for x-labels + if _xAxis.labelPosition == .bottom + { + offsetLeft += xlabelwidth + } + else if _xAxis.labelPosition == .top + { + offsetRight += xlabelwidth + } + else if _xAxis.labelPosition == .bothSided + { + offsetLeft += xlabelwidth + offsetRight += xlabelwidth + } + } + + offsetTop += self.extraTopOffset + offsetRight += self.extraRightOffset + offsetBottom += self.extraBottomOffset + offsetLeft += self.extraLeftOffset + + _viewPortHandler.restrainViewPort( + offsetLeft: max(self.minOffset, offsetLeft), + offsetTop: max(self.minOffset, offsetTop), + offsetRight: max(self.minOffset, offsetRight), + offsetBottom: max(self.minOffset, offsetBottom)) + + prepareOffsetMatrix() + prepareValuePxMatrix() + } + + internal override func prepareValuePxMatrix() + { + _rightAxisTransformer.prepareMatrixValuePx(chartXMin: rightAxis._axisMinimum, deltaX: CGFloat(rightAxis.axisRange), deltaY: CGFloat(_xAxis.axisRange), chartYMin: _xAxis._axisMinimum) + _leftAxisTransformer.prepareMatrixValuePx(chartXMin: leftAxis._axisMinimum, deltaX: CGFloat(leftAxis.axisRange), deltaY: CGFloat(_xAxis.axisRange), chartYMin: _xAxis._axisMinimum) + } + + open override func getMarkerPosition(highlight: Highlight) -> CGPoint + { + return CGPoint(x: highlight.drawY, y: highlight.drawX) + } + + open override func getBarBounds(entry e: BarChartDataEntry) -> CGRect + { + guard + let data = _data as? BarChartData, + let set = data.getDataSetForEntry(e) as? IBarChartDataSet + else { return CGRect.null } + + let y = e.y + let x = e.x + + let barWidth = data.barWidth + + let top = x - 0.5 + barWidth / 2.0 + let bottom = x + 0.5 - barWidth / 2.0 + let left = y >= 0.0 ? y : 0.0 + let right = y <= 0.0 ? y : 0.0 + + var bounds = CGRect(x: left, y: top, width: right - left, height: bottom - top) + + getTransformer(forAxis: set.axisDependency).rectValueToPixel(&bounds) + + return bounds + } + + open override func getPosition(entry e: ChartDataEntry, axis: YAxis.AxisDependency) -> CGPoint + { + var vals = CGPoint(x: CGFloat(e.y), y: CGFloat(e.x)) + + getTransformer(forAxis: axis).pointValueToPixel(&vals) + + return vals + } + + open override func getHighlightByTouchPoint(_ pt: CGPoint) -> Highlight? + { + if _data === nil + { + Swift.print("Can't select by touch. No data set.", terminator: "\n") + return nil + } + + return self.highlighter?.getHighlight(x: pt.y, y: pt.x) + } + + /// The lowest x-index (value on the x-axis) that is still visible on he chart. + open override var lowestVisibleX: Double + { + var pt = CGPoint( + x: viewPortHandler.contentLeft, + y: viewPortHandler.contentBottom) + + getTransformer(forAxis: .left).pixelToValues(&pt) + + return max(xAxis._axisMinimum, Double(pt.y)) + } + + /// The highest x-index (value on the x-axis) that is still visible on the chart. + open override var highestVisibleX: Double + { + var pt = CGPoint( + x: viewPortHandler.contentLeft, + y: viewPortHandler.contentTop) + + getTransformer(forAxis: .left).pixelToValues(&pt) + + return min(xAxis._axisMaximum, Double(pt.y)) + } + + // MARK: - Viewport + + open override func setVisibleXRangeMaximum(_ maxXRange: Double) + { + let xScale = xAxis.axisRange / maxXRange + viewPortHandler.setMinimumScaleY(CGFloat(xScale)) + } + + open override func setVisibleXRangeMinimum(_ minXRange: Double) + { + let xScale = xAxis.axisRange / minXRange + viewPortHandler.setMaximumScaleY(CGFloat(xScale)) + } + + open override func setVisibleXRange(minXRange: Double, maxXRange: Double) + { + let minScale = xAxis.axisRange / minXRange + let maxScale = xAxis.axisRange / maxXRange + viewPortHandler.setMinMaxScaleY(minScaleY: CGFloat(minScale), maxScaleY: CGFloat(maxScale)) + } + + open override func setVisibleYRangeMaximum(_ maxYRange: Double, axis: YAxis.AxisDependency) + { + let yScale = getAxisRange(axis: axis) / maxYRange + viewPortHandler.setMinimumScaleX(CGFloat(yScale)) + } + + open override func setVisibleYRangeMinimum(_ minYRange: Double, axis: YAxis.AxisDependency) + { + let yScale = getAxisRange(axis: axis) / minYRange + viewPortHandler.setMaximumScaleX(CGFloat(yScale)) + } + + open override func setVisibleYRange(minYRange: Double, maxYRange: Double, axis: YAxis.AxisDependency) + { + let minScale = getAxisRange(axis: axis) / minYRange + let maxScale = getAxisRange(axis: axis) / maxYRange + viewPortHandler.setMinMaxScaleX(minScaleX: CGFloat(minScale), maxScaleX: CGFloat(maxScale)) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Charts/LineChartView.swift b/dydx/Pods/Charts/Source/Charts/Charts/LineChartView.swift new file mode 100644 index 000000000..c5fbecfac --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Charts/LineChartView.swift @@ -0,0 +1,28 @@ +// +// LineChartView.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +/// Chart that draws lines, surfaces, circles, ... +open class LineChartView: BarLineChartViewBase, LineChartDataProvider +{ + internal override func initialize() + { + super.initialize() + + renderer = LineChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler) + } + + // MARK: - LineChartDataProvider + + open var lineData: LineChartData? { return _data as? LineChartData } +} diff --git a/dydx/Pods/Charts/Source/Charts/Charts/PieChartView.swift b/dydx/Pods/Charts/Source/Charts/Charts/PieChartView.swift new file mode 100644 index 000000000..7a036c01b --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Charts/PieChartView.swift @@ -0,0 +1,690 @@ +// +// PieChartView.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + +/// View that represents a pie chart. Draws cake like slices. +open class PieChartView: PieRadarChartViewBase +{ + /// rect object that represents the bounds of the piechart, needed for drawing the circle + private var _circleBox = CGRect() + + /// flag indicating if entry labels should be drawn or not + private var _drawEntryLabelsEnabled = true + + /// array that holds the width of each pie-slice in degrees + private var _drawAngles = [CGFloat]() + + /// array that holds the absolute angle in degrees of each slice + private var _absoluteAngles = [CGFloat]() + + /// if true, the hole inside the chart will be drawn + private var _drawHoleEnabled = true + + private var _holeColor: NSUIColor? = NSUIColor.white + + /// Sets the color the entry labels are drawn with. + private var _entryLabelColor: NSUIColor? = NSUIColor.white + + /// Sets the font the entry labels are drawn with. + private var _entryLabelFont: NSUIFont? = NSUIFont(name: "HelveticaNeue", size: 13.0) + + /// if true, the hole will see-through to the inner tips of the slices + private var _drawSlicesUnderHoleEnabled = false + + /// if true, the values inside the piechart are drawn as percent values + private var _usePercentValuesEnabled = false + + /// variable for the text that is drawn in the center of the pie-chart + private var _centerAttributedText: NSAttributedString? + + /// the offset on the x- and y-axis the center text has in dp. + private var _centerTextOffset: CGPoint = CGPoint() + + /// indicates the size of the hole in the center of the piechart + /// + /// **default**: `0.5` + private var _holeRadiusPercent = CGFloat(0.5) + + private var _transparentCircleColor: NSUIColor? = NSUIColor(white: 1.0, alpha: 105.0/255.0) + + /// the radius of the transparent circle next to the chart-hole in the center + private var _transparentCircleRadiusPercent = CGFloat(0.55) + + /// if enabled, centertext is drawn + private var _drawCenterTextEnabled = true + + private var _centerTextRadiusPercent: CGFloat = 1.0 + + /// maximum angle for this pie + private var _maxAngle: CGFloat = 360.0 + + public override init(frame: CGRect) + { + super.init(frame: frame) + } + + public required init?(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder) + } + + internal override func initialize() + { + super.initialize() + + renderer = PieChartRenderer(chart: self, animator: _animator, viewPortHandler: _viewPortHandler) + _xAxis = nil + + self.highlighter = PieHighlighter(chart: self) + } + + open override func draw(_ rect: CGRect) + { + super.draw(rect) + + if _data === nil + { + return + } + + let optionalContext = NSUIGraphicsGetCurrentContext() + guard let context = optionalContext, let renderer = renderer else + { + return + } + + renderer.drawData(context: context) + + if (valuesToHighlight()) + { + renderer.drawHighlighted(context: context, indices: _indicesToHighlight) + } + + renderer.drawExtras(context: context) + + renderer.drawValues(context: context) + + legendRenderer.renderLegend(context: context) + + drawDescription(context: context) + + drawMarkers(context: context) + } + + /// if width is larger than height + private var widthLarger: Bool + { + return _viewPortHandler.contentRect.orientation == .landscape + } + + /// adjusted radius. Use diameter when it's half pie and width is larger + private var adjustedRadius: CGFloat + { + return maxAngle <= 180 && widthLarger ? diameter : diameter / 2.0 + } + + /// true centerOffsets considering half pie & width is larger + private func adjustedCenterOffsets() -> CGPoint + { + var c = self.centerOffsets + c.y = maxAngle <= 180 && widthLarger ? c.y + adjustedRadius / 2 : c.y + return c + } + + internal override func calculateOffsets() + { + super.calculateOffsets() + + // prevent nullpointer when no data set + if _data === nil + { + return + } + + let radius = adjustedRadius + + let c = adjustedCenterOffsets() + + let shift = (data as? PieChartData)?.dataSet?.selectionShift ?? 0.0 + + // create the circle box that will contain the pie-chart (the bounds of the pie-chart) + _circleBox.origin.x = (c.x - radius) + shift + _circleBox.origin.y = (c.y - radius) + shift + _circleBox.size.width = radius * 2 - shift * 2.0 + _circleBox.size.height = radius * 2 - shift * 2.0 + + } + + internal override func calcMinMax() + { + calcAngles() + } + + @objc open override func angleForPoint(x: CGFloat, y: CGFloat) -> CGFloat + { + let c = adjustedCenterOffsets() + + let tx = Double(x - c.x) + let ty = Double(y - c.y) + let length = sqrt(tx * tx + ty * ty) + let r = acos(ty / length) + + var angle = r.RAD2DEG + + if x > c.x + { + angle = 360.0 - angle + } + + // add 90° because chart starts EAST + angle = angle + 90.0 + + // neutralize overflow + if angle > 360.0 + { + angle = angle - 360.0 + } + + return CGFloat(angle) + } + + /// - Returns: The distance of a certain point on the chart to the center of the chart. + @objc open override func distanceToCenter(x: CGFloat, y: CGFloat) -> CGFloat + { + let c = adjustedCenterOffsets() + + var dist = CGFloat(0.0) + + var xDist = CGFloat(0.0) + var yDist = CGFloat(0.0) + + if x > c.x + { + xDist = x - c.x + } + else + { + xDist = c.x - x + } + + if y > c.y + { + yDist = y - c.y + } + else + { + yDist = c.y - y + } + + // pythagoras + dist = sqrt(pow(xDist, 2.0) + pow(yDist, 2.0)) + + return dist + } + + open override func getMarkerPosition(highlight: Highlight) -> CGPoint + { + let center = self.centerCircleBox + var r = self.radius + + var off = r / 10.0 * 3.6 + + if self.isDrawHoleEnabled + { + off = (r - (r * self.holeRadiusPercent)) / 2.0 + } + + r -= off // offset to keep things inside the chart + + let rotationAngle = self.rotationAngle + + let entryIndex = Int(highlight.x) + + // offset needed to center the drawn text in the slice + let offset = drawAngles[entryIndex] / 2.0 + + // calculate the text position + let x: CGFloat = (r * cos(((rotationAngle + absoluteAngles[entryIndex] - offset) * CGFloat(_animator.phaseY)).DEG2RAD) + center.x) + let y: CGFloat = (r * sin(((rotationAngle + absoluteAngles[entryIndex] - offset) * CGFloat(_animator.phaseY)).DEG2RAD) + center.y) + + return CGPoint(x: x, y: y) + } + + /// calculates the needed angles for the chart slices + private func calcAngles() + { + _drawAngles = [CGFloat]() + _absoluteAngles = [CGFloat]() + + guard let data = _data else { return } + + let entryCount = data.entryCount + + _drawAngles.reserveCapacity(entryCount) + _absoluteAngles.reserveCapacity(entryCount) + + let yValueSum = (_data as! PieChartData).yValueSum + + var cnt = 0 + + for set in data.dataSets + { + for j in 0 ..< set.entryCount + { + guard let e = set.entryForIndex(j) else { continue } + + _drawAngles.append(calcAngle(value: abs(e.y), yValueSum: yValueSum)) + + if cnt == 0 + { + _absoluteAngles.append(_drawAngles[cnt]) + } + else + { + _absoluteAngles.append(_absoluteAngles[cnt - 1] + _drawAngles[cnt]) + } + + cnt += 1 + } + } + } + + /// Checks if the given index is set to be highlighted. + @objc open func needsHighlight(index: Int) -> Bool + { + return _indicesToHighlight.contains { Int($0.x) == index } + } + + /// calculates the needed angle for a given value + private func calcAngle(_ value: Double) -> CGFloat + { + return calcAngle(value: value, yValueSum: (_data as! PieChartData).yValueSum) + } + + /// calculates the needed angle for a given value + private func calcAngle(value: Double, yValueSum: Double) -> CGFloat + { + return CGFloat(value) / CGFloat(yValueSum) * _maxAngle + } + + /// This will throw an exception, PieChart has no XAxis object. + open override var xAxis: XAxis + { + fatalError("PieChart has no XAxis") + } + + open override func indexForAngle(_ angle: CGFloat) -> Int + { + // TODO: Return nil instead of -1 + // take the current angle of the chart into consideration + let a = (angle - self.rotationAngle).normalizedAngle + return _absoluteAngles.firstIndex { $0 > a } ?? -1 + } + + /// - Returns: The index of the DataSet this x-index belongs to. + @objc open func dataSetIndexForIndex(_ xValue: Double) -> Int + { + // TODO: Return nil instead of -1 + return _data?.dataSets.firstIndex { + $0.entryForXValue(xValue, closestToY: .nan) != nil + } ?? -1 + } + + /// - Returns: An integer array of all the different angles the chart slices + /// have the angles in the returned array determine how much space (of 360°) + /// each slice takes + @objc open var drawAngles: [CGFloat] + { + return _drawAngles + } + + /// - Returns: The absolute angles of the different chart slices (where the + /// slices end) + @objc open var absoluteAngles: [CGFloat] + { + return _absoluteAngles + } + + /// The color for the hole that is drawn in the center of the PieChart (if enabled). + /// + /// - Note: Use holeTransparent with holeColor = nil to make the hole transparent.* + @objc open var holeColor: NSUIColor? + { + get + { + return _holeColor + } + set + { + _holeColor = newValue + setNeedsDisplay() + } + } + + /// if true, the hole will see-through to the inner tips of the slices + /// + /// **default**: `false` + @objc open var drawSlicesUnderHoleEnabled: Bool + { + get + { + return _drawSlicesUnderHoleEnabled + } + set + { + _drawSlicesUnderHoleEnabled = newValue + setNeedsDisplay() + } + } + + /// `true` if the inner tips of the slices are visible behind the hole, `false` if not. + @objc open var isDrawSlicesUnderHoleEnabled: Bool + { + return drawSlicesUnderHoleEnabled + } + + /// `true` if the hole in the center of the pie-chart is set to be visible, `false` ifnot + @objc open var drawHoleEnabled: Bool + { + get + { + return _drawHoleEnabled + } + set + { + _drawHoleEnabled = newValue + setNeedsDisplay() + } + } + + /// `true` if the hole in the center of the pie-chart is set to be visible, `false` ifnot + @objc open var isDrawHoleEnabled: Bool + { + get + { + return drawHoleEnabled + } + } + + /// the text that is displayed in the center of the pie-chart + @objc open var centerText: String? + { + get + { + return self.centerAttributedText?.string + } + set + { + var attrString: NSMutableAttributedString? + if newValue == nil + { + attrString = nil + } + else + { + let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + paragraphStyle.lineBreakMode = .byTruncatingTail + paragraphStyle.alignment = .center + + attrString = NSMutableAttributedString(string: newValue!) + attrString?.setAttributes([ + .foregroundColor: NSUIColor.labelOrBlack, + .font: NSUIFont.systemFont(ofSize: 12.0), + .paragraphStyle: paragraphStyle + ], range: NSMakeRange(0, attrString!.length)) + } + self.centerAttributedText = attrString + } + } + + /// the text that is displayed in the center of the pie-chart + @objc open var centerAttributedText: NSAttributedString? + { + get + { + return _centerAttributedText + } + set + { + _centerAttributedText = newValue + setNeedsDisplay() + } + } + + /// Sets the offset the center text should have from it's original position in dp. Default x = 0, y = 0 + @objc open var centerTextOffset: CGPoint + { + get + { + return _centerTextOffset + } + set + { + _centerTextOffset = newValue + setNeedsDisplay() + } + } + + /// `true` if drawing the center text is enabled + @objc open var drawCenterTextEnabled: Bool + { + get + { + return _drawCenterTextEnabled + } + set + { + _drawCenterTextEnabled = newValue + setNeedsDisplay() + } + } + + /// `true` if drawing the center text is enabled + @objc open var isDrawCenterTextEnabled: Bool + { + get + { + return drawCenterTextEnabled + } + } + + internal override var requiredLegendOffset: CGFloat + { + return _legend.font.pointSize * 2.0 + } + + internal override var requiredBaseOffset: CGFloat + { + return 0.0 + } + + open override var radius: CGFloat + { + return _circleBox.width / 2.0 + } + + /// The circlebox, the boundingbox of the pie-chart slices + @objc open var circleBox: CGRect + { + return _circleBox + } + + /// The center of the circlebox + @objc open var centerCircleBox: CGPoint + { + return CGPoint(x: _circleBox.midX, y: _circleBox.midY) + } + + /// the radius of the hole in the center of the piechart in percent of the maximum radius (max = the radius of the whole chart) + /// + /// **default**: 0.5 (50%) (half the pie) + @objc open var holeRadiusPercent: CGFloat + { + get + { + return _holeRadiusPercent + } + set + { + _holeRadiusPercent = newValue + setNeedsDisplay() + } + } + + /// The color that the transparent-circle should have. + /// + /// **default**: `nil` + @objc open var transparentCircleColor: NSUIColor? + { + get + { + return _transparentCircleColor + } + set + { + _transparentCircleColor = newValue + setNeedsDisplay() + } + } + + /// the radius of the transparent circle that is drawn next to the hole in the piechart in percent of the maximum radius (max = the radius of the whole chart) + /// + /// **default**: 0.55 (55%) -> means 5% larger than the center-hole by default + @objc open var transparentCircleRadiusPercent: CGFloat + { + get + { + return _transparentCircleRadiusPercent + } + set + { + _transparentCircleRadiusPercent = newValue + setNeedsDisplay() + } + } + + /// The color the entry labels are drawn with. + @objc open var entryLabelColor: NSUIColor? + { + get { return _entryLabelColor } + set + { + _entryLabelColor = newValue + setNeedsDisplay() + } + } + + /// The font the entry labels are drawn with. + @objc open var entryLabelFont: NSUIFont? + { + get { return _entryLabelFont } + set + { + _entryLabelFont = newValue + setNeedsDisplay() + } + } + + /// Set this to true to draw the enrty labels into the pie slices + @objc open var drawEntryLabelsEnabled: Bool + { + get + { + return _drawEntryLabelsEnabled + } + set + { + _drawEntryLabelsEnabled = newValue + setNeedsDisplay() + } + } + + /// `true` if drawing entry labels is enabled, `false` ifnot + @objc open var isDrawEntryLabelsEnabled: Bool + { + get + { + return drawEntryLabelsEnabled + } + } + + /// If this is enabled, values inside the PieChart are drawn in percent and not with their original value. Values provided for the ValueFormatter to format are then provided in percent. + @objc open var usePercentValuesEnabled: Bool + { + get + { + return _usePercentValuesEnabled + } + set + { + _usePercentValuesEnabled = newValue + setNeedsDisplay() + } + } + + /// `true` if drawing x-values is enabled, `false` ifnot + @objc open var isUsePercentValuesEnabled: Bool + { + get + { + return usePercentValuesEnabled + } + } + + /// the rectangular radius of the bounding box for the center text, as a percentage of the pie hole + @objc open var centerTextRadiusPercent: CGFloat + { + get + { + return _centerTextRadiusPercent + } + set + { + _centerTextRadiusPercent = newValue + setNeedsDisplay() + } + } + + /// The max angle that is used for calculating the pie-circle. + /// 360 means it's a full pie-chart, 180 results in a half-pie-chart. + /// **default**: 360.0 + @objc open var maxAngle: CGFloat + { + get + { + return _maxAngle + } + set + { + _maxAngle = newValue + + if _maxAngle > 360.0 + { + _maxAngle = 360.0 + } + + if _maxAngle < 90.0 + { + _maxAngle = 90.0 + } + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Charts/PieRadarChartViewBase.swift b/dydx/Pods/Charts/Source/Charts/Charts/PieRadarChartViewBase.swift new file mode 100644 index 000000000..86edcd84b --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Charts/PieRadarChartViewBase.swift @@ -0,0 +1,855 @@ +// +// PieRadarChartViewBase.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics +import QuartzCore + +#if canImport(AppKit) +import AppKit +#endif + + +/// Base class of PieChartView and RadarChartView. +open class PieRadarChartViewBase: ChartViewBase +{ + /// holds the normalized version of the current rotation angle of the chart + private var _rotationAngle = CGFloat(270.0) + + /// holds the raw version of the current rotation angle of the chart + private var _rawRotationAngle = CGFloat(270.0) + + /// flag that indicates if rotation is enabled or not + @objc open var rotationEnabled = true + + /// Sets the minimum offset (padding) around the chart, defaults to 0.0 + @objc open var minOffset = CGFloat(0.0) + + /// iOS && OSX only: Enabled multi-touch rotation using two fingers. + private var _rotationWithTwoFingers = false + + private var _tapGestureRecognizer: NSUITapGestureRecognizer! + #if !os(tvOS) + private var _rotationGestureRecognizer: NSUIRotationGestureRecognizer! + #endif + + public override init(frame: CGRect) + { + super.init(frame: frame) + } + + public required init?(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder) + } + + deinit + { + stopDeceleration() + } + + internal override func initialize() + { + super.initialize() + + _tapGestureRecognizer = NSUITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:))) + + self.addGestureRecognizer(_tapGestureRecognizer) + + #if !os(tvOS) + _rotationGestureRecognizer = NSUIRotationGestureRecognizer(target: self, action: #selector(rotationGestureRecognized(_:))) + self.addGestureRecognizer(_rotationGestureRecognizer) + _rotationGestureRecognizer.isEnabled = rotationWithTwoFingers + #endif + } + + internal override func calcMinMax() + { + /*_xAxis.axisRange = Double((_data?.xVals.count ?? 0) - 1)*/ + } + + open override var maxVisibleCount: Int + { + get + { + return data?.entryCount ?? 0 + } + } + + open override func notifyDataSetChanged() + { + calcMinMax() + + if let data = _data , _legend !== nil + { + legendRenderer.computeLegend(data: data) + } + + calculateOffsets() + + setNeedsDisplay() + } + + internal override func calculateOffsets() + { + var legendLeft = CGFloat(0.0) + var legendRight = CGFloat(0.0) + var legendBottom = CGFloat(0.0) + var legendTop = CGFloat(0.0) + + if _legend != nil && _legend.enabled && !_legend.drawInside + { + let fullLegendWidth = min(_legend.neededWidth, _viewPortHandler.chartWidth * _legend.maxSizePercent) + + switch _legend.orientation + { + case .vertical: + + var xLegendOffset: CGFloat = 0.0 + + if _legend.horizontalAlignment == .left + || _legend.horizontalAlignment == .right + { + if _legend.verticalAlignment == .center + { + // this is the space between the legend and the chart + let spacing = CGFloat(13.0) + + xLegendOffset = fullLegendWidth + spacing + } + else + { + // this is the space between the legend and the chart + let spacing = CGFloat(8.0) + + let legendWidth = fullLegendWidth + spacing + let legendHeight = _legend.neededHeight + _legend.textHeightMax + + let c = self.midPoint + + let bottomX = _legend.horizontalAlignment == .right + ? self.bounds.width - legendWidth + 15.0 + : legendWidth - 15.0 + let bottomY = legendHeight + 15 + let distLegend = distanceToCenter(x: bottomX, y: bottomY) + + let reference = getPosition(center: c, dist: self.radius, + angle: angleForPoint(x: bottomX, y: bottomY)) + + let distReference = distanceToCenter(x: reference.x, y: reference.y) + let minOffset = CGFloat(5.0) + + if bottomY >= c.y + && self.bounds.height - legendWidth > self.bounds.width + { + xLegendOffset = legendWidth + } + else if distLegend < distReference + { + let diff = distReference - distLegend + xLegendOffset = minOffset + diff + } + } + } + + switch _legend.horizontalAlignment + { + case .left: + legendLeft = xLegendOffset + + case .right: + legendRight = xLegendOffset + + case .center: + + switch _legend.verticalAlignment + { + case .top: + legendTop = min(_legend.neededHeight, _viewPortHandler.chartHeight * _legend.maxSizePercent) + + case .bottom: + legendBottom = min(_legend.neededHeight, _viewPortHandler.chartHeight * _legend.maxSizePercent) + + default: + break + } + } + + case .horizontal: + + var yLegendOffset: CGFloat = 0.0 + + if _legend.verticalAlignment == .top + || _legend.verticalAlignment == .bottom + { + // It's possible that we do not need this offset anymore as it + // is available through the extraOffsets, but changing it can mean + // changing default visibility for existing apps. + let yOffset = self.requiredLegendOffset + + yLegendOffset = min( + _legend.neededHeight + yOffset, + _viewPortHandler.chartHeight * _legend.maxSizePercent) + } + + switch _legend.verticalAlignment + { + case .top: + + legendTop = yLegendOffset + + case .bottom: + + legendBottom = yLegendOffset + + default: + break + } + } + + legendLeft += self.requiredBaseOffset + legendRight += self.requiredBaseOffset + legendTop += self.requiredBaseOffset + legendBottom += self.requiredBaseOffset + } + + legendTop += self.extraTopOffset + legendRight += self.extraRightOffset + legendBottom += self.extraBottomOffset + legendLeft += self.extraLeftOffset + + var minOffset = self.minOffset + + if self is RadarChartView + { + let x = self.xAxis + + if x.isEnabled && x.drawLabelsEnabled + { + minOffset = max(minOffset, x.labelRotatedWidth) + } + } + + let offsetLeft = max(minOffset, legendLeft) + let offsetTop = max(minOffset, legendTop) + let offsetRight = max(minOffset, legendRight) + let offsetBottom = max(minOffset, max(self.requiredBaseOffset, legendBottom)) + + _viewPortHandler.restrainViewPort(offsetLeft: offsetLeft, offsetTop: offsetTop, offsetRight: offsetRight, offsetBottom: offsetBottom) + } + + /// - Returns: The angle relative to the chart center for the given point on the chart in degrees. + /// The angle is always between 0 and 360°, 0° is NORTH, 90° is EAST, ... + @objc open func angleForPoint(x: CGFloat, y: CGFloat) -> CGFloat + { + let c = centerOffsets + + let tx = Double(x - c.x) + let ty = Double(y - c.y) + let length = sqrt(tx * tx + ty * ty) + let r = acos(ty / length) + + var angle = r.RAD2DEG + + if x > c.x + { + angle = 360.0 - angle + } + + // add 90° because chart starts EAST + angle = angle + 90.0 + + // neutralize overflow + if angle > 360.0 + { + angle = angle - 360.0 + } + + return CGFloat(angle) + } + + /// Calculates the position around a center point, depending on the distance + /// from the center, and the angle of the position around the center. + @objc open func getPosition(center: CGPoint, dist: CGFloat, angle: CGFloat) -> CGPoint + { + return CGPoint(x: center.x + dist * cos(angle.DEG2RAD), + y: center.y + dist * sin(angle.DEG2RAD)) + } + + /// - Returns: The distance of a certain point on the chart to the center of the chart. + @objc open func distanceToCenter(x: CGFloat, y: CGFloat) -> CGFloat + { + let c = self.centerOffsets + + var dist = CGFloat(0.0) + + var xDist = CGFloat(0.0) + var yDist = CGFloat(0.0) + + if x > c.x + { + xDist = x - c.x + } + else + { + xDist = c.x - x + } + + if y > c.y + { + yDist = y - c.y + } + else + { + yDist = c.y - y + } + + // pythagoras + dist = sqrt(pow(xDist, 2.0) + pow(yDist, 2.0)) + + return dist + } + + /// - Returns: The xIndex for the given angle around the center of the chart. + /// -1 if not found / outofbounds. + @objc open func indexForAngle(_ angle: CGFloat) -> Int + { + fatalError("indexForAngle() cannot be called on PieRadarChartViewBase") + } + + /// current rotation angle of the pie chart + /// + /// **default**: 270 --> top (NORTH) + /// Will always return a normalized value, which will be between 0.0 < 360.0 + @objc open var rotationAngle: CGFloat + { + get + { + return _rotationAngle + } + set + { + _rawRotationAngle = newValue + _rotationAngle = newValue.normalizedAngle + setNeedsDisplay() + } + } + + /// gets the raw version of the current rotation angle of the pie chart the returned value could be any value, negative or positive, outside of the 360 degrees. + /// this is used when working with rotation direction, mainly by gestures and animations. + @objc open var rawRotationAngle: CGFloat + { + return _rawRotationAngle + } + + /// The diameter of the pie- or radar-chart + @objc open var diameter: CGFloat + { + var content = _viewPortHandler.contentRect + content.origin.x += extraLeftOffset + content.origin.y += extraTopOffset + content.size.width -= extraLeftOffset + extraRightOffset + content.size.height -= extraTopOffset + extraBottomOffset + return min(content.width, content.height) + } + + /// The radius of the chart in pixels. + @objc open var radius: CGFloat + { + fatalError("radius cannot be called on PieRadarChartViewBase") + } + + /// The required offset for the chart legend. + internal var requiredLegendOffset: CGFloat + { + fatalError("requiredLegendOffset cannot be called on PieRadarChartViewBase") + } + + /// - Returns: The base offset needed for the chart without calculating the + /// legend size. + internal var requiredBaseOffset: CGFloat + { + fatalError("requiredBaseOffset cannot be called on PieRadarChartViewBase") + } + + open override var chartYMax: Double + { + return 0.0 + } + + open override var chartYMin: Double + { + return 0.0 + } + + @objc open var isRotationEnabled: Bool { return rotationEnabled } + + /// flag that indicates if rotation is done with two fingers or one. + /// when the chart is inside a scrollview, you need a two-finger rotation because a one-finger rotation eats up all touch events. + /// + /// On iOS this will disable one-finger rotation. + /// On OSX this will keep two-finger multitouch rotation, and one-pointer mouse rotation. + /// + /// **default**: false + @objc open var rotationWithTwoFingers: Bool + { + get + { + return _rotationWithTwoFingers + } + set + { + _rotationWithTwoFingers = newValue + #if !os(tvOS) + _rotationGestureRecognizer.isEnabled = _rotationWithTwoFingers + #endif + } + } + + /// flag that indicates if rotation is done with two fingers or one. + /// when the chart is inside a scrollview, you need a two-finger rotation because a one-finger rotation eats up all touch events. + /// + /// On iOS this will disable one-finger rotation. + /// On OSX this will keep two-finger multitouch rotation, and one-pointer mouse rotation. + /// + /// **default**: false + @objc open var isRotationWithTwoFingers: Bool + { + return _rotationWithTwoFingers + } + + // MARK: - Animation + + private var _spinAnimator: Animator! + + /// Applys a spin animation to the Chart. + @objc open func spin(duration: TimeInterval, fromAngle: CGFloat, toAngle: CGFloat, easing: ChartEasingFunctionBlock?) + { + if _spinAnimator != nil + { + _spinAnimator.stop() + } + + _spinAnimator = Animator() + _spinAnimator.updateBlock = { + self.rotationAngle = (toAngle - fromAngle) * CGFloat(self._spinAnimator.phaseX) + fromAngle + } + _spinAnimator.stopBlock = { self._spinAnimator = nil } + + _spinAnimator.animate(xAxisDuration: duration, easing: easing) + } + + @objc open func spin(duration: TimeInterval, fromAngle: CGFloat, toAngle: CGFloat, easingOption: ChartEasingOption) + { + spin(duration: duration, fromAngle: fromAngle, toAngle: toAngle, easing: easingFunctionFromOption(easingOption)) + } + + @objc open func spin(duration: TimeInterval, fromAngle: CGFloat, toAngle: CGFloat) + { + spin(duration: duration, fromAngle: fromAngle, toAngle: toAngle, easing: nil) + } + + @objc open func stopSpinAnimation() + { + if _spinAnimator != nil + { + _spinAnimator.stop() + } + } + + // MARK: - Gestures + + private var _rotationGestureStartPoint: CGPoint! + private var _isRotating = false + private var _startAngle = CGFloat(0.0) + + private struct AngularVelocitySample + { + var time: TimeInterval + var angle: CGFloat + } + + private var velocitySamples = [AngularVelocitySample]() + + private var _decelerationLastTime: TimeInterval = 0.0 + private var _decelerationDisplayLink: NSUIDisplayLink! + private var _decelerationAngularVelocity: CGFloat = 0.0 + + internal final func processRotationGestureBegan(location: CGPoint) + { + self.resetVelocity() + + if rotationEnabled + { + self.sampleVelocity(touchLocation: location) + } + + self.setGestureStartAngle(x: location.x, y: location.y) + + _rotationGestureStartPoint = location + } + + internal final func processRotationGestureMoved(location: CGPoint) + { + if isDragDecelerationEnabled + { + sampleVelocity(touchLocation: location) + } + + if !_isRotating && + distance( + eventX: location.x, + startX: _rotationGestureStartPoint.x, + eventY: location.y, + startY: _rotationGestureStartPoint.y) > CGFloat(8.0) + { + _isRotating = true + } + else + { + self.updateGestureRotation(x: location.x, y: location.y) + setNeedsDisplay() + } + } + + internal final func processRotationGestureEnded(location: CGPoint) + { + if isDragDecelerationEnabled + { + stopDeceleration() + + sampleVelocity(touchLocation: location) + + _decelerationAngularVelocity = calculateVelocity() + + if _decelerationAngularVelocity != 0.0 + { + _decelerationLastTime = CACurrentMediaTime() + _decelerationDisplayLink = NSUIDisplayLink(target: self, selector: #selector(PieRadarChartViewBase.decelerationLoop)) + _decelerationDisplayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common) + } + } + } + + internal final func processRotationGestureCancelled() + { + if _isRotating + { + _isRotating = false + } + } + + #if !os(OSX) + open override func nsuiTouchesBegan(_ touches: Set, withEvent event: NSUIEvent?) + { + // if rotation by touch is enabled + if rotationEnabled + { + stopDeceleration() + + if !rotationWithTwoFingers, let touchLocation = touches.first?.location(in: self) + { + processRotationGestureBegan(location: touchLocation) + } + } + + if !_isRotating + { + super.nsuiTouchesBegan(touches, withEvent: event) + } + } + + open override func nsuiTouchesMoved(_ touches: Set, withEvent event: NSUIEvent?) + { + if rotationEnabled && !rotationWithTwoFingers, let touch = touches.first + { + let touchLocation = touch.location(in: self) + processRotationGestureMoved(location: touchLocation) + } + + if !_isRotating + { + super.nsuiTouchesMoved(touches, withEvent: event) + } + } + + open override func nsuiTouchesEnded(_ touches: Set, withEvent event: NSUIEvent?) + { + if !_isRotating + { + super.nsuiTouchesEnded(touches, withEvent: event) + } + + if rotationEnabled && !rotationWithTwoFingers, let touch = touches.first + { + let touchLocation = touch.location(in: self) + processRotationGestureEnded(location: touchLocation) + } + + if _isRotating + { + _isRotating = false + } + } + + open override func nsuiTouchesCancelled(_ touches: Set?, withEvent event: NSUIEvent?) + { + super.nsuiTouchesCancelled(touches, withEvent: event) + + processRotationGestureCancelled() + } + #endif + + #if os(OSX) + open override func mouseDown(with theEvent: NSEvent) + { + // if rotation by touch is enabled + if rotationEnabled + { + stopDeceleration() + + let location = self.convert(theEvent.locationInWindow, from: nil) + + processRotationGestureBegan(location: location) + } + + if !_isRotating + { + super.mouseDown(with: theEvent) + } + } + + open override func mouseDragged(with theEvent: NSEvent) + { + if rotationEnabled + { + let location = self.convert(theEvent.locationInWindow, from: nil) + + processRotationGestureMoved(location: location) + } + + if !_isRotating + { + super.mouseDragged(with: theEvent) + } + } + + open override func mouseUp(with theEvent: NSEvent) + { + if !_isRotating + { + super.mouseUp(with: theEvent) + } + + if rotationEnabled + { + let location = self.convert(theEvent.locationInWindow, from: nil) + + processRotationGestureEnded(location: location) + } + + if _isRotating + { + _isRotating = false + } + } + #endif + + private func resetVelocity() + { + velocitySamples.removeAll(keepingCapacity: false) + } + + private func sampleVelocity(touchLocation: CGPoint) + { + let currentSample: AngularVelocitySample = { + let time = CACurrentMediaTime() + let angle = angleForPoint(x: touchLocation.x, y: touchLocation.y) + return AngularVelocitySample(time: time, angle: angle) + }() + + // Remove samples older than our sample time - 1 seconds + // while keeping at least one sample + + var i = 0, count = velocitySamples.count + while (i < count - 2) + { + if currentSample.time - velocitySamples[i].time > 1.0 + { + velocitySamples.remove(at: 0) + i -= 1 + count -= 1 + } + else + { + break + } + + i += 1 + } + + velocitySamples.append(currentSample) + } + + private func calculateVelocity() -> CGFloat + { + guard var firstSample = velocitySamples.first, + var lastSample = velocitySamples.last + else { return 0 } + + // Look for a sample that's closest to the latest sample, but not the same, so we can deduce the direction + let beforeLastSample = velocitySamples.last { $0.angle != lastSample.angle } + ?? firstSample + + // Calculate the sampling time + let timeDelta: CGFloat = { + let delta = CGFloat(lastSample.time - firstSample.time) + return delta == 0 ? 0.1 : delta + }() + + // Calculate clockwise/ccw by choosing two values that should be closest to each other, + // so if the angles are two far from each other we know they are inverted "for sure" + let isClockwise: Bool = { + let isClockwise = lastSample.angle >= beforeLastSample.angle + let isInverted = abs(lastSample.angle - beforeLastSample.angle) > 270.0 + return isInverted ? !isClockwise : isClockwise + }() + + // Now if the "gesture" is over a too big of an angle - then we know the angles are inverted, and we need to move them closer to each other from both sides of the 360.0 wrapping point + if lastSample.angle - firstSample.angle > 180.0 + { + firstSample.angle += 360.0 + } + else if firstSample.angle - lastSample.angle > 180.0 + { + lastSample.angle += 360.0 + } + + // The velocity + let velocity = abs((lastSample.angle - firstSample.angle) / timeDelta) + return isClockwise ? velocity : -velocity + } + + /// sets the starting angle of the rotation, this is only used by the touch listener, x and y is the touch position + private func setGestureStartAngle(x: CGFloat, y: CGFloat) + { + _startAngle = angleForPoint(x: x, y: y) + + // take the current angle into consideration when starting a new drag + _startAngle -= _rotationAngle + } + + /// updates the view rotation depending on the given touch position, also takes the starting angle into consideration + private func updateGestureRotation(x: CGFloat, y: CGFloat) + { + self.rotationAngle = angleForPoint(x: x, y: y) - _startAngle + } + + @objc open func stopDeceleration() + { + if _decelerationDisplayLink !== nil + { + _decelerationDisplayLink.remove(from: RunLoop.main, forMode: RunLoop.Mode.common) + _decelerationDisplayLink = nil + } + } + + @objc private func decelerationLoop() + { + let currentTime = CACurrentMediaTime() + + _decelerationAngularVelocity *= self.dragDecelerationFrictionCoef + + let timeInterval = CGFloat(currentTime - _decelerationLastTime) + + self.rotationAngle += _decelerationAngularVelocity * timeInterval + + _decelerationLastTime = currentTime + + if(abs(_decelerationAngularVelocity) < 0.001) + { + stopDeceleration() + } + } + + /// - Returns: The distance between two points + private func distance(eventX: CGFloat, startX: CGFloat, eventY: CGFloat, startY: CGFloat) -> CGFloat + { + let dx = eventX - startX + let dy = eventY - startY + return sqrt(dx * dx + dy * dy) + } + + /// - Returns: The distance between two points + private func distance(from: CGPoint, to: CGPoint) -> CGFloat + { + let dx = from.x - to.x + let dy = from.y - to.y + return sqrt(dx * dx + dy * dy) + } + + /// reference to the last highlighted object + private var _lastHighlight: Highlight! + + @objc private func tapGestureRecognized(_ recognizer: NSUITapGestureRecognizer) + { + if recognizer.state == NSUIGestureRecognizerState.ended + { + if !self.isHighLightPerTapEnabled { return } + + let location = recognizer.location(in: self) + + let high = self.getHighlightByTouchPoint(location) + self.highlightValue(high, callDelegate: true) + } + } + + #if !os(tvOS) + @objc private func rotationGestureRecognized(_ recognizer: NSUIRotationGestureRecognizer) + { + if recognizer.state == NSUIGestureRecognizerState.began + { + stopDeceleration() + + _startAngle = self.rawRotationAngle + } + + if recognizer.state == NSUIGestureRecognizerState.began || recognizer.state == NSUIGestureRecognizerState.changed + { + let angle = recognizer.nsuiRotation.RAD2DEG + + self.rotationAngle = _startAngle + angle + setNeedsDisplay() + } + else if recognizer.state == NSUIGestureRecognizerState.ended + { + let angle = recognizer.nsuiRotation.RAD2DEG + + self.rotationAngle = _startAngle + angle + setNeedsDisplay() + + if isDragDecelerationEnabled + { + stopDeceleration() + + _decelerationAngularVelocity = recognizer.velocity.RAD2DEG + + if _decelerationAngularVelocity != 0.0 + { + _decelerationLastTime = CACurrentMediaTime() + _decelerationDisplayLink = NSUIDisplayLink(target: self, selector: #selector(PieRadarChartViewBase.decelerationLoop)) + _decelerationDisplayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common) + } + } + } + } + #endif +} diff --git a/dydx/Pods/Charts/Source/Charts/Charts/RadarChartView.swift b/dydx/Pods/Charts/Source/Charts/Charts/RadarChartView.swift new file mode 100644 index 000000000..6bcad3361 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Charts/RadarChartView.swift @@ -0,0 +1,221 @@ +// +// RadarChartView.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +/// Implementation of the RadarChart, a "spidernet"-like chart. It works best +/// when displaying 5-10 entries per DataSet. +open class RadarChartView: PieRadarChartViewBase +{ + /// width of the web lines that come from the center. + @objc open var webLineWidth = CGFloat(1.5) + + /// width of the web lines that are in between the lines coming from the center + @objc open var innerWebLineWidth = CGFloat(0.75) + + /// color for the web lines that come from the center + @objc open var webColor = NSUIColor(red: 122/255.0, green: 122/255.0, blue: 122.0/255.0, alpha: 1.0) + + /// color for the web lines in between the lines that come from the center. + @objc open var innerWebColor = NSUIColor(red: 122/255.0, green: 122/255.0, blue: 122.0/255.0, alpha: 1.0) + + /// transparency the grid is drawn with (0.0 - 1.0) + @objc open var webAlpha: CGFloat = 150.0 / 255.0 + + /// flag indicating if the web lines should be drawn or not + @objc open var drawWeb = true + + /// modulus that determines how many labels and web-lines are skipped before the next is drawn + private var _skipWebLineCount = 0 + + /// the object reprsenting the y-axis labels + private var _yAxis: YAxis! + + internal var _yAxisRenderer: YAxisRendererRadarChart! + internal var _xAxisRenderer: XAxisRendererRadarChart! + + public override init(frame: CGRect) + { + super.init(frame: frame) + } + + public required init?(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder) + } + + internal override func initialize() + { + super.initialize() + + _yAxis = YAxis(position: .left) + _yAxis.labelXOffset = 10.0 + + renderer = RadarChartRenderer(chart: self, animator: _animator, viewPortHandler: _viewPortHandler) + + _yAxisRenderer = YAxisRendererRadarChart(viewPortHandler: _viewPortHandler, yAxis: _yAxis, chart: self) + _xAxisRenderer = XAxisRendererRadarChart(viewPortHandler: _viewPortHandler, xAxis: _xAxis, chart: self) + + self.highlighter = RadarHighlighter(chart: self) + } + + internal override func calcMinMax() + { + super.calcMinMax() + + guard let data = _data else { return } + + _yAxis.calculate(min: data.getYMin(axis: .left), max: data.getYMax(axis: .left)) + _xAxis.calculate(min: 0.0, max: Double(data.maxEntryCountSet?.entryCount ?? 0)) + } + + open override func notifyDataSetChanged() + { + calcMinMax() + + _yAxisRenderer?.computeAxis(min: _yAxis._axisMinimum, max: _yAxis._axisMaximum, inverted: _yAxis.isInverted) + _xAxisRenderer?.computeAxis(min: _xAxis._axisMinimum, max: _xAxis._axisMaximum, inverted: false) + + if let data = _data, + let legend = _legend, + !legend.isLegendCustom + { + legendRenderer?.computeLegend(data: data) + } + + calculateOffsets() + + setNeedsDisplay() + } + + open override func draw(_ rect: CGRect) + { + super.draw(rect) + + guard data != nil, let renderer = renderer else { return } + + let optionalContext = NSUIGraphicsGetCurrentContext() + guard let context = optionalContext else { return } + + if _xAxis.isEnabled + { + _xAxisRenderer.computeAxis(min: _xAxis._axisMinimum, max: _xAxis._axisMaximum, inverted: false) + } + + _xAxisRenderer?.renderAxisLabels(context: context) + + if drawWeb + { + renderer.drawExtras(context: context) + } + + if _yAxis.isEnabled && _yAxis.isDrawLimitLinesBehindDataEnabled + { + _yAxisRenderer.renderLimitLines(context: context) + } + + renderer.drawData(context: context) + + if valuesToHighlight() + { + renderer.drawHighlighted(context: context, indices: _indicesToHighlight) + } + + if _yAxis.isEnabled && !_yAxis.isDrawLimitLinesBehindDataEnabled + { + _yAxisRenderer.renderLimitLines(context: context) + } + + _yAxisRenderer.renderAxisLabels(context: context) + + renderer.drawValues(context: context) + + legendRenderer.renderLegend(context: context) + + drawDescription(context: context) + + drawMarkers(context: context) + } + + /// The factor that is needed to transform values into pixels. + @objc open var factor: CGFloat + { + let content = _viewPortHandler.contentRect + return min(content.width / 2.0, content.height / 2.0) + / CGFloat(_yAxis.axisRange) + } + + /// The angle that each slice in the radar chart occupies. + @objc open var sliceAngle: CGFloat + { + return 360.0 / CGFloat(_data?.maxEntryCountSet?.entryCount ?? 0) + } + + open override func indexForAngle(_ angle: CGFloat) -> Int + { + // take the current angle of the chart into consideration + let a = (angle - self.rotationAngle).normalizedAngle + + let sliceAngle = self.sliceAngle + + let max = _data?.maxEntryCountSet?.entryCount ?? 0 + return (0.. a + } ?? max + } + + /// The object that represents all y-labels of the RadarChart. + @objc open var yAxis: YAxis + { + return _yAxis + } + + /// Sets the number of web-lines that should be skipped on chart web before the next one is drawn. This targets the lines that come from the center of the RadarChart. + /// if count = 1 -> 1 line is skipped in between + @objc open var skipWebLineCount: Int + { + get + { + return _skipWebLineCount + } + set + { + _skipWebLineCount = max(0, newValue) + } + } + + internal override var requiredLegendOffset: CGFloat + { + return _legend.font.pointSize * 4.0 + } + + internal override var requiredBaseOffset: CGFloat + { + return _xAxis.isEnabled && _xAxis.isDrawLabelsEnabled ? _xAxis.labelRotatedWidth : 10.0 + } + + open override var radius: CGFloat + { + let content = _viewPortHandler.contentRect + return min(content.width / 2.0, content.height / 2.0) + } + + /// The maximum value this chart can display on it's y-axis. + open override var chartYMax: Double { return _yAxis._axisMaximum } + + /// The minimum value this chart can display on it's y-axis. + open override var chartYMin: Double { return _yAxis._axisMinimum } + + /// The range of y-values this chart can display. + @objc open var yRange: Double { return _yAxis.axisRange } +} diff --git a/dydx/Pods/Charts/Source/Charts/Charts/ScatterChartView.swift b/dydx/Pods/Charts/Source/Charts/Charts/ScatterChartView.swift new file mode 100644 index 000000000..22c710ad8 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Charts/ScatterChartView.swift @@ -0,0 +1,31 @@ +// +// ScatterChartView.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +/// The ScatterChart. Draws dots, triangles, squares and custom shapes into the chartview. +open class ScatterChartView: BarLineChartViewBase, ScatterChartDataProvider +{ + open override func initialize() + { + super.initialize() + + renderer = ScatterChartRenderer(dataProvider: self, animator: _animator, viewPortHandler: _viewPortHandler) + + xAxis.spaceMin = 0.5 + xAxis.spaceMax = 0.5 + } + + // MARK: - ScatterChartDataProvider + + open var scatterData: ScatterChartData? { return _data as? ScatterChartData } +} diff --git a/dydx/Pods/Charts/Source/Charts/Components/AxisBase.swift b/dydx/Pods/Charts/Source/Charts/Components/AxisBase.swift new file mode 100644 index 000000000..0b59628d4 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Components/AxisBase.swift @@ -0,0 +1,372 @@ +// +// AxisBase.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +/// Base class for all axes +@objc(ChartAxisBase) +open class AxisBase: ComponentBase +{ + public override init() + { + super.init() + } + + /// Custom formatter that is used instead of the auto-formatter if set + private var _axisValueFormatter: IAxisValueFormatter? + + @objc open var labelFont = NSUIFont.systemFont(ofSize: 10.0) + @objc open var labelTextColor = NSUIColor.labelOrBlack + + @objc open var axisLineColor = NSUIColor.gray + @objc open var axisLineWidth = CGFloat(0.5) + @objc open var axisLineDashPhase = CGFloat(0.0) + @objc open var axisLineDashLengths: [CGFloat]! + + @objc open var gridColor = NSUIColor.gray.withAlphaComponent(0.9) + @objc open var gridLineWidth = CGFloat(0.5) + @objc open var gridLineDashPhase = CGFloat(0.0) + @objc open var gridLineDashLengths: [CGFloat]! + @objc open var gridLineCap = CGLineCap.butt + + @objc open var drawGridLinesEnabled = true + @objc open var drawAxisLineEnabled = true + + /// flag that indicates of the labels of this axis should be drawn or not + @objc open var drawLabelsEnabled = true + + private var _centerAxisLabelsEnabled = false + + /// Centers the axis labels instead of drawing them at their original position. + /// This is useful especially for grouped BarChart. + @objc open var centerAxisLabelsEnabled: Bool + { + get { return _centerAxisLabelsEnabled && entryCount > 0 } + set { _centerAxisLabelsEnabled = newValue } + } + + @objc open var isCenterAxisLabelsEnabled: Bool + { + get { return centerAxisLabelsEnabled } + } + + /// array of limitlines that can be set for the axis + private var _limitLines = [ChartLimitLine]() + + /// Are the LimitLines drawn behind the data or in front of the data? + /// + /// **default**: false + @objc open var drawLimitLinesBehindDataEnabled = false + + /// Are the grid lines drawn behind the data or in front of the data? + /// + /// **default**: true + @objc open var drawGridLinesBehindDataEnabled = true + + /// the flag can be used to turn off the antialias for grid lines + @objc open var gridAntialiasEnabled = true + + /// the actual array of entries + @objc open var entries = [Double]() + + /// axis label entries only used for centered labels + @objc open var centeredEntries = [Double]() + + /// the number of entries the legend contains + @objc open var entryCount: Int { return entries.count } + + /// the number of label entries the axis should have + /// + /// **default**: 6 + private var _labelCount = Int(6) + + /// the number of decimal digits to use (for the default formatter + @objc open var decimals: Int = 0 + + /// When true, axis labels are controlled by the `granularity` property. + /// When false, axis values could possibly be repeated. + /// This could happen if two adjacent axis values are rounded to same value. + /// If using granularity this could be avoided by having fewer axis values visible. + @objc open var granularityEnabled = false + + private var _granularity = Double(1.0) + + /// The minimum interval between axis values. + /// This can be used to avoid label duplicating when zooming in. + /// + /// **default**: 1.0 + @objc open var granularity: Double + { + get + { + return _granularity + } + set + { + _granularity = newValue + + // set this to `true` if it was disabled, as it makes no sense to set this property with granularity disabled + granularityEnabled = true + } + } + + /// The minimum interval between axis values. + @objc open var isGranularityEnabled: Bool + { + get + { + return granularityEnabled + } + } + + /// if true, the set number of y-labels will be forced + @objc open var forceLabelsEnabled = false + + @objc open func getLongestLabel() -> String + { + var longest = "" + + for i in 0 ..< entries.count + { + let text = getFormattedLabel(i) + + if longest.count < text.count + { + longest = text + } + } + + return longest + } + + /// - Returns: The formatted label at the specified index. This will either use the auto-formatter or the custom formatter (if one is set). + @objc open func getFormattedLabel(_ index: Int) -> String + { + if index < 0 || index >= entries.count + { + return "" + } + + return valueFormatter?.stringForValue(entries[index], axis: self) ?? "" + } + + /// Sets the formatter to be used for formatting the axis labels. + /// If no formatter is set, the chart will automatically determine a reasonable formatting (concerning decimals) for all the values that are drawn inside the chart. + /// Use `nil` to use the formatter calculated by the chart. + @objc open var valueFormatter: IAxisValueFormatter? + { + get + { + if _axisValueFormatter == nil + { + _axisValueFormatter = DefaultAxisValueFormatter(decimals: decimals) + } + else if _axisValueFormatter is DefaultAxisValueFormatter && + (_axisValueFormatter as! DefaultAxisValueFormatter).hasAutoDecimals && + (_axisValueFormatter as! DefaultAxisValueFormatter).decimals != decimals + { + (self._axisValueFormatter as! DefaultAxisValueFormatter).decimals = self.decimals + } + + return _axisValueFormatter + } + set + { + _axisValueFormatter = newValue ?? DefaultAxisValueFormatter(decimals: decimals) + } + } + + @objc open var isDrawGridLinesEnabled: Bool { return drawGridLinesEnabled } + + @objc open var isDrawAxisLineEnabled: Bool { return drawAxisLineEnabled } + + @objc open var isDrawLabelsEnabled: Bool { return drawLabelsEnabled } + + /// Are the LimitLines drawn behind the data or in front of the data? + /// + /// **default**: false + @objc open var isDrawLimitLinesBehindDataEnabled: Bool { return drawLimitLinesBehindDataEnabled } + + /// Are the grid lines drawn behind the data or in front of the data? + /// + /// **default**: true + @objc open var isDrawGridLinesBehindDataEnabled: Bool { return drawGridLinesBehindDataEnabled } + + /// Extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum` + @objc open var spaceMin: Double = 0.0 + + /// Extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum` + @objc open var spaceMax: Double = 0.0 + + /// Flag indicating that the axis-min value has been customized + internal var _customAxisMin: Bool = false + + /// Flag indicating that the axis-max value has been customized + internal var _customAxisMax: Bool = false + + /// Do not touch this directly, instead, use axisMinimum. + /// This is automatically calculated to represent the real min value, + /// and is used when calculating the effective minimum. + internal var _axisMinimum = Double(0) + + /// Do not touch this directly, instead, use axisMaximum. + /// This is automatically calculated to represent the real max value, + /// and is used when calculating the effective maximum. + internal var _axisMaximum = Double(0) + + /// the total range of values this axis covers + @objc open var axisRange = Double(0) + + /// The minumum number of labels on the axis + @objc open var axisMinLabels = Int(2) { + didSet { axisMinLabels = axisMinLabels > 0 ? axisMinLabels : oldValue } + } + + /// The maximum number of labels on the axis + @objc open var axisMaxLabels = Int(25) { + didSet { axisMaxLabels = axisMaxLabels > 0 ? axisMaxLabels : oldValue } + } + + /// the number of label entries the axis should have + /// max = 25, + /// min = 2, + /// default = 6, + /// be aware that this number is not fixed and can only be approximated + @objc open var labelCount: Int + { + get + { + return _labelCount + } + set + { + let range = axisMinLabels...axisMaxLabels as ClosedRange + _labelCount = newValue.clamped(to: range) + + forceLabelsEnabled = false + } + } + + @objc open func setLabelCount(_ count: Int, force: Bool) + { + self.labelCount = count + forceLabelsEnabled = force + } + + /// `true` if focing the y-label count is enabled. Default: false + @objc open var isForceLabelsEnabled: Bool { return forceLabelsEnabled } + + /// Adds a new ChartLimitLine to this axis. + @objc open func addLimitLine(_ line: ChartLimitLine) + { + _limitLines.append(line) + } + + /// Removes the specified ChartLimitLine from the axis. + @objc open func removeLimitLine(_ line: ChartLimitLine) + { + guard let i = _limitLines.firstIndex(of: line) else { return } + _limitLines.remove(at: i) + } + + /// Removes all LimitLines from the axis. + @objc open func removeAllLimitLines() + { + _limitLines.removeAll(keepingCapacity: false) + } + + /// The LimitLines of this axis. + @objc open var limitLines : [ChartLimitLine] + { + return _limitLines + } + + // MARK: Custom axis ranges + + /// By calling this method, any custom minimum value that has been previously set is reseted, and the calculation is done automatically. + @objc open func resetCustomAxisMin() + { + _customAxisMin = false + } + + @objc open var isAxisMinCustom: Bool { return _customAxisMin } + + /// By calling this method, any custom maximum value that has been previously set is reseted, and the calculation is done automatically. + @objc open func resetCustomAxisMax() + { + _customAxisMax = false + } + + @objc open var isAxisMaxCustom: Bool { return _customAxisMax } + + /// The minimum value for this axis. + /// If set, this value will not be calculated automatically depending on the provided data. + /// Use `resetCustomAxisMin()` to undo this. + @objc open var axisMinimum: Double + { + get + { + return _axisMinimum + } + set + { + _customAxisMin = true + _axisMinimum = newValue + axisRange = abs(_axisMaximum - newValue) + } + } + + /// The maximum value for this axis. + /// If set, this value will not be calculated automatically depending on the provided data. + /// Use `resetCustomAxisMax()` to undo this. + @objc open var axisMaximum: Double + { + get + { + return _axisMaximum + } + set + { + _customAxisMax = true + _axisMaximum = newValue + axisRange = abs(newValue - _axisMinimum) + } + } + + /// Calculates the minimum, maximum and range values of the YAxis with the given minimum and maximum values from the chart data. + /// + /// - Parameters: + /// - dataMin: the y-min value according to chart data + /// - dataMax: the y-max value according to chart + @objc open func calculate(min dataMin: Double, max dataMax: Double) + { + // if custom, use value as is, else use data value + var min = _customAxisMin ? _axisMinimum : (dataMin - spaceMin) + var max = _customAxisMax ? _axisMaximum : (dataMax + spaceMax) + + // temporary range (before calculations) + let range = abs(max - min) + + // in case all values are equal + if range == 0.0 + { + max = max + 1.0 + min = min - 1.0 + } + + _axisMinimum = min + _axisMaximum = max + + // actual range + axisRange = abs(max - min) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Components/ChartLimitLine.swift b/dydx/Pods/Charts/Source/Charts/Components/ChartLimitLine.swift new file mode 100644 index 000000000..407c7cd4c --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Components/ChartLimitLine.swift @@ -0,0 +1,74 @@ +// +// ChartLimitLine.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +/// The limit line is an additional feature for all Line, Bar and ScatterCharts. +/// It allows the displaying of an additional line in the chart that marks a certain maximum / limit on the specified axis (x- or y-axis). +open class ChartLimitLine: ComponentBase +{ + @objc(ChartLimitLabelPosition) + public enum LabelPosition: Int + { + case topLeft + case topRight + case bottomLeft + case bottomRight + } + + /// limit / maximum (the y-value or xIndex) + @objc open var limit = Double(0.0) + + private var _lineWidth = CGFloat(2.0) + @objc open var lineColor = NSUIColor(red: 237.0/255.0, green: 91.0/255.0, blue: 91.0/255.0, alpha: 1.0) + @objc open var lineDashPhase = CGFloat(0.0) + @objc open var lineDashLengths: [CGFloat]? + + @objc open var valueTextColor = NSUIColor.labelOrBlack + @objc open var valueFont = NSUIFont.systemFont(ofSize: 13.0) + + @objc open var drawLabelEnabled = true + @objc open var label = "" + @objc open var labelPosition = LabelPosition.topRight + + public override init() + { + super.init() + } + + @objc public init(limit: Double) + { + super.init() + self.limit = limit + } + + @objc public init(limit: Double, label: String) + { + super.init() + self.limit = limit + self.label = label + } + + /// set the line width of the chart (min = 0.2, max = 12); default 2 + @objc open var lineWidth: CGFloat + { + get + { + return _lineWidth + } + set + { + _lineWidth = newValue.clamped(to: 0.2...12) + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Components/ComponentBase.swift b/dydx/Pods/Charts/Source/Charts/Components/ComponentBase.swift new file mode 100644 index 000000000..6014a1756 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Components/ComponentBase.swift @@ -0,0 +1,37 @@ +// +// ComponentBase.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +/// This class encapsulates everything both Axis, Legend and LimitLines have in common +@objc(ChartComponentBase) +open class ComponentBase: NSObject +{ + /// flag that indicates if this component is enabled or not + @objc open var enabled = true + + /// The offset this component has on the x-axis + /// **default**: 5.0 + @objc open var xOffset = CGFloat(5.0) + + /// The offset this component has on the x-axis + /// **default**: 5.0 (or 0.0 on ChartYAxis) + @objc open var yOffset = CGFloat(5.0) + + public override init() + { + super.init() + } + + @objc open var isEnabled: Bool { return enabled } +} diff --git a/dydx/Pods/Charts/Source/Charts/Components/Description.swift b/dydx/Pods/Charts/Source/Charts/Components/Description.swift new file mode 100644 index 000000000..0288931c0 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Components/Description.swift @@ -0,0 +1,54 @@ +// +// Description.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + +@objc(ChartDescription) +open class Description: ComponentBase +{ + public override init() + { + #if os(tvOS) + // 23 is the smallest recommended font size on the TV + font = .systemFont(ofSize: 23) + #elseif os(OSX) + font = .systemFont(ofSize: NSUIFont.systemFontSize) + #else + font = .systemFont(ofSize: 8.0) + #endif + + super.init() + } + + /// The text to be shown as the description. + @objc open var text: String? + + /// Custom position for the description text in pixels on the screen. + open var position: CGPoint? = nil + + /// The text alignment of the description text. Default RIGHT. + @objc open var textAlign: NSTextAlignment = NSTextAlignment.right + + /// Font object used for drawing the description text. + @objc open var font: NSUIFont + + /// Text color used for drawing the description text + @objc open var textColor = NSUIColor.labelOrBlack +} diff --git a/dydx/Pods/Charts/Source/Charts/Components/IMarker.swift b/dydx/Pods/Charts/Source/Charts/Components/IMarker.swift new file mode 100644 index 000000000..a4b752609 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Components/IMarker.swift @@ -0,0 +1,39 @@ +// +// ChartMarker.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(IChartMarker) +public protocol IMarker: class +{ + /// - Returns: The desired (general) offset you wish the IMarker to have on the x-axis. + /// By returning x: -(width / 2) you will center the IMarker horizontally. + /// By returning y: -(height / 2) you will center the IMarker vertically. + var offset: CGPoint { get } + + /// - Parameters: + /// - point: This is the point at which the marker wants to be drawn. You can adjust the offset conditionally based on this argument. + /// - Returns: The offset for drawing at the specific `point`. + /// This allows conditional adjusting of the Marker position. + /// If you have no adjustments to make, return self.offset(). + func offsetForDrawing(atPoint: CGPoint) -> CGPoint + + /// This method enables a custom IMarker to update it's content every time the IMarker is redrawn according to the data entry it points to. + /// + /// - Parameters: + /// - entry: The Entry the IMarker belongs to. This can also be any subclass of Entry, like BarEntry or CandleEntry, simply cast it at runtime. + /// - highlight: The highlight object contains information about the highlighted value such as it's dataset-index, the selected range or stack-index (only stacked bar entries). + func refreshContent(entry: ChartDataEntry, highlight: Highlight) + + /// Draws the IMarker on the given position on the given context + func draw(context: CGContext, point: CGPoint) +} diff --git a/dydx/Pods/Charts/Source/Charts/Components/Legend.swift b/dydx/Pods/Charts/Source/Charts/Components/Legend.swift new file mode 100644 index 000000000..2a92f4eba --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Components/Legend.swift @@ -0,0 +1,420 @@ +// +// Legend.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(ChartLegend) +open class Legend: ComponentBase +{ + @objc(ChartLegendForm) + public enum Form: Int + { + /// Avoid drawing a form + case none + + /// Do not draw the a form, but leave space for it + case empty + + /// Use default (default dataset's form to the legend's form) + case `default` + + /// Draw a square + case square + + /// Draw a circle + case circle + + /// Draw a horizontal line + case line + } + + @objc(ChartLegendHorizontalAlignment) + public enum HorizontalAlignment: Int + { + case left + case center + case right + } + + @objc(ChartLegendVerticalAlignment) + public enum VerticalAlignment: Int + { + case top + case center + case bottom + } + + @objc(ChartLegendOrientation) + public enum Orientation: Int + { + case horizontal + case vertical + } + + @objc(ChartLegendDirection) + public enum Direction: Int + { + case leftToRight + case rightToLeft + } + + /// The legend entries array + @objc open var entries = [LegendEntry]() + + /// Entries that will be appended to the end of the auto calculated entries after calculating the legend. + /// (if the legend has already been calculated, you will need to call notifyDataSetChanged() to let the changes take effect) + @objc open var extraEntries = [LegendEntry]() + + /// Are the legend labels/colors a custom value or auto calculated? If false, then it's auto, if true, then custom. + /// + /// **default**: false (automatic legend) + private var _isLegendCustom = false + + /// The horizontal alignment of the legend + @objc open var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.left + + /// The vertical alignment of the legend + @objc open var verticalAlignment: VerticalAlignment = VerticalAlignment.bottom + + /// The orientation of the legend + @objc open var orientation: Orientation = Orientation.horizontal + + /// Flag indicating whether the legend will draw inside the chart or outside + @objc open var drawInside: Bool = false + + /// Flag indicating whether the legend will draw inside the chart or outside + @objc open var isDrawInsideEnabled: Bool { return drawInside } + + /// The text direction of the legend + @objc open var direction: Direction = Direction.leftToRight + + @objc open var font: NSUIFont = NSUIFont.systemFont(ofSize: 10.0) + @objc open var textColor = NSUIColor.labelOrBlack + + /// The form/shape of the legend forms + @objc open var form = Form.square + + /// The size of the legend forms + @objc open var formSize = CGFloat(8.0) + + /// The line width for forms that consist of lines + @objc open var formLineWidth = CGFloat(3.0) + + /// Line dash configuration for shapes that consist of lines. + /// + /// This is how much (in pixels) into the dash pattern are we starting from. + @objc open var formLineDashPhase: CGFloat = 0.0 + + /// Line dash configuration for shapes that consist of lines. + /// + /// This is the actual dash pattern. + /// I.e. [2, 3] will paint [-- -- ] + /// [1, 3, 4, 2] will paint [- ---- - ---- ] + @objc open var formLineDashLengths: [CGFloat]? + + @objc open var xEntrySpace = CGFloat(6.0) + @objc open var yEntrySpace = CGFloat(0.0) + @objc open var formToTextSpace = CGFloat(5.0) + @objc open var stackSpace = CGFloat(3.0) + + @objc open var calculatedLabelSizes = [CGSize]() + @objc open var calculatedLabelBreakPoints = [Bool]() + @objc open var calculatedLineSizes = [CGSize]() + + public override init() + { + super.init() + + self.xOffset = 5.0 + self.yOffset = 3.0 + } + + @objc public init(entries: [LegendEntry]) + { + super.init() + + self.entries = entries + } + + @objc open func getMaximumEntrySize(withFont font: NSUIFont) -> CGSize + { + var maxW = CGFloat(0.0) + var maxH = CGFloat(0.0) + + var maxFormSize: CGFloat = 0.0 + + for entry in entries + { + let formSize = entry.formSize.isNaN ? self.formSize : entry.formSize + if formSize > maxFormSize + { + maxFormSize = formSize + } + + guard let label = entry.label + else { continue } + + let size = (label as NSString).size(withAttributes: [.font: font]) + + if size.width > maxW + { + maxW = size.width + } + if size.height > maxH + { + maxH = size.height + } + } + + return CGSize( + width: maxW + maxFormSize + formToTextSpace, + height: maxH + ) + } + + @objc open var neededWidth = CGFloat(0.0) + @objc open var neededHeight = CGFloat(0.0) + @objc open var textWidthMax = CGFloat(0.0) + @objc open var textHeightMax = CGFloat(0.0) + + /// flag that indicates if word wrapping is enabled + /// this is currently supported only for `orientation == Horizontal`. + /// you may want to set maxSizePercent when word wrapping, to set the point where the text wraps. + /// + /// **default**: true + @objc open var wordWrapEnabled = true + + /// if this is set, then word wrapping the legend is enabled. + @objc open var isWordWrapEnabled: Bool { return wordWrapEnabled } + + /// The maximum relative size out of the whole chart view in percent. + /// If the legend is to the right/left of the chart, then this affects the width of the legend. + /// If the legend is to the top/bottom of the chart, then this affects the height of the legend. + /// + /// **default**: 0.95 (95%) + @objc open var maxSizePercent: CGFloat = 0.95 + + @objc open func calculateDimensions(labelFont: NSUIFont, viewPortHandler: ViewPortHandler) + { + let maxEntrySize = getMaximumEntrySize(withFont: labelFont) + let defaultFormSize = self.formSize + let stackSpace = self.stackSpace + let formToTextSpace = self.formToTextSpace + let xEntrySpace = self.xEntrySpace + let yEntrySpace = self.yEntrySpace + let wordWrapEnabled = self.wordWrapEnabled + let entries = self.entries + let entryCount = entries.count + + textWidthMax = maxEntrySize.width + textHeightMax = maxEntrySize.height + + switch orientation + { + case .vertical: + + var maxWidth = CGFloat(0.0) + var width = CGFloat(0.0) + var maxHeight = CGFloat(0.0) + let labelLineHeight = labelFont.lineHeight + + var wasStacked = false + + for i in 0 ..< entryCount + { + let e = entries[i] + let drawingForm = e.form != .none + let formSize = e.formSize.isNaN ? defaultFormSize : e.formSize + let label = e.label + + if !wasStacked + { + width = 0.0 + } + + if drawingForm + { + if wasStacked + { + width += stackSpace + } + width += formSize + } + + if label != nil + { + let size = (label! as NSString).size(withAttributes: [.font: labelFont]) + + if drawingForm && !wasStacked + { + width += formToTextSpace + } + else if wasStacked + { + maxWidth = max(maxWidth, width) + maxHeight += labelLineHeight + yEntrySpace + width = 0.0 + wasStacked = false + } + + width += size.width + maxHeight += labelLineHeight + yEntrySpace + } + else + { + wasStacked = true + width += formSize + + if i < entryCount - 1 + { + width += stackSpace + } + } + + maxWidth = max(maxWidth, width) + } + + neededWidth = maxWidth + neededHeight = maxHeight + + case .horizontal: + + let labelLineHeight = labelFont.lineHeight + + let contentWidth: CGFloat = viewPortHandler.contentWidth * maxSizePercent + + // Prepare arrays for calculated layout + if calculatedLabelSizes.count != entryCount + { + calculatedLabelSizes = [CGSize](repeating: CGSize(), count: entryCount) + } + + if calculatedLabelBreakPoints.count != entryCount + { + calculatedLabelBreakPoints = [Bool](repeating: false, count: entryCount) + } + + calculatedLineSizes.removeAll(keepingCapacity: true) + + // Start calculating layout + + let labelAttrs = [NSAttributedString.Key.font: labelFont] + var maxLineWidth: CGFloat = 0.0 + var currentLineWidth: CGFloat = 0.0 + var requiredWidth: CGFloat = 0.0 + var stackedStartIndex: Int = -1 + + for i in 0 ..< entryCount + { + let e = entries[i] + let drawingForm = e.form != .none + let label = e.label + + calculatedLabelBreakPoints[i] = false + + if stackedStartIndex == -1 + { + // we are not stacking, so required width is for this label only + requiredWidth = 0.0 + } + else + { + // add the spacing appropriate for stacked labels/forms + requiredWidth += stackSpace + } + + // grouped forms have null labels + if label != nil + { + calculatedLabelSizes[i] = (label! as NSString).size(withAttributes: labelAttrs) + requiredWidth += drawingForm ? formToTextSpace + formSize : 0.0 + requiredWidth += calculatedLabelSizes[i].width + } + else + { + calculatedLabelSizes[i] = CGSize() + requiredWidth += drawingForm ? formSize : 0.0 + + if stackedStartIndex == -1 + { + // mark this index as we might want to break here later + stackedStartIndex = i + } + } + + if label != nil || i == entryCount - 1 + { + let requiredSpacing = currentLineWidth == 0.0 ? 0.0 : xEntrySpace + + if (!wordWrapEnabled || // No word wrapping, it must fit. + currentLineWidth == 0.0 || // The line is empty, it must fit. + (contentWidth - currentLineWidth >= requiredSpacing + requiredWidth)) // It simply fits + { + // Expand current line + currentLineWidth += requiredSpacing + requiredWidth + } + else + { // It doesn't fit, we need to wrap a line + + // Add current line size to array + calculatedLineSizes.append(CGSize(width: currentLineWidth, height: labelLineHeight)) + maxLineWidth = max(maxLineWidth, currentLineWidth) + + // Start a new line + calculatedLabelBreakPoints[stackedStartIndex > -1 ? stackedStartIndex : i] = true + currentLineWidth = requiredWidth + } + + if i == entryCount - 1 + { // Add last line size to array + calculatedLineSizes.append(CGSize(width: currentLineWidth, height: labelLineHeight)) + maxLineWidth = max(maxLineWidth, currentLineWidth) + } + } + + stackedStartIndex = label != nil ? -1 : stackedStartIndex + } + + neededWidth = maxLineWidth + neededHeight = labelLineHeight * CGFloat(calculatedLineSizes.count) + + yEntrySpace * CGFloat(calculatedLineSizes.count == 0 ? 0 : (calculatedLineSizes.count - 1)) + } + + neededWidth += xOffset + neededHeight += yOffset + } + + /// MARK: - Custom legend + + /// Sets a custom legend's entries array. + /// * A nil label will start a group. + /// This will disable the feature that automatically calculates the legend entries from the datasets. + /// Call `resetCustom(...)` to re-enable automatic calculation (and then `notifyDataSetChanged()` is needed). + @objc open func setCustom(entries: [LegendEntry]) + { + self.entries = entries + _isLegendCustom = true + } + + /// Calling this will disable the custom legend entries (set by `setLegend(...)`). Instead, the entries will again be calculated automatically (after `notifyDataSetChanged()` is called). + @objc open func resetCustom() + { + _isLegendCustom = false + } + + /// **default**: false (automatic legend) + /// `true` if a custom legend entries has been set + @objc open var isLegendCustom: Bool + { + return _isLegendCustom + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Components/LegendEntry.swift b/dydx/Pods/Charts/Source/Charts/Components/LegendEntry.swift new file mode 100644 index 000000000..5868137ef --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Components/LegendEntry.swift @@ -0,0 +1,88 @@ +// +// LegendEntry.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(ChartLegendEntry) +open class LegendEntry: NSObject +{ + public override init() + { + super.init() + } + + /// - Parameters: + /// - label: The legend entry text. + /// A `nil` label will start a group. + /// - form: The form to draw for this entry. + /// - formSize: Set to NaN to use the legend's default. + /// - formLineWidth: Set to NaN to use the legend's default. + /// - formLineDashPhase: Line dash configuration. + /// - formLineDashLengths: Line dash configurationas NaN to use the legend's default. + /// - formColor: The color for drawing the form. + @objc public init(label: String?, + form: Legend.Form, + formSize: CGFloat, + formLineWidth: CGFloat, + formLineDashPhase: CGFloat, + formLineDashLengths: [CGFloat]?, + formColor: NSUIColor?) + { + self.label = label + self.form = form + self.formSize = formSize + self.formLineWidth = formLineWidth + self.formLineDashPhase = formLineDashPhase + self.formLineDashLengths = formLineDashLengths + self.formColor = formColor + } + + /// The legend entry text. + /// A `nil` label will start a group. + @objc open var label: String? + + /// The form to draw for this entry. + /// + /// `None` will avoid drawing a form, and any related space. + /// `Empty` will avoid drawing a form, but keep its space. + /// `Default` will use the Legend's default. + @objc open var form: Legend.Form = .default + + /// Form size will be considered except for when .None is used + /// + /// Set as NaN to use the legend's default + @objc open var formSize: CGFloat = CGFloat.nan + + /// Line width used for shapes that consist of lines. + /// + /// Set to NaN to use the legend's default. + @objc open var formLineWidth: CGFloat = CGFloat.nan + + /// Line dash configuration for shapes that consist of lines. + /// + /// This is how much (in pixels) into the dash pattern are we starting from. + /// + /// Set to NaN to use the legend's default. + @objc open var formLineDashPhase: CGFloat = 0.0 + + /// Line dash configuration for shapes that consist of lines. + /// + /// This is the actual dash pattern. + /// I.e. [2, 3] will paint [-- -- ] + /// [1, 3, 4, 2] will paint [- ---- - ---- ] + /// + /// Set to nil to use the legend's default. + @objc open var formLineDashLengths: [CGFloat]? + + /// The color for drawing the form + @objc open var formColor: NSUIColor? +} diff --git a/dydx/Pods/Charts/Source/Charts/Components/MarkerImage.swift b/dydx/Pods/Charts/Source/Charts/Components/MarkerImage.swift new file mode 100644 index 000000000..341b1186c --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Components/MarkerImage.swift @@ -0,0 +1,106 @@ +// +// ChartMarkerImage.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(ChartMarkerImage) +open class MarkerImage: NSObject, IMarker +{ + /// The marker image to render + @objc open var image: NSUIImage? + + open var offset: CGPoint = CGPoint() + + @objc open weak var chartView: ChartViewBase? + + /// As long as size is 0.0/0.0 - it will default to the image's size + @objc open var size: CGSize = CGSize() + + public override init() + { + super.init() + } + + open func offsetForDrawing(atPoint point: CGPoint) -> CGPoint + { + var offset = self.offset + + let chart = self.chartView + + var size = self.size + + if size.width == 0.0 && image != nil + { + size.width = image?.size.width ?? 0.0 + } + if size.height == 0.0 && image != nil + { + size.height = image?.size.height ?? 0.0 + } + + let width = size.width + let height = size.height + + if point.x + offset.x < 0.0 + { + offset.x = -point.x + } + else if chart != nil && point.x + width + offset.x > chart!.bounds.size.width + { + offset.x = chart!.bounds.size.width - point.x - width + } + + if point.y + offset.y < 0 + { + offset.y = -point.y + } + else if chart != nil && point.y + height + offset.y > chart!.bounds.size.height + { + offset.y = chart!.bounds.size.height - point.y - height + } + + return offset + } + + open func refreshContent(entry: ChartDataEntry, highlight: Highlight) + { + // Do nothing here... + } + + open func draw(context: CGContext, point: CGPoint) + { + guard let image = image else { return } + + let offset = offsetForDrawing(atPoint: point) + + var size = self.size + + if size.width == 0.0 + { + size.width = image.size.width + } + if size.height == 0.0 + { + size.height = image.size.height + } + + let rect = CGRect( + x: point.x + offset.x, + y: point.y + offset.y, + width: size.width, + height: size.height) + + NSUIGraphicsPushContext(context) + image.draw(in: rect) + NSUIGraphicsPopContext() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Components/MarkerView.swift b/dydx/Pods/Charts/Source/Charts/Components/MarkerView.swift new file mode 100644 index 000000000..bf65e67cf --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Components/MarkerView.swift @@ -0,0 +1,100 @@ +// +// ChartMarkerView.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(AppKit) +import AppKit +#endif + +@objc(ChartMarkerView) +open class MarkerView: NSUIView, IMarker +{ + open var offset: CGPoint = CGPoint() + + @objc open weak var chartView: ChartViewBase? + + open func offsetForDrawing(atPoint point: CGPoint) -> CGPoint + { + guard let chart = chartView else { return self.offset } + + var offset = self.offset + + let width = self.bounds.size.width + let height = self.bounds.size.height + + if point.x + offset.x < 0.0 + { + offset.x = -point.x + } + else if point.x + width + offset.x > chart.bounds.size.width + { + offset.x = chart.bounds.size.width - point.x - width + } + + if point.y + offset.y < 0 + { + offset.y = -point.y + } + else if point.y + height + offset.y > chart.bounds.size.height + { + offset.y = chart.bounds.size.height - point.y - height + } + + return offset + } + + open func refreshContent(entry: ChartDataEntry, highlight: Highlight) + { + // Do nothing here... + } + + open func draw(context: CGContext, point: CGPoint) + { + let offset = self.offsetForDrawing(atPoint: point) + + context.saveGState() + context.translateBy(x: point.x + offset.x, + y: point.y + offset.y) + NSUIGraphicsPushContext(context) + self.nsuiLayer?.render(in: context) + NSUIGraphicsPopContext() + context.restoreGState() + } + + @objc + open class func viewFromXib(in bundle: Bundle = .main) -> MarkerView? + { + #if !os(OSX) + + return bundle.loadNibNamed( + String(describing: self), + owner: nil, + options: nil)?[0] as? MarkerView + #else + + var loadedObjects = NSArray() + let loadedObjectsPointer = AutoreleasingUnsafeMutablePointer(&loadedObjects) + + if bundle.loadNibNamed( + NSNib.Name(String(describing: self)), + owner: nil, + topLevelObjects: loadedObjectsPointer) + { + return loadedObjects[0] as? MarkerView + } + + return nil + #endif + } + +} diff --git a/dydx/Pods/Charts/Source/Charts/Components/XAxis.swift b/dydx/Pods/Charts/Source/Charts/Components/XAxis.swift new file mode 100644 index 000000000..77dbbd697 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Components/XAxis.swift @@ -0,0 +1,75 @@ +// +// XAxis.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(ChartXAxis) +open class XAxis: AxisBase +{ + @objc(XAxisLabelPosition) + public enum LabelPosition: Int + { + case top + case bottom + case bothSided + case topInside + case bottomInside + } + + /// width of the x-axis labels in pixels - this is automatically calculated by the `computeSize()` methods in the renderers + @objc open var labelWidth = CGFloat(1.0) + + /// height of the x-axis labels in pixels - this is automatically calculated by the `computeSize()` methods in the renderers + @objc open var labelHeight = CGFloat(1.0) + + /// width of the (rotated) x-axis labels in pixels - this is automatically calculated by the `computeSize()` methods in the renderers + @objc open var labelRotatedWidth = CGFloat(1.0) + + /// height of the (rotated) x-axis labels in pixels - this is automatically calculated by the `computeSize()` methods in the renderers + @objc open var labelRotatedHeight = CGFloat(1.0) + + /// This is the angle for drawing the X axis labels (in degrees) + @objc open var labelRotationAngle = CGFloat(0.0) + + /// if set to true, the chart will avoid that the first and last label entry in the chart "clip" off the edge of the chart + @objc open var avoidFirstLastClippingEnabled = false + + /// the position of the x-labels relative to the chart + @objc open var labelPosition = LabelPosition.top + + /// if set to true, word wrapping the labels will be enabled. + /// word wrapping is done using `(value width * labelRotatedWidth)` + /// + /// - Note: currently supports all charts except pie/radar/horizontal-bar* + @objc open var wordWrapEnabled = false + + /// `true` if word wrapping the labels is enabled + @objc open var isWordWrapEnabled: Bool { return wordWrapEnabled } + + /// the width for wrapping the labels, as percentage out of one value width. + /// used only when isWordWrapEnabled = true. + /// + /// **default**: 1.0 + @objc open var wordWrapWidthPercent: CGFloat = 1.0 + + public override init() + { + super.init() + + self.yOffset = 4.0 + } + + @objc open var isAvoidFirstLastClippingEnabled: Bool + { + return avoidFirstLastClippingEnabled + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Components/YAxis.swift b/dydx/Pods/Charts/Source/Charts/Components/YAxis.swift new file mode 100644 index 000000000..8f528615a --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Components/YAxis.swift @@ -0,0 +1,206 @@ +// +// YAxis.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + + +/// Class representing the y-axis labels settings and its entries. +/// Be aware that not all features the YLabels class provides are suitable for the RadarChart. +/// Customizations that affect the value range of the axis need to be applied before setting data for the chart. +@objc(ChartYAxis) +open class YAxis: AxisBase +{ + @objc(YAxisLabelPosition) + public enum LabelPosition: Int + { + case outsideChart + case insideChart + } + + /// Enum that specifies the axis a DataSet should be plotted against, either Left or Right. + @objc + public enum AxisDependency: Int + { + case left + case right + } + + /// indicates if the bottom y-label entry is drawn or not + @objc open var drawBottomYLabelEntryEnabled = true + + /// indicates if the top y-label entry is drawn or not + @objc open var drawTopYLabelEntryEnabled = true + + /// flag that indicates if the axis is inverted or not + @objc open var inverted = false + + /// flag that indicates if the zero-line should be drawn regardless of other grid lines + @objc open var drawZeroLineEnabled = false + + /// Color of the zero line + @objc open var zeroLineColor: NSUIColor? = NSUIColor.gray + + /// Width of the zero line + @objc open var zeroLineWidth: CGFloat = 1.0 + + /// This is how much (in pixels) into the dash pattern are we starting from. + @objc open var zeroLineDashPhase = CGFloat(0.0) + + /// This is the actual dash pattern. + /// I.e. [2, 3] will paint [-- -- ] + /// [1, 3, 4, 2] will paint [- ---- - ---- ] + @objc open var zeroLineDashLengths: [CGFloat]? + + /// axis space from the largest value to the top in percent of the total axis range + @objc open var spaceTop = CGFloat(0.1) + + /// axis space from the smallest value to the bottom in percent of the total axis range + @objc open var spaceBottom = CGFloat(0.1) + + /// the position of the y-labels relative to the chart + @objc open var labelPosition = LabelPosition.outsideChart + + /// the alignment of the text in the y-label + @objc open var labelAlignment: NSTextAlignment = .left + + /// the horizontal offset of the y-label + @objc open var labelXOffset: CGFloat = 0.0 + + /// the side this axis object represents + private var _axisDependency = AxisDependency.left + + /// the minimum width that the axis should take + /// + /// **default**: 0.0 + @objc open var minWidth = CGFloat(0) + + /// the maximum width that the axis can take. + /// use Infinity for disabling the maximum. + /// + /// **default**: CGFloat.infinity + @objc open var maxWidth = CGFloat(CGFloat.infinity) + + public override init() + { + super.init() + + self.yOffset = 0.0 + } + + @objc public init(position: AxisDependency) + { + super.init() + + _axisDependency = position + + self.yOffset = 0.0 + } + + @objc open var axisDependency: AxisDependency + { + return _axisDependency + } + + @objc open func requiredSize() -> CGSize + { + let label = getLongestLabel() as NSString + var size = label.size(withAttributes: [NSAttributedString.Key.font: labelFont]) + size.width += xOffset * 2.0 + size.height += yOffset * 2.0 + size.width = max(minWidth, min(size.width, maxWidth > 0.0 ? maxWidth : size.width)) + return size + } + + @objc open func getRequiredHeightSpace() -> CGFloat + { + return requiredSize().height + } + + /// `true` if this axis needs horizontal offset, `false` ifno offset is needed. + @objc open var needsOffset: Bool + { + if isEnabled && isDrawLabelsEnabled && labelPosition == .outsideChart + { + return true + } + else + { + return false + } + } + + @objc open var isInverted: Bool { return inverted } + + open override func calculate(min dataMin: Double, max dataMax: Double) + { + // if custom, use value as is, else use data value + var min = _customAxisMin ? _axisMinimum : dataMin + var max = _customAxisMax ? _axisMaximum : dataMax + + // Make sure max is greater than min + // Discussion: https://github.com/danielgindi/Charts/pull/3650#discussion_r221409991 + if min > max + { + switch(_customAxisMax, _customAxisMin) + { + case(true, true): + (min, max) = (max, min) + case(true, false): + min = max < 0 ? max * 1.5 : max * 0.5 + case(false, true): + max = min < 0 ? min * 0.5 : min * 1.5 + case(false, false): + break + } + } + + // temporary range (before calculations) + let range = abs(max - min) + + // in case all values are equal + if range == 0.0 + { + max = max + 1.0 + min = min - 1.0 + } + + // bottom-space only effects non-custom min + if !_customAxisMin + { + let bottomSpace = range * Double(spaceBottom) + _axisMinimum = (min - bottomSpace) + } + + // top-space only effects non-custom max + if !_customAxisMax + { + let topSpace = range * Double(spaceTop) + _axisMaximum = (max + topSpace) + } + + // calc actual range + axisRange = abs(_axisMaximum - _axisMinimum) + } + + @objc open var isDrawBottomYLabelEntryEnabled: Bool { return drawBottomYLabelEntryEnabled } + + @objc open var isDrawTopYLabelEntryEnabled: Bool { return drawTopYLabelEntryEnabled } + +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/ChartBaseDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/ChartBaseDataSet.swift new file mode 100644 index 000000000..b416a422f --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/ChartBaseDataSet.swift @@ -0,0 +1,435 @@ +// +// BaseDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +open class ChartBaseDataSet: NSObject, IChartDataSet, NSCopying +{ + public required override init() + { + super.init() + + // default color + colors.append(NSUIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0)) + valueColors.append(.labelOrBlack) + } + + @objc public init(label: String?) + { + super.init() + + // default color + colors.append(NSUIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0)) + valueColors.append(.labelOrBlack) + + self.label = label + } + + // MARK: - Data functions and accessors + + /// Use this method to tell the data set that the underlying data has changed + open func notifyDataSetChanged() + { + calcMinMax() + } + + open func calcMinMax() + { + fatalError("calcMinMax is not implemented in ChartBaseDataSet") + } + + open func calcMinMaxY(fromX: Double, toX: Double) + { + fatalError("calcMinMaxY(fromX:, toX:) is not implemented in ChartBaseDataSet") + } + + open var yMin: Double + { + fatalError("yMin is not implemented in ChartBaseDataSet") + } + + open var yMax: Double + { + fatalError("yMax is not implemented in ChartBaseDataSet") + } + + open var xMin: Double + { + fatalError("xMin is not implemented in ChartBaseDataSet") + } + + open var xMax: Double + { + fatalError("xMax is not implemented in ChartBaseDataSet") + } + + open var entryCount: Int + { + fatalError("entryCount is not implemented in ChartBaseDataSet") + } + + open func entryForIndex(_ i: Int) -> ChartDataEntry? + { + fatalError("entryForIndex is not implemented in ChartBaseDataSet") + } + + open func entryForXValue( + _ x: Double, + closestToY y: Double, + rounding: ChartDataSetRounding) -> ChartDataEntry? + { + fatalError("entryForXValue(x, closestToY, rounding) is not implemented in ChartBaseDataSet") + } + + open func entryForXValue( + _ x: Double, + closestToY y: Double) -> ChartDataEntry? + { + fatalError("entryForXValue(x, closestToY) is not implemented in ChartBaseDataSet") + } + + open func entriesForXValue(_ x: Double) -> [ChartDataEntry] + { + fatalError("entriesForXValue is not implemented in ChartBaseDataSet") + } + + open func entryIndex( + x xValue: Double, + closestToY y: Double, + rounding: ChartDataSetRounding) -> Int + { + fatalError("entryIndex(x, closestToY, rounding) is not implemented in ChartBaseDataSet") + } + + open func entryIndex(entry e: ChartDataEntry) -> Int + { + fatalError("entryIndex(entry) is not implemented in ChartBaseDataSet") + } + + open func addEntry(_ e: ChartDataEntry) -> Bool + { + fatalError("addEntry is not implemented in ChartBaseDataSet") + } + + open func addEntryOrdered(_ e: ChartDataEntry) -> Bool + { + fatalError("addEntryOrdered is not implemented in ChartBaseDataSet") + } + + @discardableResult open func removeEntry(_ entry: ChartDataEntry) -> Bool + { + fatalError("removeEntry is not implemented in ChartBaseDataSet") + } + + @discardableResult open func removeEntry(index: Int) -> Bool + { + if let entry = entryForIndex(index) + { + return removeEntry(entry) + } + return false + } + + @discardableResult open func removeEntry(x: Double) -> Bool + { + if let entry = entryForXValue(x, closestToY: Double.nan) + { + return removeEntry(entry) + } + return false + } + + @discardableResult open func removeFirst() -> Bool + { + if entryCount > 0 + { + if let entry = entryForIndex(0) + { + return removeEntry(entry) + } + } + return false + } + + @discardableResult open func removeLast() -> Bool + { + if entryCount > 0 + { + if let entry = entryForIndex(entryCount - 1) + { + return removeEntry(entry) + } + } + return false + } + + open func contains(_ e: ChartDataEntry) -> Bool + { + fatalError("removeEntry is not implemented in ChartBaseDataSet") + } + + open func clear() + { + fatalError("clear is not implemented in ChartBaseDataSet") + } + + // MARK: - Styling functions and accessors + + /// All the colors that are used for this DataSet. + /// Colors are reused as soon as the number of Entries the DataSet represents is higher than the size of the colors array. + open var colors = [NSUIColor]() + + /// List representing all colors that are used for drawing the actual values for this DataSet + open var valueColors = [NSUIColor]() + + /// The label string that describes the DataSet. + open var label: String? = "DataSet" + + /// The axis this DataSet should be plotted against. + open var axisDependency = YAxis.AxisDependency.left + + /// - Returns: The color at the given index of the DataSet's color array. + /// This prevents out-of-bounds by performing a modulus on the color index, so colours will repeat themselves. + open func color(atIndex index: Int) -> NSUIColor + { + var index = index + if index < 0 + { + index = 0 + } + return colors[index % colors.count] + } + + /// Resets all colors of this DataSet and recreates the colors array. + open func resetColors() + { + colors.removeAll(keepingCapacity: false) + } + + /// Adds a new color to the colors array of the DataSet. + /// + /// - Parameters: + /// - color: the color to add + open func addColor(_ color: NSUIColor) + { + colors.append(color) + } + + /// Sets the one and **only** color that should be used for this DataSet. + /// Internally, this recreates the colors array and adds the specified color. + /// + /// - Parameters: + /// - color: the color to set + open func setColor(_ color: NSUIColor) + { + colors.removeAll(keepingCapacity: false) + colors.append(color) + } + + /// Sets colors to a single color a specific alpha value. + /// + /// - Parameters: + /// - color: the color to set + /// - alpha: alpha to apply to the set `color` + @objc open func setColor(_ color: NSUIColor, alpha: CGFloat) + { + setColor(color.withAlphaComponent(alpha)) + } + + /// Sets colors with a specific alpha value. + /// + /// - Parameters: + /// - colors: the colors to set + /// - alpha: alpha to apply to the set `colors` + @objc open func setColors(_ colors: [NSUIColor], alpha: CGFloat) + { + self.colors = colors.map { $0.withAlphaComponent(alpha) } + } + + /// Sets colors with a specific alpha value. + /// + /// - Parameters: + /// - colors: the colors to set + /// - alpha: alpha to apply to the set `colors` + open func setColors(_ colors: NSUIColor...) + { + self.colors = colors + } + + /// if true, value highlighting is enabled + open var highlightEnabled = true + + /// `true` if value highlighting is enabled for this dataset + open var isHighlightEnabled: Bool { return highlightEnabled } + + /// Custom formatter that is used instead of the auto-formatter if set + internal var _valueFormatter: IValueFormatter? + + /// Custom formatter that is used instead of the auto-formatter if set + open var valueFormatter: IValueFormatter? + { + get + { + if needsFormatter + { + return ChartUtils.defaultValueFormatter() + } + + return _valueFormatter + } + set + { + if newValue == nil { return } + + _valueFormatter = newValue + } + } + + open var needsFormatter: Bool + { + return _valueFormatter == nil + } + + /// Sets/get a single color for value text. + /// Setting the color clears the colors array and adds a single color. + /// Getting will return the first color in the array. + open var valueTextColor: NSUIColor + { + get + { + return valueColors[0] + } + set + { + valueColors.removeAll(keepingCapacity: false) + valueColors.append(newValue) + } + } + + /// - Returns: The color at the specified index that is used for drawing the values inside the chart. Uses modulus internally. + open func valueTextColorAt(_ index: Int) -> NSUIColor + { + var index = index + if index < 0 + { + index = 0 + } + return valueColors[index % valueColors.count] + } + + /// the font for the value-text labels + open var valueFont: NSUIFont = NSUIFont.systemFont(ofSize: 7.0) + + /// The form to draw for this dataset in the legend. + open var form = Legend.Form.default + + /// The form size to draw for this dataset in the legend. + /// + /// Return `NaN` to use the default legend form size. + open var formSize: CGFloat = CGFloat.nan + + /// The line width for drawing the form of this dataset in the legend + /// + /// Return `NaN` to use the default legend form line width. + open var formLineWidth: CGFloat = CGFloat.nan + + /// Line dash configuration for legend shapes that consist of lines. + /// + /// This is how much (in pixels) into the dash pattern are we starting from. + open var formLineDashPhase: CGFloat = 0.0 + + /// Line dash configuration for legend shapes that consist of lines. + /// + /// This is the actual dash pattern. + /// I.e. [2, 3] will paint [-- -- ] + /// [1, 3, 4, 2] will paint [- ---- - ---- ] + open var formLineDashLengths: [CGFloat]? = nil + + /// Set this to true to draw y-values on the chart. + /// + /// - Note: For bar and line charts: if `maxVisibleCount` is reached, no values will be drawn even if this is enabled. + open var drawValuesEnabled = true + + /// `true` if y-value drawing is enabled, `false` ifnot + open var isDrawValuesEnabled: Bool + { + return drawValuesEnabled + } + + /// Set this to true to draw y-icons on the chart. + /// + /// - Note: For bar and line charts: if `maxVisibleCount` is reached, no icons will be drawn even if this is enabled. + open var drawIconsEnabled = true + + /// Returns true if y-icon drawing is enabled, false if not + open var isDrawIconsEnabled: Bool + { + return drawIconsEnabled + } + + /// Offset of icons drawn on the chart. + /// + /// For all charts except Pie and Radar it will be ordinary (x offset, y offset). + /// + /// For Pie and Radar chart it will be (y offset, distance from center offset); so if you want icon to be rendered under value, you should increase X component of CGPoint, and if you want icon to be rendered closet to center, you should decrease height component of CGPoint. + open var iconsOffset = CGPoint(x: 0, y: 0) + + /// Set the visibility of this DataSet. If not visible, the DataSet will not be drawn to the chart upon refreshing it. + open var visible = true + + /// `true` if this DataSet is visible inside the chart, or `false` ifit is currently hidden. + open var isVisible: Bool + { + return visible + } + + // MARK: - NSObject + + open override var description: String + { + return String(format: "%@, label: %@, %i entries", arguments: [NSStringFromClass(type(of: self)), self.label ?? "", self.entryCount]) + } + + open override var debugDescription: String + { + return (0.. Any + { + let copy = type(of: self).init() + + copy.colors = colors + copy.valueColors = valueColors + copy.label = label + copy.axisDependency = axisDependency + copy.highlightEnabled = highlightEnabled + copy._valueFormatter = _valueFormatter + copy.valueFont = valueFont + copy.form = form + copy.formSize = formSize + copy.formLineWidth = formLineWidth + copy.formLineDashPhase = formLineDashPhase + copy.formLineDashLengths = formLineDashLengths + copy.drawValuesEnabled = drawValuesEnabled + copy.drawValuesEnabled = drawValuesEnabled + copy.iconsOffset = iconsOffset + copy.visible = visible + + return copy + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartData.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartData.swift new file mode 100644 index 000000000..6697c32cf --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartData.swift @@ -0,0 +1,107 @@ +// +// BarChartData.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class BarChartData: BarLineScatterCandleBubbleChartData +{ + public override init() + { + super.init() + } + + public override init(dataSets: [IChartDataSet]?) + { + super.init(dataSets: dataSets) + } + + /// The width of the bars on the x-axis, in values (not pixels) + /// + /// **default**: 0.85 + @objc open var barWidth = Double(0.85) + + /// Groups all BarDataSet objects this data object holds together by modifying the x-value of their entries. + /// Previously set x-values of entries will be overwritten. Leaves space between bars and groups as specified by the parameters. + /// Do not forget to call notifyDataSetChanged() on your BarChart object after calling this method. + /// + /// - Parameters: + /// - fromX: the starting point on the x-axis where the grouping should begin + /// - groupSpace: The space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f + /// - barSpace: The space between individual bars in values (not pixels) e.g. 0.1f for bar width 1f + @objc open func groupBars(fromX: Double, groupSpace: Double, barSpace: Double) + { + let setCount = _dataSets.count + if setCount <= 1 + { + print("BarData needs to hold at least 2 BarDataSets to allow grouping.", terminator: "\n") + return + } + + let max = maxEntryCountSet + let maxEntryCount = max?.entryCount ?? 0 + + let groupSpaceWidthHalf = groupSpace / 2.0 + let barSpaceHalf = barSpace / 2.0 + let barWidthHalf = self.barWidth / 2.0 + + var fromX = fromX + + let interval = groupWidth(groupSpace: groupSpace, barSpace: barSpace) + + for i in stride(from: 0, to: maxEntryCount, by: 1) + { + let start = fromX + fromX += groupSpaceWidthHalf + + (_dataSets as? [IBarChartDataSet])?.forEach { set in + fromX += barSpaceHalf + fromX += barWidthHalf + + if i < set.entryCount + { + if let entry = set.entryForIndex(i) + { + entry.x = fromX + } + } + + fromX += barWidthHalf + fromX += barSpaceHalf + } + + fromX += groupSpaceWidthHalf + let end = fromX + let innerInterval = end - start + let diff = interval - innerInterval + + // correct rounding errors + if diff > 0 || diff < 0 + { + fromX += diff + } + + } + + notifyDataChanged() + } + + /// In case of grouped bars, this method returns the space an individual group of bar needs on the x-axis. + /// + /// - Parameters: + /// - groupSpace: + /// - barSpace: + @objc open func groupWidth(groupSpace: Double, barSpace: Double) -> Double + { + return Double(_dataSets.count) * (self.barWidth + barSpace) + groupSpace + } + +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataEntry.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataEntry.swift new file mode 100644 index 000000000..31c51b0f8 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataEntry.swift @@ -0,0 +1,230 @@ +// +// BarChartDataEntry.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +open class BarChartDataEntry: ChartDataEntry +{ + /// the values the stacked barchart holds + private var _yVals: [Double]? + + /// the ranges for the individual stack values - automatically calculated + private var _ranges: [Range]? + + /// the sum of all negative values this entry (if stacked) contains + private var _negativeSum: Double = 0.0 + + /// the sum of all positive values this entry (if stacked) contains + private var _positiveSum: Double = 0.0 + + public required init() + { + super.init() + } + + /// Constructor for normal bars (not stacked). + public override init(x: Double, y: Double) + { + super.init(x: x, y: y) + } + + /// Constructor for normal bars (not stacked). + public convenience init(x: Double, y: Double, data: Any?) + { + self.init(x: x, y: y) + self.data = data + } + + /// Constructor for normal bars (not stacked). + public convenience init(x: Double, y: Double, icon: NSUIImage?) + { + self.init(x: x, y: y) + self.icon = icon + } + + /// Constructor for normal bars (not stacked). + public convenience init(x: Double, y: Double, icon: NSUIImage?, data: Any?) + { + self.init(x: x, y: y) + self.icon = icon + self.data = data + } + + /// Constructor for stacked bar entries. + @objc public init(x: Double, yValues: [Double]) + { + super.init(x: x, y: BarChartDataEntry.calcSum(values: yValues)) + self._yVals = yValues + calcPosNegSum() + calcRanges() + } + + /// Constructor for stacked bar entries. One data object for whole stack + @objc public convenience init(x: Double, yValues: [Double], icon: NSUIImage?) + { + self.init(x: x, yValues: yValues) + self.icon = icon + } + + /// Constructor for stacked bar entries. One data object for whole stack + @objc public convenience init(x: Double, yValues: [Double], data: Any?) + { + self.init(x: x, yValues: yValues) + self.data = data + } + + /// Constructor for stacked bar entries. One data object for whole stack + @objc public convenience init(x: Double, yValues: [Double], icon: NSUIImage?, data: Any?) + { + self.init(x: x, yValues: yValues) + self.icon = icon + self.data = data + } + + @objc open func sumBelow(stackIndex :Int) -> Double + { + guard let yVals = _yVals else + { + return 0 + } + + var remainder: Double = 0.0 + var index = yVals.count - 1 + + while (index > stackIndex && index >= 0) + { + remainder += yVals[index] + index -= 1 + } + + return remainder + } + + /// The sum of all negative values this entry (if stacked) contains. (this is a positive number) + @objc open var negativeSum: Double + { + return _negativeSum + } + + /// The sum of all positive values this entry (if stacked) contains. + @objc open var positiveSum: Double + { + return _positiveSum + } + + @objc open func calcPosNegSum() + { + (_negativeSum, _positiveSum) = _yVals?.reduce(into: (0,0)) { (result, y) in + if y < 0 + { + result.0 += -y + } + else + { + result.1 += y + } + } ?? (0,0) + } + + /// Splits up the stack-values of the given bar-entry into Range objects. + /// + /// - Parameters: + /// - entry: + /// - Returns: + @objc open func calcRanges() + { + guard let values = yValues, !values.isEmpty else { return } + + if _ranges == nil + { + _ranges = [Range]() + } + else + { + _ranges!.removeAll() + } + + _ranges!.reserveCapacity(values.count) + + var negRemain = -negativeSum + var posRemain: Double = 0.0 + + for value in values + { + if value < 0 + { + _ranges!.append(Range(from: negRemain, to: negRemain - value)) + negRemain -= value + } + else + { + _ranges!.append(Range(from: posRemain, to: posRemain + value)) + posRemain += value + } + } + } + + // MARK: Accessors + + /// the values the stacked barchart holds + @objc open var isStacked: Bool { return _yVals != nil } + + /// the values the stacked barchart holds + @objc open var yValues: [Double]? + { + get { return self._yVals } + set + { + self.y = BarChartDataEntry.calcSum(values: newValue) + self._yVals = newValue + calcPosNegSum() + calcRanges() + } + } + + /// The ranges of the individual stack-entries. Will return null if this entry is not stacked. + @objc open var ranges: [Range]? + { + return _ranges + } + + // MARK: NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! BarChartDataEntry + copy._yVals = _yVals + copy.y = y + copy._negativeSum = _negativeSum + copy._positiveSum = _positiveSum + return copy + } + + /// Calculates the sum across all values of the given stack. + /// + /// - Parameters: + /// - vals: + /// - Returns: + private static func calcSum(values: [Double]?) -> Double + { + guard let values = values + else { return 0.0 } + + var sum = 0.0 + + for f in values + { + sum += f + } + + return sum + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift new file mode 100644 index 000000000..2478aeccf --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift @@ -0,0 +1,167 @@ +// +// BarChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +open class BarChartDataSet: BarLineScatterCandleBubbleChartDataSet, IBarChartDataSet +{ + private func initialize() + { + self.highlightColor = NSUIColor.black + + self.calcStackSize(entries: entries as! [BarChartDataEntry]) + self.calcEntryCountIncludingStacks(entries: entries as! [BarChartDataEntry]) + } + + public required init() + { + super.init() + initialize() + } + + public override init(entries: [ChartDataEntry]?, label: String?) + { + super.init(entries: entries, label: label) + initialize() + } + + // MARK: - Data functions and accessors + + /// the maximum number of bars that are stacked upon each other, this value + /// is calculated from the Entries that are added to the DataSet + private var _stackSize = 1 + + /// the overall entry count, including counting each stack-value individually + private var _entryCountStacks = 0 + + /// Calculates the total number of entries this DataSet represents, including + /// stacks. All values belonging to a stack are calculated separately. + private func calcEntryCountIncludingStacks(entries: [BarChartDataEntry]) + { + _entryCountStacks = 0 + + for i in 0 ..< entries.count + { + if let vals = entries[i].yValues + { + _entryCountStacks += vals.count + } + else + { + _entryCountStacks += 1 + } + } + } + + /// calculates the maximum stacksize that occurs in the Entries array of this DataSet + private func calcStackSize(entries: [BarChartDataEntry]) + { + for i in 0 ..< entries.count + { + if let vals = entries[i].yValues + { + if vals.count > _stackSize + { + _stackSize = vals.count + } + } + } + } + + open override func calcMinMax(entry e: ChartDataEntry) + { + guard let e = e as? BarChartDataEntry + else { return } + + if !e.y.isNaN + { + if e.yValues == nil + { + if e.y < _yMin + { + _yMin = e.y + } + + if e.y > _yMax + { + _yMax = e.y + } + } + else + { + if -e.negativeSum < _yMin + { + _yMin = -e.negativeSum + } + + if e.positiveSum > _yMax + { + _yMax = e.positiveSum + } + } + + calcMinMaxX(entry: e) + } + } + + /// The maximum number of bars that can be stacked upon another in this DataSet. + open var stackSize: Int + { + return _stackSize + } + + /// `true` if this DataSet is stacked (stacksize > 1) or not. + open var isStacked: Bool + { + return _stackSize > 1 ? true : false + } + + /// The overall entry count, including counting each stack-value individually + @objc open var entryCountStacks: Int + { + return _entryCountStacks + } + + /// array of labels used to describe the different values of the stacked bars + open var stackLabels: [String] = [] + + // MARK: - Styling functions and accessors + + /// the color used for drawing the bar-shadows. The bar shadows is a surface behind the bar that indicates the maximum value + open var barShadowColor = NSUIColor(red: 215.0/255.0, green: 215.0/255.0, blue: 215.0/255.0, alpha: 1.0) + + /// the width used for drawing borders around the bars. If borderWidth == 0, no border will be drawn. + open var barBorderWidth : CGFloat = 0.0 + + /// the color drawing borders around the bars. + open var barBorderColor = NSUIColor.black + + /// the alpha value (transparency) that is used for drawing the highlight indicator bar. min = 0.0 (fully transparent), max = 1.0 (fully opaque) + open var highlightAlpha = CGFloat(120.0 / 255.0) + + // MARK: - NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! BarChartDataSet + copy._stackSize = _stackSize + copy._entryCountStacks = _entryCountStacks + copy.stackLabels = stackLabels + + copy.barShadowColor = barShadowColor + copy.barBorderWidth = barBorderWidth + copy.barBorderColor = barBorderColor + copy.highlightAlpha = highlightAlpha + return copy + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartData.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartData.swift new file mode 100644 index 000000000..c98bb1d07 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartData.swift @@ -0,0 +1,25 @@ +// +// BarLineScatterCandleBubbleChartData.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +open class BarLineScatterCandleBubbleChartData: ChartData +{ + public override init() + { + super.init() + } + + public override init(dataSets: [IChartDataSet]?) + { + super.init(dataSets: dataSets) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartDataSet.swift new file mode 100644 index 000000000..91382515a --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartDataSet.swift @@ -0,0 +1,38 @@ +// +// BarLineScatterCandleBubbleChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +open class BarLineScatterCandleBubbleChartDataSet: ChartDataSet, IBarLineScatterCandleBubbleChartDataSet +{ + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + open var highlightColor = NSUIColor(red: 255.0/255.0, green: 187.0/255.0, blue: 115.0/255.0, alpha: 1.0) + open var highlightLineWidth = CGFloat(0.5) + open var highlightLineDashPhase = CGFloat(0.0) + open var highlightLineDashLengths: [CGFloat]? + + // MARK: - NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! BarLineScatterCandleBubbleChartDataSet + copy.highlightColor = highlightColor + copy.highlightLineWidth = highlightLineWidth + copy.highlightLineDashPhase = highlightLineDashPhase + copy.highlightLineDashLengths = highlightLineDashLengths + return copy + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartData.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartData.swift new file mode 100644 index 000000000..433f384f7 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartData.swift @@ -0,0 +1,32 @@ +// +// BubbleChartData.swift +// Charts +// +// Bubble chart implementation: +// Copyright 2015 Pierre-Marc Airoldi +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class BubbleChartData: BarLineScatterCandleBubbleChartData +{ + public override init() + { + super.init() + } + + public override init(dataSets: [IChartDataSet]?) + { + super.init(dataSets: dataSets) + } + + /// Sets the width of the circle that surrounds the bubble when highlighted for all DataSet objects this data object contains + @objc open func setHighlightCircleWidth(_ width: CGFloat) + { + (_dataSets as? [IBubbleChartDataSet])?.forEach { $0.highlightCircleWidth = width } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataEntry.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataEntry.swift new file mode 100644 index 000000000..01f9fc968 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataEntry.swift @@ -0,0 +1,79 @@ +// +// BubbleDataEntry.swift +// Charts +// +// Bubble chart implementation: +// Copyright 2015 Pierre-Marc Airoldi +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class BubbleChartDataEntry: ChartDataEntry +{ + /// The size of the bubble. + @objc open var size = CGFloat(0.0) + + public required init() + { + super.init() + } + + /// - Parameters: + /// - x: The index on the x-axis. + /// - y: The value on the y-axis. + /// - size: The size of the bubble. + @objc public init(x: Double, y: Double, size: CGFloat) + { + super.init(x: x, y: y) + + self.size = size + } + + /// - Parameters: + /// - x: The index on the x-axis. + /// - y: The value on the y-axis. + /// - size: The size of the bubble. + /// - data: Spot for additional data this Entry represents. + @objc public convenience init(x: Double, y: Double, size: CGFloat, data: Any?) + { + self.init(x: x, y: y, size: size) + self.data = data + } + + /// - Parameters: + /// - x: The index on the x-axis. + /// - y: The value on the y-axis. + /// - size: The size of the bubble. + /// - icon: icon image + @objc public convenience init(x: Double, y: Double, size: CGFloat, icon: NSUIImage?) + { + self.init(x: x, y: y, size: size) + self.icon = icon + } + + /// - Parameters: + /// - x: The index on the x-axis. + /// - y: The value on the y-axis. + /// - size: The size of the bubble. + /// - icon: icon image + /// - data: Spot for additional data this Entry represents. + @objc public convenience init(x: Double, y: Double, size: CGFloat, icon: NSUIImage?, data: Any?) + { + self.init(x: x, y: y, size: size) + self.icon = icon + self.data = data + } + + // MARK: NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! BubbleChartDataEntry + copy.size = size + return copy + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataSet.swift new file mode 100644 index 000000000..775fafb8b --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataSet.swift @@ -0,0 +1,58 @@ +// +// BubbleChartDataSet.swift +// Charts +// +// Bubble chart implementation: +// Copyright 2015 Pierre-Marc Airoldi +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +open class BubbleChartDataSet: BarLineScatterCandleBubbleChartDataSet, IBubbleChartDataSet +{ + // MARK: - Data functions and accessors + + internal var _maxSize = CGFloat(0.0) + + open var maxSize: CGFloat { return _maxSize } + @objc open var normalizeSizeEnabled: Bool = true + open var isNormalizeSizeEnabled: Bool { return normalizeSizeEnabled } + + open override func calcMinMax(entry e: ChartDataEntry) + { + guard let e = e as? BubbleChartDataEntry + else { return } + + super.calcMinMax(entry: e) + + let size = e.size + + if size > _maxSize + { + _maxSize = size + } + } + + // MARK: - Styling functions and accessors + + /// Sets/gets the width of the circle that surrounds the bubble when highlighted + open var highlightCircleWidth: CGFloat = 2.5 + + // MARK: - NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! BubbleChartDataSet + copy._xMin = _xMin + copy._xMax = _xMax + copy._maxSize = _maxSize + copy.normalizeSizeEnabled = normalizeSizeEnabled + copy.highlightCircleWidth = highlightCircleWidth + return copy + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartData.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartData.swift new file mode 100644 index 000000000..5158668ad --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartData.swift @@ -0,0 +1,25 @@ +// +// CandleChartData.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +open class CandleChartData: BarLineScatterCandleBubbleChartData +{ + public override init() + { + super.init() + } + + public override init(dataSets: [IChartDataSet]?) + { + super.init(dataSets: dataSets) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataEntry.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataEntry.swift new file mode 100644 index 000000000..467bc168d --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataEntry.swift @@ -0,0 +1,98 @@ +// +// CandleChartDataEntry.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +open class CandleChartDataEntry: ChartDataEntry +{ + /// shadow-high value + @objc open var high = Double(0.0) + + /// shadow-low value + @objc open var low = Double(0.0) + + /// close value + @objc open var close = Double(0.0) + + /// open value + @objc open var open = Double(0.0) + + public required init() + { + super.init() + } + + @objc public init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double) + { + super.init(x: x, y: (shadowH + shadowL) / 2.0) + + self.high = shadowH + self.low = shadowL + self.open = open + self.close = close + } + + @objc public convenience init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double, icon: NSUIImage?) + { + self.init(x: x, shadowH: shadowH, shadowL: shadowL, open: open, close: close) + self.icon = icon + } + + @objc public convenience init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double, data: Any?) + { + self.init(x: x, shadowH: shadowH, shadowL: shadowL, open: open, close: close) + self.data = data + } + + @objc public convenience init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double, icon: NSUIImage?, data: Any?) + { + self.init(x: x, shadowH: shadowH, shadowL: shadowL, open: open, close: close) + self.icon = icon + self.data = data + } + + /// The overall range (difference) between shadow-high and shadow-low. + @objc open var shadowRange: Double + { + return abs(high - low) + } + + /// The body size (difference between open and close). + @objc open var bodyRange: Double + { + return abs(open - close) + } + + /// the center value of the candle. (Middle value between high and low) + open override var y: Double + { + get + { + return super.y + } + set + { + super.y = (high + low) / 2.0 + } + } + + // MARK: NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! CandleChartDataEntry + copy.high = high + copy.low = low + copy.open = open + copy.close = close + return copy + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataSet.swift new file mode 100644 index 000000000..1c51da7d2 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataSet.swift @@ -0,0 +1,136 @@ +// +// CandleChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +open class CandleChartDataSet: LineScatterCandleRadarChartDataSet, ICandleChartDataSet +{ + + public required init() + { + super.init() + } + + public override init(entries: [ChartDataEntry]?, label: String?) + { + super.init(entries: entries, label: label) + } + + // MARK: - Data functions and accessors + + open override func calcMinMax(entry e: ChartDataEntry) + { + guard let e = e as? CandleChartDataEntry + else { return } + + if e.low < _yMin + { + _yMin = e.low + } + + if e.high > _yMax + { + _yMax = e.high + } + + calcMinMaxX(entry: e) + } + + open override func calcMinMaxY(entry e: ChartDataEntry) + { + guard let e = e as? CandleChartDataEntry + else { return } + + if e.high < _yMin + { + _yMin = e.high + } + if e.high > _yMax + { + _yMax = e.high + } + + if e.low < _yMin + { + _yMin = e.low + } + if e.low > _yMax + { + _yMax = e.low + } + } + + // MARK: - Styling functions and accessors + + /// the space between the candle entries + /// + /// **default**: 0.1 (10%) + private var _barSpace = CGFloat(0.1) + + /// the space that is left out on the left and right side of each candle, + /// **default**: 0.1 (10%), max 0.45, min 0.0 + open var barSpace: CGFloat + { + get + { + return _barSpace + } + set + { + _barSpace = newValue.clamped(to: 0...0.45) + } + } + + /// should the candle bars show? + /// when false, only "ticks" will show + /// + /// **default**: true + open var showCandleBar: Bool = true + + /// the width of the candle-shadow-line in pixels. + /// + /// **default**: 1.5 + open var shadowWidth = CGFloat(1.5) + + /// the color of the shadow line + open var shadowColor: NSUIColor? + + /// use candle color for the shadow + open var shadowColorSameAsCandle = false + + /// Is the shadow color same as the candle color? + open var isShadowColorSameAsCandle: Bool { return shadowColorSameAsCandle } + + /// color for open == close + open var neutralColor: NSUIColor? + + /// color for open > close + open var increasingColor: NSUIColor? + + /// color for open < close + open var decreasingColor: NSUIColor? + + /// Are increasing values drawn as filled? + /// increasing candlesticks are traditionally hollow + open var increasingFilled = false + + /// Are increasing values drawn as filled? + open var isIncreasingFilled: Bool { return increasingFilled } + + /// Are decreasing values drawn as filled? + /// descreasing candlesticks are traditionally filled + open var decreasingFilled = true + + /// Are decreasing values drawn as filled? + open var isDecreasingFilled: Bool { return decreasingFilled } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartData.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartData.swift new file mode 100644 index 000000000..f4699ef70 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -0,0 +1,610 @@ +// +// ChartData.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +open class ChartData: NSObject +{ + internal var _yMax: Double = -Double.greatestFiniteMagnitude + internal var _yMin: Double = Double.greatestFiniteMagnitude + internal var _xMax: Double = -Double.greatestFiniteMagnitude + internal var _xMin: Double = Double.greatestFiniteMagnitude + internal var _leftAxisMax: Double = -Double.greatestFiniteMagnitude + internal var _leftAxisMin: Double = Double.greatestFiniteMagnitude + internal var _rightAxisMax: Double = -Double.greatestFiniteMagnitude + internal var _rightAxisMin: Double = Double.greatestFiniteMagnitude + + internal var _dataSets = [IChartDataSet]() + + public override init() + { + super.init() + + _dataSets = [IChartDataSet]() + } + + @objc public init(dataSets: [IChartDataSet]?) + { + super.init() + + _dataSets = dataSets ?? [IChartDataSet]() + + self.initialize(dataSets: _dataSets) + } + + @objc public convenience init(dataSet: IChartDataSet?) + { + self.init(dataSets: dataSet === nil ? nil : [dataSet!]) + } + + internal func initialize(dataSets: [IChartDataSet]) + { + notifyDataChanged() + } + + /// Call this method to let the ChartData know that the underlying data has changed. + /// Calling this performs all necessary recalculations needed when the contained data has changed. + @objc open func notifyDataChanged() + { + calcMinMax() + } + + @objc open func calcMinMaxY(fromX: Double, toX: Double) + { + _dataSets.forEach { $0.calcMinMaxY(fromX: fromX, toX: toX) } + // apply the new data + calcMinMax() + } + + /// calc minimum and maximum y value over all datasets + @objc open func calcMinMax() + { + _yMax = -Double.greatestFiniteMagnitude + _yMin = Double.greatestFiniteMagnitude + _xMax = -Double.greatestFiniteMagnitude + _xMin = Double.greatestFiniteMagnitude + + _dataSets.forEach { calcMinMax(dataSet: $0) } + + _leftAxisMax = -Double.greatestFiniteMagnitude + _leftAxisMin = Double.greatestFiniteMagnitude + _rightAxisMax = -Double.greatestFiniteMagnitude + _rightAxisMin = Double.greatestFiniteMagnitude + + // left axis + let firstLeft = getFirstLeft(dataSets: dataSets) + + if firstLeft !== nil + { + _leftAxisMax = firstLeft!.yMax + _leftAxisMin = firstLeft!.yMin + + for dataSet in _dataSets + { + if dataSet.axisDependency == .left + { + if dataSet.yMin < _leftAxisMin + { + _leftAxisMin = dataSet.yMin + } + + if dataSet.yMax > _leftAxisMax + { + _leftAxisMax = dataSet.yMax + } + } + } + } + + // right axis + let firstRight = getFirstRight(dataSets: dataSets) + + if firstRight !== nil + { + _rightAxisMax = firstRight!.yMax + _rightAxisMin = firstRight!.yMin + + for dataSet in _dataSets + { + if dataSet.axisDependency == .right + { + if dataSet.yMin < _rightAxisMin + { + _rightAxisMin = dataSet.yMin + } + + if dataSet.yMax > _rightAxisMax + { + _rightAxisMax = dataSet.yMax + } + } + } + } + } + + /// Adjusts the current minimum and maximum values based on the provided Entry object. + @objc open func calcMinMax(entry e: ChartDataEntry, axis: YAxis.AxisDependency) + { + if _yMax < e.y + { + _yMax = e.y + } + + if _yMin > e.y + { + _yMin = e.y + } + + if _xMax < e.x + { + _xMax = e.x + } + + if _xMin > e.x + { + _xMin = e.x + } + + if axis == .left + { + if _leftAxisMax < e.y + { + _leftAxisMax = e.y + } + + if _leftAxisMin > e.y + { + _leftAxisMin = e.y + } + } + else + { + if _rightAxisMax < e.y + { + _rightAxisMax = e.y + } + + if _rightAxisMin > e.y + { + _rightAxisMin = e.y + } + } + } + + /// Adjusts the minimum and maximum values based on the given DataSet. + @objc open func calcMinMax(dataSet d: IChartDataSet) + { + if _yMax < d.yMax + { + _yMax = d.yMax + } + + if _yMin > d.yMin + { + _yMin = d.yMin + } + + if _xMax < d.xMax + { + _xMax = d.xMax + } + + if _xMin > d.xMin + { + _xMin = d.xMin + } + + if d.axisDependency == .left + { + if _leftAxisMax < d.yMax + { + _leftAxisMax = d.yMax + } + + if _leftAxisMin > d.yMin + { + _leftAxisMin = d.yMin + } + } + else + { + if _rightAxisMax < d.yMax + { + _rightAxisMax = d.yMax + } + + if _rightAxisMin > d.yMin + { + _rightAxisMin = d.yMin + } + } + } + + /// The number of LineDataSets this object contains + @objc open var dataSetCount: Int + { + return _dataSets.count + } + + /// The smallest y-value the data object contains. + @objc open var yMin: Double + { + return _yMin + } + + @nonobjc + open func getYMin() -> Double + { + return _yMin + } + + @objc open func getYMin(axis: YAxis.AxisDependency) -> Double + { + if axis == .left + { + if _leftAxisMin == Double.greatestFiniteMagnitude + { + return _rightAxisMin + } + else + { + return _leftAxisMin + } + } + else + { + if _rightAxisMin == Double.greatestFiniteMagnitude + { + return _leftAxisMin + } + else + { + return _rightAxisMin + } + } + } + + /// The greatest y-value the data object contains. + @objc open var yMax: Double + { + return _yMax + } + + @nonobjc + open func getYMax() -> Double + { + return _yMax + } + + @objc open func getYMax(axis: YAxis.AxisDependency) -> Double + { + if axis == .left + { + if _leftAxisMax == -Double.greatestFiniteMagnitude + { + return _rightAxisMax + } + else + { + return _leftAxisMax + } + } + else + { + if _rightAxisMax == -Double.greatestFiniteMagnitude + { + return _leftAxisMax + } + else + { + return _rightAxisMax + } + } + } + + /// The minimum x-value the data object contains. + @objc open var xMin: Double + { + return _xMin + } + /// The maximum x-value the data object contains. + @objc open var xMax: Double + { + return _xMax + } + + /// All DataSet objects this ChartData object holds. + @objc open var dataSets: [IChartDataSet] + { + get + { + return _dataSets + } + set + { + _dataSets = newValue + notifyDataChanged() + } + } + + /// Retrieve the index of a ChartDataSet with a specific label from the ChartData. Search can be case sensitive or not. + /// + /// **IMPORTANT: This method does calculations at runtime, do not over-use in performance critical situations.** + /// + /// - Parameters: + /// - dataSets: the DataSet array to search + /// - type: + /// - ignorecase: if true, the search is not case-sensitive + /// - Returns: The index of the DataSet Object with the given label. Sensitive or not. + internal func getDataSetIndexByLabel(_ label: String, ignorecase: Bool) -> Int + { + // TODO: Return nil instead of -1 + if ignorecase + { + return dataSets.firstIndex { $0.label?.caseInsensitiveCompare(label) == .orderedSame } + ?? -1 + } + else + { + return dataSets.firstIndex { $0.label == label } + ?? -1 + } + } + + /// Get the Entry for a corresponding highlight object + /// + /// - Parameters: + /// - highlight: + /// - Returns: The entry that is highlighted + @objc open func entryForHighlight(_ highlight: Highlight) -> ChartDataEntry? + { + if highlight.dataSetIndex >= dataSets.count + { + return nil + } + else + { + return dataSets[highlight.dataSetIndex].entryForXValue(highlight.x, closestToY: highlight.y) + } + } + + /// **IMPORTANT: This method does calculations at runtime. Use with care in performance critical situations.** + /// + /// - Parameters: + /// - label: + /// - ignorecase: + /// - Returns: The DataSet Object with the given label. Sensitive or not. + @objc open func getDataSetByLabel(_ label: String, ignorecase: Bool) -> IChartDataSet? + { + let index = getDataSetIndexByLabel(label, ignorecase: ignorecase) + + if index < 0 || index >= _dataSets.count + { + return nil + } + else + { + return _dataSets[index] + } + } + + @objc open func getDataSetByIndex(_ index: Int) -> IChartDataSet! + { + if index < 0 || index >= _dataSets.count + { + return nil + } + + return _dataSets[index] + } + + @objc open func addDataSet(_ dataSet: IChartDataSet!) + { + calcMinMax(dataSet: dataSet) + + _dataSets.append(dataSet) + } + + /// Removes the given DataSet from this data object. + /// Also recalculates all minimum and maximum values. + /// + /// - Returns: `true` if a DataSet was removed, `false` ifno DataSet could be removed. + @objc @discardableResult open func removeDataSet(_ dataSet: IChartDataSet) -> Bool + { + guard let i = _dataSets.firstIndex(where: { $0 === dataSet }) else { return false } + return removeDataSetByIndex(i) + } + + /// Removes the DataSet at the given index in the DataSet array from the data object. + /// Also recalculates all minimum and maximum values. + /// + /// - Returns: `true` if a DataSet was removed, `false` ifno DataSet could be removed. + @objc @discardableResult open func removeDataSetByIndex(_ index: Int) -> Bool + { + if index >= _dataSets.count || index < 0 + { + return false + } + + _dataSets.remove(at: index) + + calcMinMax() + + return true + } + + /// Adds an Entry to the DataSet at the specified index. Entries are added to the end of the list. + @objc open func addEntry(_ e: ChartDataEntry, dataSetIndex: Int) + { + if _dataSets.count > dataSetIndex && dataSetIndex >= 0 + { + let set = _dataSets[dataSetIndex] + + if !set.addEntry(e) { return } + + calcMinMax(entry: e, axis: set.axisDependency) + } + else + { + print("ChartData.addEntry() - Cannot add Entry because dataSetIndex too high or too low.", terminator: "\n") + } + } + + /// Removes the given Entry object from the DataSet at the specified index. + @objc @discardableResult open func removeEntry(_ entry: ChartDataEntry, dataSetIndex: Int) -> Bool + { + // entry outofbounds + if dataSetIndex >= _dataSets.count + { + return false + } + + // remove the entry from the dataset + let removed = _dataSets[dataSetIndex].removeEntry(entry) + + if removed + { + calcMinMax() + } + + return removed + } + + /// Removes the Entry object closest to the given xIndex from the ChartDataSet at the + /// specified index. + /// + /// - Returns: `true` if an entry was removed, `false` ifno Entry was found that meets the specified requirements. + @objc @discardableResult open func removeEntry(xValue: Double, dataSetIndex: Int) -> Bool + { + if dataSetIndex >= _dataSets.count + { + return false + } + + if let entry = _dataSets[dataSetIndex].entryForXValue(xValue, closestToY: Double.nan) + { + return removeEntry(entry, dataSetIndex: dataSetIndex) + } + + return false + } + + /// - Returns: The DataSet that contains the provided Entry, or null, if no DataSet contains this entry. + @objc open func getDataSetForEntry(_ e: ChartDataEntry) -> IChartDataSet? + { + return _dataSets.first { $0.entryForXValue(e.x, closestToY: e.y) === e } + } + + /// - Returns: The index of the provided DataSet in the DataSet array of this data object, or -1 if it does not exist. + @objc open func indexOfDataSet(_ dataSet: IChartDataSet) -> Int + { + // TODO: Return nil instead of -1 + return _dataSets.firstIndex { $0 === dataSet } ?? -1 + } + + /// - Returns: The first DataSet from the datasets-array that has it's dependency on the left axis. Returns null if no DataSet with left dependency could be found. + @objc open func getFirstLeft(dataSets: [IChartDataSet]) -> IChartDataSet? + { + return dataSets.first { $0.axisDependency == .left } + } + + /// - Returns: The first DataSet from the datasets-array that has it's dependency on the right axis. Returns null if no DataSet with right dependency could be found. + @objc open func getFirstRight(dataSets: [IChartDataSet]) -> IChartDataSet? + { + return dataSets.first { $0.axisDependency == .right } + } + + /// - Returns: All colors used across all DataSet objects this object represents. + @objc open func getColors() -> [NSUIColor]? + { + // TODO: Don't return nil + return _dataSets.flatMap { $0.colors } + } + + /// Sets a custom IValueFormatter for all DataSets this data object contains. + @objc open func setValueFormatter(_ formatter: IValueFormatter) + { + dataSets.forEach { $0.valueFormatter = formatter } + } + + /// Sets the color of the value-text (color in which the value-labels are drawn) for all DataSets this data object contains. + @objc open func setValueTextColor(_ color: NSUIColor) + { + dataSets.forEach { $0.valueTextColor = color } + } + + /// Sets the font for all value-labels for all DataSets this data object contains. + @objc open func setValueFont(_ font: NSUIFont) + { + dataSets.forEach { $0.valueFont = font } + } + + /// Enables / disables drawing values (value-text) for all DataSets this data object contains. + @objc open func setDrawValues(_ enabled: Bool) + { + dataSets.forEach { $0.drawValuesEnabled = enabled } + } + + /// Enables / disables highlighting values for all DataSets this data object contains. + /// If set to true, this means that values can be highlighted programmatically or by touch gesture. + @objc open var highlightEnabled: Bool + { + get { return dataSets.allSatisfy { $0.highlightEnabled } } + set { dataSets.forEach { $0.highlightEnabled = newValue } } + } + + /// if true, value highlightning is enabled + @objc open var isHighlightEnabled: Bool { return highlightEnabled } + + /// Clears this data object from all DataSets and removes all Entries. + /// Don't forget to invalidate the chart after this. + @objc open func clearValues() + { + dataSets.removeAll(keepingCapacity: false) + notifyDataChanged() + } + + /// Checks if this data object contains the specified DataSet. + /// + /// - Returns: `true` if so, `false` ifnot. + @objc open func contains(dataSet: IChartDataSet) -> Bool + { + return dataSets.contains { $0 === dataSet } + } + + /// The total entry count across all DataSet objects this data object contains. + @objc open var entryCount: Int + { + return _dataSets.reduce(0) { $0 + $1.entryCount } + } + + /// The DataSet object with the maximum number of entries or null if there are no DataSets. + @objc open var maxEntryCountSet: IChartDataSet? + { + return dataSets.max { $0.entryCount < $1.entryCount } + } + + // MARK: - Accessibility + + /// When the data entry labels are generated identifiers, set this property to prepend a string before each identifier + /// + /// For example, if a label is "#3", settings this property to "Item" allows it to be spoken as "Item #3" + @objc open var accessibilityEntryLabelPrefix: String? + + /// When the data entry value requires a unit, use this property to append the string representation of the unit to the value + /// + /// For example, if a value is "44.1", setting this property to "m" allows it to be spoken as "44.1 m" + @objc open var accessibilityEntryLabelSuffix: String? + + /// If the data entry value is a count, set this to true to allow plurals and other grammatical changes + /// **default**: false + @objc open var accessibilityEntryLabelSuffixIsCount: Bool = false +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataEntry.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataEntry.swift new file mode 100644 index 000000000..3f29f964f --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataEntry.swift @@ -0,0 +1,110 @@ +// +// ChartDataEntry.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +open class ChartDataEntry: ChartDataEntryBase, NSCopying +{ + /// the x value + @objc open var x = 0.0 + + public required init() + { + super.init() + } + + /// An Entry represents one single entry in the chart. + /// + /// - Parameters: + /// - x: the x value + /// - y: the y value (the actual value of the entry) + @objc public init(x: Double, y: Double) + { + super.init(y: y) + self.x = x + } + + /// An Entry represents one single entry in the chart. + /// + /// - Parameters: + /// - x: the x value + /// - y: the y value (the actual value of the entry) + /// - data: Space for additional data this Entry represents. + + @objc public convenience init(x: Double, y: Double, data: Any?) + { + self.init(x: x, y: y) + self.data = data + } + + /// An Entry represents one single entry in the chart. + /// + /// - Parameters: + /// - x: the x value + /// - y: the y value (the actual value of the entry) + /// - icon: icon image + + @objc public convenience init(x: Double, y: Double, icon: NSUIImage?) + { + self.init(x: x, y: y) + self.icon = icon + } + + /// An Entry represents one single entry in the chart. + /// + /// - Parameters: + /// - x: the x value + /// - y: the y value (the actual value of the entry) + /// - icon: icon image + /// - data: Space for additional data this Entry represents. + + @objc public convenience init(x: Double, y: Double, icon: NSUIImage?, data: Any?) + { + self.init(x: x, y: y) + self.icon = icon + self.data = data + } + + // MARK: NSObject + + open override var description: String + { + return "ChartDataEntry, x: \(x), y \(y)" + } + + // MARK: NSCopying + + open func copy(with zone: NSZone? = nil) -> Any + { + let copy = type(of: self).init() + + copy.x = x + copy.y = y + copy.data = data + + return copy + } +} + +// MARK: Equatable +extension ChartDataEntry/*: Equatable*/ { + open override func isEqual(_ object: Any?) -> Bool { + guard let object = object as? ChartDataEntry else { return false } + + if self === object + { + return true + } + + return y == object.y + && x == object.x + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataEntryBase.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataEntryBase.swift new file mode 100644 index 000000000..2c9d0d574 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataEntryBase.swift @@ -0,0 +1,96 @@ +// +// ChartDataEntryBase.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +open class ChartDataEntryBase: NSObject +{ + /// the y value + @objc open var y = 0.0 + + /// optional spot for additional data this Entry represents + @objc open var data: Any? + + /// optional icon image + @objc open var icon: NSUIImage? + + public override required init() + { + super.init() + } + + /// An Entry represents one single entry in the chart. + /// + /// - Parameters: + /// - y: the y value (the actual value of the entry) + @objc public init(y: Double) + { + super.init() + + self.y = y + } + + /// - Parameters: + /// - y: the y value (the actual value of the entry) + /// - data: Space for additional data this Entry represents. + + @objc public convenience init(y: Double, data: Any?) + { + self.init(y: y) + + self.data = data + } + + /// - Parameters: + /// - y: the y value (the actual value of the entry) + /// - icon: icon image + + @objc public convenience init(y: Double, icon: NSUIImage?) + { + self.init(y: y) + + self.icon = icon + } + + /// - Parameters: + /// - y: the y value (the actual value of the entry) + /// - icon: icon image + /// - data: Space for additional data this Entry represents. + + @objc public convenience init(y: Double, icon: NSUIImage?, data: Any?) + { + self.init(y: y) + + self.icon = icon + self.data = data + } + + // MARK: NSObject + + open override var description: String + { + return "ChartDataEntryBase, y \(y)" + } +} + +// MARK: Equatable +extension ChartDataEntryBase/*: Equatable*/ { + open override func isEqual(_ object: Any?) -> Bool { + guard let object = object as? ChartDataEntryBase else { return false } + + if self === object + { + return true + } + + return y == object.y + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift new file mode 100644 index 000000000..2c3b506f7 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift @@ -0,0 +1,582 @@ +// +// ChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +/// Determines how to round DataSet index values for `ChartDataSet.entryIndex(x, rounding)` when an exact x-value is not found. +@objc +public enum ChartDataSetRounding: Int +{ + case up = 0 + case down = 1 + case closest = 2 +} + +/// The DataSet class represents one group or type of entries (Entry) in the Chart that belong together. +/// It is designed to logically separate different groups of values inside the Chart (e.g. the values for a specific line in the LineChart, or the values of a specific group of bars in the BarChart). +open class ChartDataSet: ChartBaseDataSet +{ + public required init() + { + entries = [] + + super.init() + } + + public override convenience init(label: String?) + { + self.init(entries: nil, label: label) + } + + @objc public init(entries: [ChartDataEntry]?, label: String?) + { + self.entries = entries ?? [] + + super.init(label: label) + + self.calcMinMax() + } + + @objc public convenience init(entries: [ChartDataEntry]?) + { + self.init(entries: entries, label: "DataSet") + } + + // MARK: - Data functions and accessors + + /// - Note: Calls `notifyDataSetChanged()` after setting a new value. + /// - Returns: The array of y-values that this DataSet represents. + /// the entries that this dataset represents / holds together + @available(*, unavailable, renamed: "entries") + @objc + open var values: [ChartDataEntry] { return entries } + + @objc + open private(set) var entries: [ChartDataEntry] + + /// Used to replace all entries of a data set while retaining styling properties. + /// This is a separate method from a setter on `entries` to encourage usage + /// of `Collection` conformances. + /// + /// - Parameter entries: new entries to replace existing entries in the dataset + @objc + public func replaceEntries(_ entries: [ChartDataEntry]) { + self.entries = entries + notifyDataSetChanged() + } + + /// maximum y-value in the value array + internal var _yMax: Double = -Double.greatestFiniteMagnitude + + /// minimum y-value in the value array + internal var _yMin: Double = Double.greatestFiniteMagnitude + + /// maximum x-value in the value array + internal var _xMax: Double = -Double.greatestFiniteMagnitude + + /// minimum x-value in the value array + internal var _xMin: Double = Double.greatestFiniteMagnitude + + open override func calcMinMax() + { + _yMax = -Double.greatestFiniteMagnitude + _yMin = Double.greatestFiniteMagnitude + _xMax = -Double.greatestFiniteMagnitude + _xMin = Double.greatestFiniteMagnitude + + guard !isEmpty else { return } + + forEach(calcMinMax) + } + + open override func calcMinMaxY(fromX: Double, toX: Double) + { + _yMax = -Double.greatestFiniteMagnitude + _yMin = Double.greatestFiniteMagnitude + + guard !isEmpty else { return } + + let indexFrom = entryIndex(x: fromX, closestToY: Double.nan, rounding: .down) + let indexTo = entryIndex(x: toX, closestToY: Double.nan, rounding: .up) + + guard !(indexTo < indexFrom) else { return } + // only recalculate y + self[indexFrom...indexTo].forEach(calcMinMaxY) + } + + @objc open func calcMinMaxX(entry e: ChartDataEntry) + { + if e.x < _xMin + { + _xMin = e.x + } + if e.x > _xMax + { + _xMax = e.x + } + } + + @objc open func calcMinMaxY(entry e: ChartDataEntry) + { + if e.y < _yMin + { + _yMin = e.y + } + if e.y > _yMax + { + _yMax = e.y + } + } + + /// Updates the min and max x and y value of this DataSet based on the given Entry. + /// + /// - Parameters: + /// - e: + internal func calcMinMax(entry e: ChartDataEntry) + { + calcMinMaxX(entry: e) + calcMinMaxY(entry: e) + } + + /// The minimum y-value this DataSet holds + open override var yMin: Double { return _yMin } + + /// The maximum y-value this DataSet holds + open override var yMax: Double { return _yMax } + + /// The minimum x-value this DataSet holds + open override var xMin: Double { return _xMin } + + /// The maximum x-value this DataSet holds + open override var xMax: Double { return _xMax } + + /// The number of y-values this DataSet represents + @available(*, deprecated, message: "Use `count` instead") + open override var entryCount: Int { return count } + + /// - Throws: out of bounds + /// if `i` is out of bounds, it may throw an out-of-bounds exception + /// - Returns: The entry object found at the given index (not x-value!) + @available(*, deprecated, message: "Use `subscript(index:)` instead.") + open override func entryForIndex(_ i: Int) -> ChartDataEntry? + { + guard i >= startIndex, i < endIndex else { + return nil + } + return self[i] + } + + /// - Parameters: + /// - xValue: the x-value + /// - closestToY: If there are multiple y-values for the specified x-value, + /// - rounding: determine whether to round up/down/closest if there is no Entry matching the provided x-value + /// - Returns: The first Entry object found at the given x-value with binary search. + /// If the no Entry at the specified x-value is found, this method returns the Entry at the closest x-value according to the rounding. + /// nil if no Entry object at that x-value. + open override func entryForXValue( + _ xValue: Double, + closestToY yValue: Double, + rounding: ChartDataSetRounding) -> ChartDataEntry? + { + let index = entryIndex(x: xValue, closestToY: yValue, rounding: rounding) + if index > -1 + { + return self[index] + } + return nil + } + + /// - Parameters: + /// - xValue: the x-value + /// - closestToY: If there are multiple y-values for the specified x-value, + /// - Returns: The first Entry object found at the given x-value with binary search. + /// If the no Entry at the specified x-value is found, this method returns the Entry at the closest x-value. + /// nil if no Entry object at that x-value. + open override func entryForXValue( + _ xValue: Double, + closestToY yValue: Double) -> ChartDataEntry? + { + return entryForXValue(xValue, closestToY: yValue, rounding: .closest) + } + + /// - Returns: All Entry objects found at the given xIndex with binary search. + /// An empty array if no Entry object at that index. + open override func entriesForXValue(_ xValue: Double) -> [ChartDataEntry] + { + var entries = [ChartDataEntry]() + + var low = startIndex + var high = endIndex - 1 + + while low <= high + { + var m = (high + low) / 2 + var entry = self[m] + + // if we have a match + if xValue == entry.x + { + while m > 0 && self[m - 1].x == xValue + { + m -= 1 + } + + high = endIndex + + // loop over all "equal" entries + while m < high + { + entry = self[m] + if entry.x == xValue + { + entries.append(entry) + } + else + { + break + } + + m += 1 + } + + break + } + else + { + if xValue > entry.x + { + low = m + 1 + } + else + { + high = m - 1 + } + } + } + + return entries + } + + /// - Parameters: + /// - xValue: x-value of the entry to search for + /// - closestToY: If there are multiple y-values for the specified x-value, + /// - rounding: Rounding method if exact value was not found + /// - Returns: The array-index of the specified entry. + /// If the no Entry at the specified x-value is found, this method returns the index of the Entry at the closest x-value according to the rounding. + open override func entryIndex( + x xValue: Double, + closestToY yValue: Double, + rounding: ChartDataSetRounding) -> Int + { + var low = startIndex + var high = endIndex - 1 + var closest = high + + while low < high + { + let m = (low + high) / 2 + + let d1 = self[m].x - xValue + let d2 = self[m + 1].x - xValue + let ad1 = abs(d1), ad2 = abs(d2) + + if ad2 < ad1 + { + // [m + 1] is closer to xValue + // Search in an higher place + low = m + 1 + } + else if ad1 < ad2 + { + // [m] is closer to xValue + // Search in a lower place + high = m + } + else + { + // We have multiple sequential x-value with same distance + + if d1 >= 0.0 + { + // Search in a lower place + high = m + } + else if d1 < 0.0 + { + // Search in an higher place + low = m + 1 + } + } + + closest = high + } + + if closest != -1 + { + let closestXValue = self[closest].x + + if rounding == .up + { + // If rounding up, and found x-value is lower than specified x, and we can go upper... + if closestXValue < xValue && closest < endIndex - 1 + { + closest += 1 + } + } + else if rounding == .down + { + // If rounding down, and found x-value is upper than specified x, and we can go lower... + if closestXValue > xValue && closest > 0 + { + closest -= 1 + } + } + + // Search by closest to y-value + if !yValue.isNaN + { + while closest > 0 && self[closest - 1].x == closestXValue + { + closest -= 1 + } + + var closestYValue = self[closest].y + var closestYIndex = closest + + while true + { + closest += 1 + if closest >= endIndex { break } + + let value = self[closest] + + if value.x != closestXValue { break } + if abs(value.y - yValue) <= abs(closestYValue - yValue) + { + closestYValue = yValue + closestYIndex = closest + } + } + + closest = closestYIndex + } + } + + return closest + } + + /// - Parameters: + /// - e: the entry to search for + /// - Returns: The array-index of the specified entry + @available(*, deprecated, message: "Use `firstIndex(of:)` or `lastIndex(of:)`") + open override func entryIndex(entry e: ChartDataEntry) -> Int + { + return firstIndex(of: e) ?? -1 + } + + /// Adds an Entry to the DataSet dynamically. + /// Entries are added to the end of the list. + /// This will also recalculate the current minimum and maximum values of the DataSet and the value-sum. + /// + /// - Parameters: + /// - e: the entry to add + /// - Returns: True + @available(*, deprecated, message: "Use `append(_:)` instead") + open override func addEntry(_ e: ChartDataEntry) -> Bool + { + append(e) + return true + } + + /// Adds an Entry to the DataSet dynamically. + /// Entries are added to their appropriate index respective to it's x-index. + /// This will also recalculate the current minimum and maximum values of the DataSet and the value-sum. + /// + /// - Parameters: + /// - e: the entry to add + /// - Returns: True + open override func addEntryOrdered(_ e: ChartDataEntry) -> Bool + { + calcMinMax(entry: e) + + if let last = last, last.x > e.x + { + var closestIndex = entryIndex(x: e.x, closestToY: e.y, rounding: .up) + while self[closestIndex].x < e.x + { + closestIndex += 1 + } + entries.insert(e, at: closestIndex) + } + else + { + append(e) + } + + return true + } + + @available(*, renamed: "remove(_:)") + open override func removeEntry(_ entry: ChartDataEntry) -> Bool + { + return remove(entry) + } + + /// Removes an Entry from the DataSet dynamically. + /// This will also recalculate the current minimum and maximum values of the DataSet and the value-sum. + /// + /// - Parameters: + /// - entry: the entry to remove + /// - Returns: `true` if the entry was removed successfully, else if the entry does not exist + open func remove(_ entry: ChartDataEntry) -> Bool + { + guard let index = firstIndex(of: entry) else { return false } + _ = remove(at: index) + return true + } + + /// Removes the first Entry (at index 0) of this DataSet from the entries array. + /// + /// - Returns: `true` if successful, `false` if not. + @available(*, deprecated, message: "Use `func removeFirst() -> ChartDataEntry` instead.") + open override func removeFirst() -> Bool + { + let entry: ChartDataEntry? = isEmpty ? nil : removeFirst() + return entry != nil + } + + /// Removes the last Entry (at index size-1) of this DataSet from the entries array. + /// + /// - Returns: `true` if successful, `false` if not. + @available(*, deprecated, message: "Use `func removeLast() -> ChartDataEntry` instead.") + open override func removeLast() -> Bool + { + let entry: ChartDataEntry? = isEmpty ? nil : removeLast() + return entry != nil + } + + /// Removes all values from this DataSet and recalculates min and max value. + @available(*, deprecated, message: "Use `removeAll(keepingCapacity:)` instead.") + open override func clear() + { + removeAll(keepingCapacity: true) + } + + // MARK: - Data functions and accessors + + // MARK: - NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! ChartDataSet + + copy.entries = entries + copy._yMax = _yMax + copy._yMin = _yMin + copy._xMax = _xMax + copy._xMin = _xMin + + return copy + } +} + +// MARK: MutableCollection +extension ChartDataSet: MutableCollection { + public typealias Index = Int + public typealias Element = ChartDataEntry + + public var startIndex: Index { + return entries.startIndex + } + + public var endIndex: Index { + return entries.endIndex + } + + public func index(after: Index) -> Index { + return entries.index(after: after) + } + + @objc + public subscript(position: Index) -> Element { + get { + // This is intentionally not a safe subscript to mirror + // the behaviour of the built in Swift Collection Types + return entries[position] + } + set { + calcMinMax(entry: newValue) + entries[position] = newValue + } + } +} + +// MARK: RandomAccessCollection +extension ChartDataSet: RandomAccessCollection { + public func index(before: Index) -> Index { + return entries.index(before: before) + } +} + +// MARK: RangeReplaceableCollection +extension ChartDataSet: RangeReplaceableCollection { + public func append(_ newElement: Element) { + calcMinMax(entry: newElement) + entries.append(newElement) + } + + public func remove(at position: Index) -> Element { + let element = entries.remove(at: position) + notifyDataSetChanged() + return element + } + + public func removeFirst() -> Element { + let element = entries.removeFirst() + notifyDataSetChanged() + return element + } + + public func removeFirst(_ n: Int) { + entries.removeFirst(n) + notifyDataSetChanged() + } + + public func removeLast() -> Element { + let element = entries.removeLast() + notifyDataSetChanged() + return element + } + + public func removeLast(_ n: Int) { + entries.removeLast(n) + notifyDataSetChanged() + } + + public func removeSubrange(_ bounds: R) where R : RangeExpression, Index == R.Bound { + entries.removeSubrange(bounds) + notifyDataSetChanged() + } + + @objc + public func removeAll(keepingCapacity keepCapacity: Bool) { + entries.removeAll(keepingCapacity: keepCapacity) + notifyDataSetChanged() + } + + public func replaceSubrange(_ subrange: Swift.Range, with newElements: C) where C : Collection, Element == C.Element + { + assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") + + entries.replaceSubrange(subrange, with: newElements) + newElements.forEach { self.calcMinMax(entry: $0) } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/CombinedChartData.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/CombinedChartData.swift new file mode 100644 index 000000000..e883c8b7f --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/CombinedChartData.swift @@ -0,0 +1,301 @@ +// +// CombinedChartData.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +open class CombinedChartData: BarLineScatterCandleBubbleChartData +{ + private var _lineData: LineChartData! + private var _barData: BarChartData! + private var _scatterData: ScatterChartData! + private var _candleData: CandleChartData! + private var _bubbleData: BubbleChartData! + + public override init() + { + super.init() + } + + public override init(dataSets: [IChartDataSet]?) + { + super.init(dataSets: dataSets) + } + + @objc open var lineData: LineChartData! + { + get + { + return _lineData + } + set + { + _lineData = newValue + notifyDataChanged() + } + } + + @objc open var barData: BarChartData! + { + get + { + return _barData + } + set + { + _barData = newValue + notifyDataChanged() + } + } + + @objc open var scatterData: ScatterChartData! + { + get + { + return _scatterData + } + set + { + _scatterData = newValue + notifyDataChanged() + } + } + + @objc open var candleData: CandleChartData! + { + get + { + return _candleData + } + set + { + _candleData = newValue + notifyDataChanged() + } + } + + @objc open var bubbleData: BubbleChartData! + { + get + { + return _bubbleData + } + set + { + _bubbleData = newValue + notifyDataChanged() + } + } + + open override func calcMinMax() + { + _dataSets.removeAll() + + _yMax = -Double.greatestFiniteMagnitude + _yMin = Double.greatestFiniteMagnitude + _xMax = -Double.greatestFiniteMagnitude + _xMin = Double.greatestFiniteMagnitude + + _leftAxisMax = -Double.greatestFiniteMagnitude + _leftAxisMin = Double.greatestFiniteMagnitude + _rightAxisMax = -Double.greatestFiniteMagnitude + _rightAxisMin = Double.greatestFiniteMagnitude + + let allData = self.allData + + for data in allData + { + data.calcMinMax() + + let sets = data.dataSets + _dataSets.append(contentsOf: sets) + + if data.yMax > _yMax + { + _yMax = data.yMax + } + + if data.yMin < _yMin + { + _yMin = data.yMin + } + + if data.xMax > _xMax + { + _xMax = data.xMax + } + + if data.xMin < _xMin + { + _xMin = data.xMin + } + + for dataset in sets + { + if dataset.axisDependency == .left + { + if dataset.yMax > _leftAxisMax + { + _leftAxisMax = dataset.yMax + } + if dataset.yMin < _leftAxisMin + { + _leftAxisMin = dataset.yMin + } + } + else + { + if dataset.yMax > _rightAxisMax + { + _rightAxisMax = dataset.yMax + } + if dataset.yMin < _rightAxisMin + { + _rightAxisMin = dataset.yMin + } + } + } + } + } + + /// All data objects in row: line-bar-scatter-candle-bubble if not null. + @objc open var allData: [ChartData] + { + var data = [ChartData]() + + if lineData !== nil + { + data.append(lineData) + } + if barData !== nil + { + data.append(barData) + } + if scatterData !== nil + { + data.append(scatterData) + } + if candleData !== nil + { + data.append(candleData) + } + if bubbleData !== nil + { + data.append(bubbleData) + } + + return data + } + + @objc open func dataByIndex(_ index: Int) -> ChartData + { + return allData[index] + } + + open func dataIndex(_ data: ChartData) -> Int? + { + return allData.firstIndex(of: data) + } + + open override func removeDataSet(_ dataSet: IChartDataSet) -> Bool + { + return allData.contains { $0.removeDataSet(dataSet) } + } + + open override func removeDataSetByIndex(_ index: Int) -> Bool + { + print("removeDataSet(index) not supported for CombinedData", terminator: "\n") + return false + } + + open override func removeEntry(_ entry: ChartDataEntry, dataSetIndex: Int) -> Bool + { + print("removeEntry(entry, dataSetIndex) not supported for CombinedData", terminator: "\n") + return false + } + + open override func removeEntry(xValue: Double, dataSetIndex: Int) -> Bool + { + print("removeEntry(xValue, dataSetIndex) not supported for CombinedData", terminator: "\n") + return false + } + + open override func notifyDataChanged() + { + if _lineData !== nil + { + _lineData.notifyDataChanged() + } + if _barData !== nil + { + _barData.notifyDataChanged() + } + if _scatterData !== nil + { + _scatterData.notifyDataChanged() + } + if _candleData !== nil + { + _candleData.notifyDataChanged() + } + if _bubbleData !== nil + { + _bubbleData.notifyDataChanged() + } + + super.notifyDataChanged() // recalculate everything + } + + /// Get the Entry for a corresponding highlight object + /// + /// - Parameters: + /// - highlight: + /// - Returns: The entry that is highlighted + open override func entryForHighlight(_ highlight: Highlight) -> ChartDataEntry? + { + if highlight.dataIndex >= allData.count + { + return nil + } + + let data = dataByIndex(highlight.dataIndex) + + if highlight.dataSetIndex >= data.dataSetCount + { + return nil + } + + // The value of the highlighted entry could be NaN - if we are not interested in highlighting a specific value. + let entries = data.getDataSetByIndex(highlight.dataSetIndex).entriesForXValue(highlight.x) + return entries.first { $0.y == highlight.y || highlight.y.isNaN } + } + + /// Get dataset for highlight + /// + /// - Parameters: + /// - highlight: current highlight + /// - Returns: dataset related to highlight + @objc open func getDataSetByHighlight(_ highlight: Highlight) -> IChartDataSet! + { + if highlight.dataIndex >= allData.count + { + return nil + } + + let data = dataByIndex(highlight.dataIndex) + + if highlight.dataSetIndex >= data.dataSetCount + { + return nil + } + + return data.dataSets[highlight.dataSetIndex] + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineChartData.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineChartData.swift new file mode 100644 index 000000000..2ebd6b42a --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineChartData.swift @@ -0,0 +1,26 @@ +// +// LineChartData.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +/// Data object that encapsulates all data associated with a LineChart. +open class LineChartData: ChartData +{ + public override init() + { + super.init() + } + + public override init(dataSets: [IChartDataSet]?) + { + super.init(dataSets: dataSets) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineChartDataSet.swift new file mode 100644 index 000000000..b53ddd4d0 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineChartDataSet.swift @@ -0,0 +1,173 @@ +// +// LineChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +open class LineChartDataSet: LineRadarChartDataSet, ILineChartDataSet +{ + @objc(LineChartMode) + public enum Mode: Int + { + case linear + case stepped + case cubicBezier + case horizontalBezier + } + + private func initialize() + { + // default color + circleColors.append(NSUIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0)) + } + + public required init() + { + super.init() + initialize() + } + + public override init(entries: [ChartDataEntry]?, label: String?) + { + super.init(entries: entries, label: label) + initialize() + } + + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// The drawing mode for this line dataset + /// + /// **default**: Linear + open var mode: Mode = Mode.linear + + private var _cubicIntensity = CGFloat(0.2) + + /// Intensity for cubic lines (min = 0.05, max = 1) + /// + /// **default**: 0.2 + open var cubicIntensity: CGFloat + { + get + { + return _cubicIntensity + } + set + { + _cubicIntensity = newValue.clamped(to: 0.05...1) + } + } + + /// The radius of the drawn circles. + open var circleRadius = CGFloat(8.0) + + /// The hole radius of the drawn circles + open var circleHoleRadius = CGFloat(4.0) + + open var circleColors = [NSUIColor]() + + /// - Returns: The color at the given index of the DataSet's circle-color array. + /// Performs a IndexOutOfBounds check by modulus. + open func getCircleColor(atIndex index: Int) -> NSUIColor? + { + let size = circleColors.count + let index = index % size + if index >= size + { + return nil + } + return circleColors[index] + } + + /// Sets the one and ONLY color that should be used for this DataSet. + /// Internally, this recreates the colors array and adds the specified color. + open func setCircleColor(_ color: NSUIColor) + { + circleColors.removeAll(keepingCapacity: false) + circleColors.append(color) + } + + open func setCircleColors(_ colors: NSUIColor...) + { + circleColors.removeAll(keepingCapacity: false) + circleColors.append(contentsOf: colors) + } + + /// Resets the circle-colors array and creates a new one + open func resetCircleColors(_ index: Int) + { + circleColors.removeAll(keepingCapacity: false) + } + + /// If true, drawing circles is enabled + open var drawCirclesEnabled = true + + /// `true` if drawing circles for this DataSet is enabled, `false` ifnot + open var isDrawCirclesEnabled: Bool { return drawCirclesEnabled } + + /// The color of the inner circle (the circle-hole). + open var circleHoleColor: NSUIColor? = NSUIColor.white + + /// `true` if drawing circles for this DataSet is enabled, `false` ifnot + open var drawCircleHoleEnabled = true + + /// `true` if drawing the circle-holes is enabled, `false` ifnot. + open var isDrawCircleHoleEnabled: Bool { return drawCircleHoleEnabled } + + /// This is how much (in pixels) into the dash pattern are we starting from. + open var lineDashPhase = CGFloat(0.0) + + /// This is the actual dash pattern. + /// I.e. [2, 3] will paint [-- -- ] + /// [1, 3, 4, 2] will paint [- ---- - ---- ] + open var lineDashLengths: [CGFloat]? + + /// Line cap type, default is CGLineCap.Butt + open var lineCapType = CGLineCap.butt + + /// formatter for customizing the position of the fill-line + private var _fillFormatter: IFillFormatter = DefaultFillFormatter() + + /// Sets a custom IFillFormatter to the chart that handles the position of the filled-line for each DataSet. Set this to null to use the default logic. + open var fillFormatter: IFillFormatter? + { + get + { + return _fillFormatter + } + set + { + _fillFormatter = newValue ?? DefaultFillFormatter() + } + } + + // MARK: NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! LineChartDataSet + copy.circleColors = circleColors + copy.circleHoleColor = circleHoleColor + copy.circleRadius = circleRadius + copy.circleHoleRadius = circleHoleRadius + copy.cubicIntensity = cubicIntensity + copy.lineDashPhase = lineDashPhase + copy.lineDashLengths = lineDashLengths + copy.lineCapType = lineCapType + copy.drawCirclesEnabled = drawCirclesEnabled + copy.drawCircleHoleEnabled = drawCircleHoleEnabled + copy.mode = mode + copy._fillFormatter = _fillFormatter + return copy + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineRadarChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineRadarChartDataSet.swift new file mode 100644 index 000000000..54a3af692 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineRadarChartDataSet.swift @@ -0,0 +1,85 @@ +// +// LineRadarChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +open class LineRadarChartDataSet: LineScatterCandleRadarChartDataSet, ILineRadarChartDataSet +{ + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// The color that is used for filling the line surface area. + private var _fillColor = NSUIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0) + + /// The color that is used for filling the line surface area. + open var fillColor: NSUIColor + { + get { return _fillColor } + set + { + _fillColor = newValue + fill = nil + } + } + + /// The object that is used for filling the area below the line. + /// **default**: nil + open var fill: Fill? + + /// The alpha value that is used for filling the line surface, + /// **default**: 0.33 + open var fillAlpha = CGFloat(0.33) + + private var _lineWidth = CGFloat(1.0) + + /// line width of the chart (min = 0.0, max = 10) + /// + /// **default**: 1 + open var lineWidth: CGFloat + { + get + { + return _lineWidth + } + set + { + _lineWidth = newValue.clamped(to: 0...10) + } + } + + /// Set to `true` if the DataSet should be drawn filled (surface), and not just as a line. + /// Disabling this will give great performance boost. + /// Please note that this method uses the path clipping for drawing the filled area (with images, gradients and layers). + open var drawFilledEnabled = false + + /// `true` if filled drawing is enabled, `false` ifnot + open var isDrawFilledEnabled: Bool + { + return drawFilledEnabled + } + + // MARK: NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! LineRadarChartDataSet + copy.fill = fill + copy.fillAlpha = fillAlpha + copy._fillColor = _fillColor + copy._lineWidth = _lineWidth + copy.drawFilledEnabled = drawFilledEnabled + return copy + } + +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineScatterCandleRadarChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineScatterCandleRadarChartDataSet.swift new file mode 100644 index 000000000..1c68983bb --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/LineScatterCandleRadarChartDataSet.swift @@ -0,0 +1,51 @@ +// +// LineScatterCandleRadarChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + + +open class LineScatterCandleRadarChartDataSet: BarLineScatterCandleBubbleChartDataSet, ILineScatterCandleRadarChartDataSet +{ + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// Enables / disables the horizontal highlight-indicator. If disabled, the indicator is not drawn. + open var drawHorizontalHighlightIndicatorEnabled = true + + /// Enables / disables the vertical highlight-indicator. If disabled, the indicator is not drawn. + open var drawVerticalHighlightIndicatorEnabled = true + + /// `true` if horizontal highlight indicator lines are enabled (drawn) + open var isHorizontalHighlightIndicatorEnabled: Bool { return drawHorizontalHighlightIndicatorEnabled } + + /// `true` if vertical highlight indicator lines are enabled (drawn) + open var isVerticalHighlightIndicatorEnabled: Bool { return drawVerticalHighlightIndicatorEnabled } + + /// Enables / disables both vertical and horizontal highlight-indicators. + /// :param: enabled + open func setDrawHighlightIndicators(_ enabled: Bool) + { + drawHorizontalHighlightIndicatorEnabled = enabled + drawVerticalHighlightIndicatorEnabled = enabled + } + + // MARK: NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! LineScatterCandleRadarChartDataSet + copy.drawHorizontalHighlightIndicatorEnabled = drawHorizontalHighlightIndicatorEnabled + copy.drawVerticalHighlightIndicatorEnabled = drawVerticalHighlightIndicatorEnabled + return copy + } + +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartData.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartData.swift new file mode 100644 index 000000000..7f3438351 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartData.swift @@ -0,0 +1,124 @@ +// +// PieData.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +open class PieChartData: ChartData +{ + public override init() + { + super.init() + } + + public override init(dataSets: [IChartDataSet]?) + { + super.init(dataSets: dataSets) + } + + /// All DataSet objects this ChartData object holds. + @objc open override var dataSets: [IChartDataSet] + { + get + { + assert(super.dataSets.count <= 1, "Found multiple data sets while pie chart only allows one") + return super.dataSets + } + set + { + super.dataSets = newValue + } + } + + @objc var dataSet: IPieChartDataSet? + { + get + { + return dataSets.count > 0 ? dataSets[0] as? IPieChartDataSet : nil + } + set + { + if let newValue = newValue + { + dataSets = [newValue] + } + else + { + dataSets = [] + } + } + } + + open override func getDataSetByIndex(_ index: Int) -> IChartDataSet? + { + if index != 0 + { + return nil + } + return super.getDataSetByIndex(index) + } + + open override func getDataSetByLabel(_ label: String, ignorecase: Bool) -> IChartDataSet? + { + if dataSets.count == 0 || dataSets[0].label == nil + { + return nil + } + + if ignorecase + { + if let label = dataSets[0].label, label.caseInsensitiveCompare(label) == .orderedSame + { + return dataSets[0] + } + } + else + { + if label == dataSets[0].label + { + return dataSets[0] + } + } + return nil + } + + open override func entryForHighlight(_ highlight: Highlight) -> ChartDataEntry? + { + return dataSet?.entryForIndex(Int(highlight.x)) + } + + open override func addDataSet(_ d: IChartDataSet!) + { + super.addDataSet(d) + } + + /// Removes the DataSet at the given index in the DataSet array from the data object. + /// Also recalculates all minimum and maximum values. + /// + /// - Returns: `true` if a DataSet was removed, `false` ifno DataSet could be removed. + open override func removeDataSetByIndex(_ index: Int) -> Bool + { + if index >= _dataSets.count || index < 0 + { + return false + } + + return false + } + + /// The total y-value sum across all DataSet objects the this object represents. + @objc open var yValueSum: Double + { + guard let dataSet = dataSet else { return 0.0 } + return (0.. Any + { + let copy = super.copy(with: zone) as! PieChartDataEntry + copy.label = label + return copy + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartDataSet.swift new file mode 100644 index 000000000..ac0e63489 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/PieChartDataSet.swift @@ -0,0 +1,135 @@ +// +// PieChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class PieChartDataSet: ChartDataSet, IPieChartDataSet +{ + @objc(PieChartValuePosition) + public enum ValuePosition: Int + { + case insideSlice + case outsideSlice + } + + private func initialize() + { + self.valueTextColor = NSUIColor.white + self.valueFont = NSUIFont.systemFont(ofSize: 13.0) + } + + public required init() + { + super.init() + initialize() + } + + public override init(entries: [ChartDataEntry]?, label: String?) + { + super.init(entries: entries, label: label) + initialize() + } + + internal override func calcMinMax(entry e: ChartDataEntry) + { + calcMinMaxY(entry: e) + } + + // MARK: - Styling functions and accessors + + private var _sliceSpace = CGFloat(0.0) + + /// the space in pixels between the pie-slices + /// **default**: 0 + /// **maximum**: 20 + open var sliceSpace: CGFloat + { + get + { + return _sliceSpace + } + set + { + var space = newValue + if space > 20.0 + { + space = 20.0 + } + if space < 0.0 + { + space = 0.0 + } + _sliceSpace = space + } + } + + /// When enabled, slice spacing will be 0.0 when the smallest value is going to be smaller than the slice spacing itself. + open var automaticallyDisableSliceSpacing: Bool = false + + /// indicates the selection distance of a pie slice + open var selectionShift = CGFloat(18.0) + + open var xValuePosition: ValuePosition = .insideSlice + open var yValuePosition: ValuePosition = .insideSlice + + /// When valuePosition is OutsideSlice, indicates line color + open var valueLineColor: NSUIColor? = NSUIColor.black + + /// When valuePosition is OutsideSlice and enabled, line will have the same color as the slice + open var useValueColorForLine: Bool = false + + /// When valuePosition is OutsideSlice, indicates line width + open var valueLineWidth: CGFloat = 1.0 + + /// When valuePosition is OutsideSlice, indicates offset as percentage out of the slice size + open var valueLinePart1OffsetPercentage: CGFloat = 0.75 + + /// When valuePosition is OutsideSlice, indicates length of first half of the line + open var valueLinePart1Length: CGFloat = 0.3 + + /// When valuePosition is OutsideSlice, indicates length of second half of the line + open var valueLinePart2Length: CGFloat = 0.4 + + /// When valuePosition is OutsideSlice, this allows variable line length + open var valueLineVariableLength: Bool = true + + /// the font for the slice-text labels + open var entryLabelFont: NSUIFont? = nil + + /// the color for the slice-text labels + open var entryLabelColor: NSUIColor? = nil + + /// the color for the highlighted sector + open var highlightColor: NSUIColor? = nil + + // MARK: - NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! PieChartDataSet + copy._sliceSpace = _sliceSpace + copy.automaticallyDisableSliceSpacing = automaticallyDisableSliceSpacing + copy.selectionShift = selectionShift + copy.xValuePosition = xValuePosition + copy.yValuePosition = yValuePosition + copy.valueLineColor = valueLineColor + copy.valueLineWidth = valueLineWidth + copy.valueLinePart1OffsetPercentage = valueLinePart1OffsetPercentage + copy.valueLinePart1Length = valueLinePart1Length + copy.valueLinePart2Length = valueLinePart2Length + copy.valueLineVariableLength = valueLineVariableLength + copy.entryLabelFont = entryLabelFont + copy.entryLabelColor = entryLabelColor + copy.highlightColor = highlightColor + return copy + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartData.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartData.swift new file mode 100644 index 000000000..31fd7d2ba --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartData.swift @@ -0,0 +1,46 @@ +// +// RadarChartData.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +open class RadarChartData: ChartData +{ + @objc open var highlightColor = NSUIColor(red: 255.0/255.0, green: 187.0/255.0, blue: 115.0/255.0, alpha: 1.0) + @objc open var highlightLineWidth = CGFloat(1.0) + @objc open var highlightLineDashPhase = CGFloat(0.0) + @objc open var highlightLineDashLengths: [CGFloat]? + + /// Sets labels that should be drawn around the RadarChart at the end of each web line. + @objc open var labels = [String]() + + /// Sets the labels that should be drawn around the RadarChart at the end of each web line. + open func setLabels(_ labels: String...) + { + self.labels = labels + } + + public override init() + { + super.init() + } + + public override init(dataSets: [IChartDataSet]?) + { + super.init(dataSets: dataSets) + } + + open override func entryForHighlight(_ highlight: Highlight) -> ChartDataEntry? + { + return getDataSetByIndex(highlight.dataSetIndex)?.entryForIndex(Int(highlight.x)) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataEntry.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataEntry.swift new file mode 100644 index 000000000..576ad24c2 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataEntry.swift @@ -0,0 +1,54 @@ +// +// RadarChartDataEntry.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class RadarChartDataEntry: ChartDataEntry +{ + public required init() + { + super.init() + } + + /// - Parameters: + /// - value: The value on the y-axis. + @objc public init(value: Double) + { + super.init(x: .nan, y: value) + } + + /// - Parameters: + /// - value: The value on the y-axis. + /// - data: Spot for additional data this Entry represents. + @objc public convenience init(value: Double, data: Any?) + { + self.init(value: value) + self.data = data + } + + // MARK: Data property accessors + + @objc open var value: Double + { + get { return y } + set { y = newValue } + } + + // MARK: NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! RadarChartDataEntry + + return copy + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataSet.swift new file mode 100644 index 000000000..030269d6a --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataSet.swift @@ -0,0 +1,59 @@ +// +// RadarChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +open class RadarChartDataSet: LineRadarChartDataSet, IRadarChartDataSet +{ + private func initialize() + { + self.valueFont = NSUIFont.systemFont(ofSize: 13.0) + } + + public required init() + { + super.init() + initialize() + } + + public required override init(entries: [ChartDataEntry]?, label: String?) + { + super.init(entries: entries, label: label) + initialize() + } + + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// flag indicating whether highlight circle should be drawn or not + /// **default**: false + open var drawHighlightCircleEnabled: Bool = false + + /// `true` if highlight circle should be drawn, `false` ifnot + open var isDrawHighlightCircleEnabled: Bool { return drawHighlightCircleEnabled } + + open var highlightCircleFillColor: NSUIColor? = NSUIColor.white + + /// The stroke color for highlight circle. + /// If `nil`, the color of the dataset is taken. + open var highlightCircleStrokeColor: NSUIColor? + + open var highlightCircleStrokeAlpha: CGFloat = 0.3 + + open var highlightCircleInnerRadius: CGFloat = 3.0 + + open var highlightCircleOuterRadius: CGFloat = 4.0 + + open var highlightCircleStrokeWidth: CGFloat = 2.0 +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartData.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartData.swift new file mode 100644 index 000000000..e06a60325 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartData.swift @@ -0,0 +1,34 @@ +// +// ScatterChartData.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class ScatterChartData: BarLineScatterCandleBubbleChartData +{ + public override init() + { + super.init() + } + + public override init(dataSets: [IChartDataSet]?) + { + super.init(dataSets: dataSets) + } + + /// - Returns: The maximum shape-size across all DataSets. + @objc open func getGreatestShapeSize() -> CGFloat + { + return (_dataSets as? [IScatterChartDataSet])? + .max { $0.scatterShapeSize < $1.scatterShapeSize }? + .scatterShapeSize ?? 0 + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartDataSet.swift new file mode 100644 index 000000000..bc9767a06 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartDataSet.swift @@ -0,0 +1,78 @@ +// +// ScatterChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class ScatterChartDataSet: LineScatterCandleRadarChartDataSet, IScatterChartDataSet +{ + + @objc(ScatterShape) + public enum Shape: Int + { + case square + case circle + case triangle + case cross + case x + case chevronUp + case chevronDown + } + + /// The size the scatter shape will have + open var scatterShapeSize = CGFloat(10.0) + + /// The radius of the hole in the shape (applies to Square, Circle and Triangle) + /// **default**: 0.0 + open var scatterShapeHoleRadius: CGFloat = 0.0 + + /// Color for the hole in the shape. Setting to `nil` will behave as transparent. + /// **default**: nil + open var scatterShapeHoleColor: NSUIColor? = nil + + /// Sets the ScatterShape this DataSet should be drawn with. + /// This will search for an available IShapeRenderer and set this renderer for the DataSet + @objc open func setScatterShape(_ shape: Shape) + { + self.shapeRenderer = ScatterChartDataSet.renderer(forShape: shape) + } + + /// The IShapeRenderer responsible for rendering this DataSet. + /// This can also be used to set a custom IShapeRenderer aside from the default ones. + /// **default**: `SquareShapeRenderer` + open var shapeRenderer: IShapeRenderer? = SquareShapeRenderer() + + @objc open class func renderer(forShape shape: Shape) -> IShapeRenderer + { + switch shape + { + case .square: return SquareShapeRenderer() + case .circle: return CircleShapeRenderer() + case .triangle: return TriangleShapeRenderer() + case .cross: return CrossShapeRenderer() + case .x: return XShapeRenderer() + case .chevronUp: return ChevronUpShapeRenderer() + case .chevronDown: return ChevronDownShapeRenderer() + } + } + + // MARK: NSCopying + + open override func copy(with zone: NSZone? = nil) -> Any + { + let copy = super.copy(with: zone) as! ScatterChartDataSet + copy.scatterShapeSize = scatterShapeSize + copy.scatterShapeHoleRadius = scatterShapeHoleRadius + copy.scatterShapeHoleColor = scatterShapeHoleColor + copy.shapeRenderer = shapeRenderer + return copy + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IBarChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IBarChartDataSet.swift new file mode 100644 index 000000000..b90b4dc0c --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IBarChartDataSet.swift @@ -0,0 +1,42 @@ +// +// IBarChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol IBarChartDataSet: IBarLineScatterCandleBubbleChartDataSet +{ + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// `true` if this DataSet is stacked (stacksize > 1) or not. + var isStacked: Bool { get } + + /// The maximum number of bars that can be stacked upon another in this DataSet. + var stackSize: Int { get } + + /// the color used for drawing the bar-shadows. The bar shadows is a surface behind the bar that indicates the maximum value + var barShadowColor: NSUIColor { get set } + + /// the width used for drawing borders around the bars. If borderWidth == 0, no border will be drawn. + var barBorderWidth : CGFloat { get set } + + /// the color drawing borders around the bars. + var barBorderColor: NSUIColor { get set } + + /// the alpha value (transparency) that is used for drawing the highlight indicator bar. min = 0.0 (fully transparent), max = 1.0 (fully opaque) + var highlightAlpha: CGFloat { get set } + + /// array of labels used to describe the different values of the stacked bars + var stackLabels: [String] { get set } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IBarLineScatterCandleBubbleChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IBarLineScatterCandleBubbleChartDataSet.swift new file mode 100644 index 000000000..8af47ff4a --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IBarLineScatterCandleBubbleChartDataSet.swift @@ -0,0 +1,26 @@ +// +// IBarLineScatterCandleBubbleChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol IBarLineScatterCandleBubbleChartDataSet: IChartDataSet +{ + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + var highlightColor: NSUIColor { get set } + var highlightLineWidth: CGFloat { get set } + var highlightLineDashPhase: CGFloat { get set } + var highlightLineDashLengths: [CGFloat]? { get set } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IBubbleChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IBubbleChartDataSet.swift new file mode 100644 index 000000000..10c5d9eec --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IBubbleChartDataSet.swift @@ -0,0 +1,27 @@ +// +// IBubbleChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol IBubbleChartDataSet: IBarLineScatterCandleBubbleChartDataSet +{ + // MARK: - Data functions and accessors + + var maxSize: CGFloat { get } + var isNormalizeSizeEnabled: Bool { get } + + // MARK: - Styling functions and accessors + + /// Sets/gets the width of the circle that surrounds the bubble when highlighted + var highlightCircleWidth: CGFloat { get set } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Interfaces/ICandleChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/ICandleChartDataSet.swift new file mode 100644 index 000000000..fac88d3b2 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/ICandleChartDataSet.swift @@ -0,0 +1,66 @@ +// +// ICandleChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol ICandleChartDataSet: ILineScatterCandleRadarChartDataSet +{ + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// the space that is left out on the left and right side of each candle, + /// **default**: 0.1 (10%), max 0.45, min 0.0 + var barSpace: CGFloat { get set } + + /// should the candle bars show? + /// when false, only "ticks" will show + /// + /// **default**: true + var showCandleBar: Bool { get set } + + /// the width of the candle-shadow-line in pixels. + /// + /// **default**: 3.0 + var shadowWidth: CGFloat { get set } + + /// the color of the shadow line + var shadowColor: NSUIColor? { get set } + + /// use candle color for the shadow + var shadowColorSameAsCandle: Bool { get set } + + /// Is the shadow color same as the candle color? + var isShadowColorSameAsCandle: Bool { get } + + /// color for open == close + var neutralColor: NSUIColor? { get set } + + /// color for open > close + var increasingColor: NSUIColor? { get set } + + /// color for open < close + var decreasingColor: NSUIColor? { get set } + + /// Are increasing values drawn as filled? + var increasingFilled: Bool { get set } + + /// Are increasing values drawn as filled? + var isIncreasingFilled: Bool { get } + + /// Are decreasing values drawn as filled? + var decreasingFilled: Bool { get set } + + /// Are decreasing values drawn as filled? + var isDecreasingFilled: Bool { get } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IChartDataSet.swift new file mode 100644 index 000000000..849da103b --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IChartDataSet.swift @@ -0,0 +1,270 @@ +// +// IChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol IChartDataSet +{ + // MARK: - Data functions and accessors + + /// Use this method to tell the data set that the underlying data has changed + func notifyDataSetChanged() + + /// Calculates the minimum and maximum x and y values (_xMin, _xMax, _yMin, _yMax). + func calcMinMax() + + /// Calculates the min and max y-values from the Entry closest to the given fromX to the Entry closest to the given toX value. + /// This is only needed for the autoScaleMinMax feature. + func calcMinMaxY(fromX: Double, toX: Double) + + /// The minimum y-value this DataSet holds + var yMin: Double { get } + + /// The maximum y-value this DataSet holds + var yMax: Double { get } + + /// The minimum x-value this DataSet holds + var xMin: Double { get } + + /// The maximum x-value this DataSet holds + var xMax: Double { get } + + /// The number of y-values this DataSet represents + var entryCount: Int { get } + + /// - Throws: out of bounds + /// if `i` is out of bounds, it may throw an out-of-bounds exception + /// - Returns: The entry object found at the given index (not x-value!) + func entryForIndex(_ i: Int) -> ChartDataEntry? + + /// - Parameters: + /// - xValue: the x-value + /// - closestToY: If there are multiple y-values for the specified x-value, + /// - rounding: determine whether to round up/down/closest if there is no Entry matching the provided x-value + /// - Returns: The first Entry object found at the given x-value with binary search. + /// If the no Entry at the specified x-value is found, this method returns the Entry at the closest x-value according to the rounding. + /// nil if no Entry object at that x-value. + func entryForXValue( + _ xValue: Double, + closestToY yValue: Double, + rounding: ChartDataSetRounding) -> ChartDataEntry? + + /// - Parameters: + /// - xValue: the x-value + /// - closestToY: If there are multiple y-values for the specified x-value, + /// - Returns: The first Entry object found at the given x-value with binary search. + /// If the no Entry at the specified x-value is found, this method returns the Entry at the closest x-value. + /// nil if no Entry object at that x-value. + func entryForXValue( + _ xValue: Double, + closestToY yValue: Double) -> ChartDataEntry? + + /// - Returns: All Entry objects found at the given x-value with binary search. + /// An empty array if no Entry object at that x-value. + func entriesForXValue(_ xValue: Double) -> [ChartDataEntry] + + /// - Parameters: + /// - xValue: x-value of the entry to search for + /// - closestToY: If there are multiple y-values for the specified x-value, + /// - rounding: Rounding method if exact value was not found + /// - Returns: The array-index of the specified entry. + /// If the no Entry at the specified x-value is found, this method returns the index of the Entry at the closest x-value according to the rounding. + func entryIndex( + x xValue: Double, + closestToY yValue: Double, + rounding: ChartDataSetRounding) -> Int + + /// - Parameters: + /// - e: the entry to search for + /// - Returns: The array-index of the specified entry + func entryIndex(entry e: ChartDataEntry) -> Int + + /// Adds an Entry to the DataSet dynamically. + /// + /// *optional feature, can return `false` ifnot implemented* + /// + /// Entries are added to the end of the list. + /// + /// - Parameters: + /// - e: the entry to add + /// - Returns: `true` if the entry was added successfully, `false` ifthis feature is not supported + func addEntry(_ e: ChartDataEntry) -> Bool + + /// Adds an Entry to the DataSet dynamically. + /// Entries are added to their appropriate index in the values array respective to their x-position. + /// This will also recalculate the current minimum and maximum values of the DataSet and the value-sum. + /// + /// *optional feature, can return `false` ifnot implemented* + /// + /// Entries are added to the end of the list. + /// + /// - Parameters: + /// - e: the entry to add + /// - Returns: `true` if the entry was added successfully, `false` ifthis feature is not supported + func addEntryOrdered(_ e: ChartDataEntry) -> Bool + + /// Removes an Entry from the DataSet dynamically. + /// + /// *optional feature, can return `false` ifnot implemented* + /// + /// - Parameters: + /// - entry: the entry to remove + /// - Returns: `true` if the entry was removed successfully, `false` ifthe entry does not exist or if this feature is not supported + func removeEntry(_ entry: ChartDataEntry) -> Bool + + /// Removes the Entry object at the given index in the values array from the DataSet. + /// + /// *optional feature, can return `false` ifnot implemented* + /// + /// - Parameters: + /// - index: the index of the entry to remove + /// - Returns: `true` if the entry was removed successfully, `false` ifthe entry does not exist or if this feature is not supported + func removeEntry(index: Int) -> Bool + + /// Removes the Entry object closest to the given x-value from the DataSet. + /// + /// *optional feature, can return `false` ifnot implemented* + /// + /// - Parameters: + /// - x: the x-value to remove + /// - Returns: `true` if the entry was removed successfully, `false` ifthe entry does not exist or if this feature is not supported + func removeEntry(x: Double) -> Bool + + /// Removes the first Entry (at index 0) of this DataSet from the entries array. + /// + /// *optional feature, can return `false` ifnot implemented* + /// + /// - Returns: `true` if the entry was removed successfully, `false` ifthe entry does not exist or if this feature is not supported + func removeFirst() -> Bool + + /// Removes the last Entry (at index 0) of this DataSet from the entries array. + /// + /// *optional feature, can return `false` ifnot implemented* + /// + /// - Returns: `true` if the entry was removed successfully, `false` ifthe entry does not exist or if this feature is not supported + func removeLast() -> Bool + + /// Checks if this DataSet contains the specified Entry. + /// + /// - Returns: `true` if contains the entry, `false` ifnot. + func contains(_ e: ChartDataEntry) -> Bool + + /// Removes all values from this DataSet and does all necessary recalculations. + /// + /// *optional feature, could throw if not implemented* + func clear() + + // MARK: - Styling functions and accessors + + /// The label string that describes the DataSet. + var label: String? { get } + + /// The axis this DataSet should be plotted against. + var axisDependency: YAxis.AxisDependency { get } + + /// List representing all colors that are used for drawing the actual values for this DataSet + var valueColors: [NSUIColor] { get } + + /// All the colors that are used for this DataSet. + /// Colors are reused as soon as the number of Entries the DataSet represents is higher than the size of the colors array. + var colors: [NSUIColor] { get } + + /// - Returns: The color at the given index of the DataSet's color array. + /// This prevents out-of-bounds by performing a modulus on the color index, so colours will repeat themselves. + func color(atIndex: Int) -> NSUIColor + + func resetColors() + + func addColor(_ color: NSUIColor) + + func setColor(_ color: NSUIColor) + + /// if true, value highlighting is enabled + var highlightEnabled: Bool { get set } + + /// `true` if value highlighting is enabled for this dataset + var isHighlightEnabled: Bool { get } + + /// Custom formatter that is used instead of the auto-formatter if set + var valueFormatter: IValueFormatter? { get set } + + /// `true` if the valueFormatter object of this DataSet is null. + var needsFormatter: Bool { get } + + /// Sets/get a single color for value text. + /// Setting the color clears the colors array and adds a single color. + /// Getting will return the first color in the array. + var valueTextColor: NSUIColor { get set } + + /// - Returns: The color at the specified index that is used for drawing the values inside the chart. Uses modulus internally. + func valueTextColorAt(_ index: Int) -> NSUIColor + + /// the font for the value-text labels + var valueFont: NSUIFont { get set } + + /// The form to draw for this dataset in the legend. + /// + /// Return `.Default` to use the default legend form. + var form: Legend.Form { get } + + /// The form size to draw for this dataset in the legend. + /// + /// Return `NaN` to use the default legend form size. + var formSize: CGFloat { get } + + /// The line width for drawing the form of this dataset in the legend + /// + /// Return `NaN` to use the default legend form line width. + var formLineWidth: CGFloat { get } + + /// Line dash configuration for legend shapes that consist of lines. + /// + /// This is how much (in pixels) into the dash pattern are we starting from. + var formLineDashPhase: CGFloat { get } + + /// Line dash configuration for legend shapes that consist of lines. + /// + /// This is the actual dash pattern. + /// I.e. [2, 3] will paint [-- -- ] + /// [1, 3, 4, 2] will paint [- ---- - ---- ] + var formLineDashLengths: [CGFloat]? { get } + + /// Set this to true to draw y-values on the chart. + /// + /// - Note: For bar and line charts: if `maxVisibleCount` is reached, no values will be drawn even if this is enabled. + var drawValuesEnabled: Bool { get set } + + /// `true` if y-value drawing is enabled, `false` ifnot + var isDrawValuesEnabled: Bool { get } + + /// Set this to true to draw y-icons on the chart + /// + /// - Note: For bar and line charts: if `maxVisibleCount` is reached, no icons will be drawn even if this is enabled. + var drawIconsEnabled: Bool { get set } + + /// Returns true if y-icon drawing is enabled, false if not + var isDrawIconsEnabled: Bool { get } + + /// Offset of icons drawn on the chart. + /// + /// For all charts except Pie and Radar it will be ordinary (x offset, y offset). + /// + /// For Pie and Radar chart it will be (y offset, distance from center offset); so if you want icon to be rendered under value, you should increase X component of CGPoint, and if you want icon to be rendered closet to center, you should decrease height component of CGPoint. + var iconsOffset: CGPoint { get set } + + /// Set the visibility of this DataSet. If not visible, the DataSet will not be drawn to the chart upon refreshing it. + var visible: Bool { get set } + + /// `true` if this DataSet is visible inside the chart, or `false` ifit is currently hidden. + var isVisible: Bool { get } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Interfaces/ILineChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/ILineChartDataSet.swift new file mode 100644 index 000000000..071f26953 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/ILineChartDataSet.swift @@ -0,0 +1,80 @@ +// +// ILineChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + + +@objc +public protocol ILineChartDataSet: ILineRadarChartDataSet +{ + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// The drawing mode for this line dataset + /// + /// **default**: Linear + var mode: LineChartDataSet.Mode { get set } + + /// Intensity for cubic lines (min = 0.05, max = 1) + /// + /// **default**: 0.2 + var cubicIntensity: CGFloat { get set } + + /// The radius of the drawn circles. + var circleRadius: CGFloat { get set } + + /// The hole radius of the drawn circles. + var circleHoleRadius: CGFloat { get set } + + var circleColors: [NSUIColor] { get set } + + /// - Returns: The color at the given index of the DataSet's circle-color array. + /// Performs a IndexOutOfBounds check by modulus. + func getCircleColor(atIndex: Int) -> NSUIColor? + + /// Sets the one and ONLY color that should be used for this DataSet. + /// Internally, this recreates the colors array and adds the specified color. + func setCircleColor(_ color: NSUIColor) + + /// Resets the circle-colors array and creates a new one + func resetCircleColors(_ index: Int) + + /// If true, drawing circles is enabled + var drawCirclesEnabled: Bool { get set } + + /// `true` if drawing circles for this DataSet is enabled, `false` ifnot + var isDrawCirclesEnabled: Bool { get } + + /// The color of the inner circle (the circle-hole). + var circleHoleColor: NSUIColor? { get set } + + /// `true` if drawing circles for this DataSet is enabled, `false` ifnot + var drawCircleHoleEnabled: Bool { get set } + + /// `true` if drawing the circle-holes is enabled, `false` ifnot. + var isDrawCircleHoleEnabled: Bool { get } + + /// This is how much (in pixels) into the dash pattern are we starting from. + var lineDashPhase: CGFloat { get } + + /// This is the actual dash pattern. + /// I.e. [2, 3] will paint [-- -- ] + /// [1, 3, 4, 2] will paint [- ---- - ---- ] + var lineDashLengths: [CGFloat]? { get set } + + /// Line cap type, default is CGLineCap.Butt + var lineCapType: CGLineCap { get set } + + /// Sets a custom IFillFormatter to the chart that handles the position of the filled-line for each DataSet. Set this to null to use the default logic. + var fillFormatter: IFillFormatter? { get set } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Interfaces/ILineRadarChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/ILineRadarChartDataSet.swift new file mode 100644 index 000000000..d9f8980b4 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/ILineRadarChartDataSet.swift @@ -0,0 +1,45 @@ +// +// ILineRadarChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol ILineRadarChartDataSet: ILineScatterCandleRadarChartDataSet +{ + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// The color that is used for filling the line surface area. + var fillColor: NSUIColor { get set } + + /// - Returns: The object that is used for filling the area below the line. + /// **default**: nil + var fill: Fill? { get set } + + /// The alpha value that is used for filling the line surface. + /// **default**: 0.33 + var fillAlpha: CGFloat { get set } + + /// line width of the chart (min = 0.0, max = 10) + /// + /// **default**: 1 + var lineWidth: CGFloat { get set } + + /// Set to `true` if the DataSet should be drawn filled (surface), and not just as a line. + /// Disabling this will give great performance boost. + /// Please note that this method uses the path clipping for drawing the filled area (with images, gradients and layers). + var drawFilledEnabled: Bool { get set } + + /// `true` if filled drawing is enabled, `false` if not + var isDrawFilledEnabled: Bool { get } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Interfaces/ILineScatterCandleRadarChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/ILineScatterCandleRadarChartDataSet.swift new file mode 100644 index 000000000..27c2bca94 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/ILineScatterCandleRadarChartDataSet.swift @@ -0,0 +1,36 @@ +// +// ILineScatterCandleRadarChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +@objc +public protocol ILineScatterCandleRadarChartDataSet: IBarLineScatterCandleBubbleChartDataSet +{ + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// Enables / disables the horizontal highlight-indicator. If disabled, the indicator is not drawn. + var drawHorizontalHighlightIndicatorEnabled: Bool { get set } + + /// Enables / disables the vertical highlight-indicator. If disabled, the indicator is not drawn. + var drawVerticalHighlightIndicatorEnabled: Bool { get set } + + /// `true` if horizontal highlight indicator lines are enabled (drawn) + var isHorizontalHighlightIndicatorEnabled: Bool { get } + + /// `true` if vertical highlight indicator lines are enabled (drawn) + var isVerticalHighlightIndicatorEnabled: Bool { get } + + /// Enables / disables both vertical and horizontal highlight-indicators. + /// :param: enabled + func setDrawHighlightIndicators(_ enabled: Bool) +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IPieChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IPieChartDataSet.swift new file mode 100644 index 000000000..433c08f26 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IPieChartDataSet.swift @@ -0,0 +1,64 @@ +// +// IPieChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol IPieChartDataSet: IChartDataSet +{ + // MARK: - Styling functions and accessors + + /// the space in pixels between the pie-slices + /// **default**: 0 + /// **maximum**: 20 + var sliceSpace: CGFloat { get set } + + /// When enabled, slice spacing will be 0.0 when the smallest value is going to be smaller than the slice spacing itself. + var automaticallyDisableSliceSpacing: Bool { get set } + + /// indicates the selection distance of a pie slice + var selectionShift: CGFloat { get set } + + var xValuePosition: PieChartDataSet.ValuePosition { get set } + var yValuePosition: PieChartDataSet.ValuePosition { get set } + + /// When valuePosition is OutsideSlice, indicates line color + var valueLineColor: NSUIColor? { get set } + + /// When valuePosition is OutsideSlice and enabled, line will have the same color as the slice + var useValueColorForLine: Bool { get set } + + /// When valuePosition is OutsideSlice, indicates line width + var valueLineWidth: CGFloat { get set } + + /// When valuePosition is OutsideSlice, indicates offset as percentage out of the slice size + var valueLinePart1OffsetPercentage: CGFloat { get set } + + /// When valuePosition is OutsideSlice, indicates length of first half of the line + var valueLinePart1Length: CGFloat { get set } + + /// When valuePosition is OutsideSlice, indicates length of second half of the line + var valueLinePart2Length: CGFloat { get set } + + /// When valuePosition is OutsideSlice, this allows variable line length + var valueLineVariableLength: Bool { get set } + + /// the font for the slice-text labels + var entryLabelFont: NSUIFont? { get set } + + /// the color for the slice-text labels + var entryLabelColor: NSUIColor? { get set } + + /// get/sets the color for the highlighted sector + var highlightColor: NSUIColor? { get set } + +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IRadarChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IRadarChartDataSet.swift new file mode 100644 index 000000000..2e37b4ffd --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IRadarChartDataSet.swift @@ -0,0 +1,40 @@ +// +// IRadarChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol IRadarChartDataSet: ILineRadarChartDataSet +{ + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// flag indicating whether highlight circle should be drawn or not + var drawHighlightCircleEnabled: Bool { get set } + + var isDrawHighlightCircleEnabled: Bool { get } + + var highlightCircleFillColor: NSUIColor? { get set } + + /// The stroke color for highlight circle. + /// If `nil`, the color of the dataset is taken. + var highlightCircleStrokeColor: NSUIColor? { get set } + + var highlightCircleStrokeAlpha: CGFloat { get set } + + var highlightCircleInnerRadius: CGFloat { get set } + + var highlightCircleOuterRadius: CGFloat { get set } + + var highlightCircleStrokeWidth: CGFloat { get set } +} diff --git a/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IScatterChartDataSet.swift b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IScatterChartDataSet.swift new file mode 100644 index 000000000..056514672 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Data/Interfaces/IScatterChartDataSet.swift @@ -0,0 +1,36 @@ +// +// IScatterChartDataSet.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol IScatterChartDataSet: ILineScatterCandleRadarChartDataSet +{ + // MARK: - Data functions and accessors + + // MARK: - Styling functions and accessors + + /// The size the scatter shape will have + var scatterShapeSize: CGFloat { get } + + /// - Returns: The radius of the hole in the shape (applies to Square, Circle and Triangle) + /// Set this to <= 0 to remove holes. + /// **default**: 0.0 + var scatterShapeHoleRadius: CGFloat { get } + + /// - Returns: Color for the hole in the shape. Setting to `nil` will behave as transparent. + /// **default**: nil + var scatterShapeHoleColor: NSUIColor? { get } + + /// The IShapeRenderer responsible for rendering this DataSet. + var shapeRenderer: IShapeRenderer? { get } +} diff --git a/dydx/Pods/Charts/Source/Charts/Filters/DataApproximator+N.swift b/dydx/Pods/Charts/Source/Charts/Filters/DataApproximator+N.swift new file mode 100644 index 000000000..b19495500 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Filters/DataApproximator+N.swift @@ -0,0 +1,153 @@ +// +// DataApproximator+N.swift +// Charts +// +// Created by M Ivaniushchenko on 9/6/17. +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +extension CGPoint { + fileprivate func distanceToLine(from linePoint1: CGPoint, to linePoint2: CGPoint) -> CGFloat { + let dx = linePoint2.x - linePoint1.x + let dy = linePoint2.y - linePoint1.y + + let dividend = abs(dy * self.x - dx * self.y - linePoint1.x * linePoint2.y + linePoint2.x * linePoint1.y) + let divisor = sqrt(dx * dx + dy * dy) + + return dividend / divisor + } +} + +private struct LineAlt { + let start: Int + let end: Int + + var distance: CGFloat = 0 + var index: Int = 0 + + init(start: Int, end: Int, points: [CGPoint]) { + self.start = start + self.end = end + + let startPoint = points[start] + let endPoint = points[end] + + guard (end > start + 1) else { + return + } + + for i in start + 1 ..< end { + let currentPoint = points[i] + + let distance = currentPoint.distanceToLine(from: startPoint, to: endPoint) + + if distance > self.distance { + self.index = i + self.distance = distance + } + } + } +} + +extension LineAlt: Comparable { + static func ==(lhs: LineAlt, rhs: LineAlt) -> Bool { + return (lhs.start == rhs.start) && (lhs.end == rhs.end) && (lhs.index == rhs.index) + } + + static func <(lhs: LineAlt, rhs: LineAlt) -> Bool { + return lhs.distance < rhs.distance + } +} + + +extension DataApproximator { + /// uses the douglas peuker algorithm to reduce the given arraylist of entries to given number of points + /// More algorithm details here - http://psimpl.sourceforge.net/douglas-peucker.html + @objc open class func reduceWithDouglasPeukerN(_ points: [CGPoint], resultCount: Int) -> [CGPoint] + { + // if a shape has 2 or less points it cannot be reduced + if resultCount <= 2 || resultCount >= points.count + { + return points + } + var keep = [Bool](repeating: false, count: points.count) + + // first and last always stay + keep[0] = true + keep[points.count - 1] = true + var currentStoredPoints = 2 + + var queue = [LineAlt]() + let line = LineAlt(start: 0, end: points.count - 1, points: points) + queue.append(line) + + repeat { + let line = queue.popLast()! + + // store the key + keep[line.index] = true + + // check point count tolerance + currentStoredPoints += 1 + + if (currentStoredPoints == resultCount) { + break; + } + + // split the polyline at the key and recurse + let left = LineAlt(start: line.start, end: line.index, points: points) + if (left.index > 0) { + self.insertLine(left, into: &queue) + } + + let right = LineAlt(start: line.index, end: line.end, points: points) + if (right.index > 0) { + self.insertLine(right, into: &queue) + } + + } while !queue.isEmpty + + // create a new array with series, only take the kept ones + let reducedEntries = points.enumerated().compactMap { (index: Int, point: CGPoint) -> CGPoint? in + return keep[index] ? point : nil + } + + return reducedEntries + } + + // Keeps array sorted + private static func insertLine(_ line: LineAlt, into array: inout [LineAlt]) { + let insertionIndex = self.insertionIndex(for: line, into: &array) + array.insert(line, at: insertionIndex) + } + + private static func insertionIndex(for line: LineAlt, into array: inout [LineAlt]) -> Int { + var indices = array.indices + + while !indices.isEmpty { + let midIndex = indices.lowerBound.advanced(by: indices.count / 2) + let midLine = array[midIndex] + + if midLine == line { + return midIndex + } + else if (line < midLine) { + // perform search in left half + indices = indices.lowerBound.. [CGPoint] + { + // if a shape has 2 or less points it cannot be reduced + if tolerance <= 0 || points.count < 3 + { + return points + } + + var keep = [Bool](repeating: false, count: points.count) + + // first and last always stay + keep[0] = true + keep[points.count - 1] = true + + // first and last entry are entry point to recursion + reduceWithDouglasPeuker(points: points, + tolerance: tolerance, + start: 0, + end: points.count - 1, + keep: &keep) + + // create a new array with series, only take the kept ones + return zip(keep, points).compactMap { $0 ? nil : $1 } + } + + /// apply the Douglas-Peucker-Reduction to an array of `CGPoint`s with a given tolerance + /// + /// - Parameters: + /// - points: + /// - tolerance: + /// - start: + /// - end: + open class func reduceWithDouglasPeuker( + points: [CGPoint], + tolerance: CGFloat, + start: Int, + end: Int, + keep: inout [Bool]) + { + if end <= start + 1 + { + // recursion finished + return + } + + var greatestIndex = Int(0) + var greatestDistance = CGFloat(0.0) + + let line = Line(pt1: points[start], pt2: points[end]) + + for i in start + 1 ..< end + { + let distance = line.distance(toPoint: points[i]) + + if distance > greatestDistance + { + greatestDistance = distance + greatestIndex = i + } + } + + if greatestDistance > tolerance + { + // keep max dist point + keep[greatestIndex] = true + + // recursive call + reduceWithDouglasPeuker(points: points, tolerance: tolerance, start: start, end: greatestIndex, keep: &keep) + reduceWithDouglasPeuker(points: points, tolerance: tolerance, start: greatestIndex, end: end, keep: &keep) + } // else don't keep the point... + } + + private class Line + { + var sxey: CGFloat + var exsy: CGFloat + + var dx: CGFloat + var dy: CGFloat + + var length: CGFloat + + init(pt1: CGPoint, pt2: CGPoint) + { + dx = pt1.x - pt2.x + dy = pt1.y - pt2.y + sxey = pt1.x * pt2.y + exsy = pt2.x * pt1.y + length = sqrt(dx * dx + dy * dy) + } + + func distance(toPoint pt: CGPoint) -> CGFloat + { + return abs(dy * pt.x - dx * pt.y + sxey - exsy) / length + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Formatters/DefaultAxisValueFormatter.swift b/dydx/Pods/Charts/Source/Charts/Formatters/DefaultAxisValueFormatter.swift new file mode 100644 index 000000000..85193d0c4 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Formatters/DefaultAxisValueFormatter.swift @@ -0,0 +1,100 @@ +// +// DefaultAxisValueFormatter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +@objc(ChartDefaultAxisValueFormatter) +open class DefaultAxisValueFormatter: NSObject, IAxisValueFormatter +{ + public typealias Block = ( + _ value: Double, + _ axis: AxisBase?) -> String + + @objc open var block: Block? + + @objc open var hasAutoDecimals: Bool = false + + private var _formatter: NumberFormatter? + @objc open var formatter: NumberFormatter? + { + get { return _formatter } + set + { + hasAutoDecimals = false + _formatter = newValue + } + } + + // TODO: Documentation. Especially the nil case + private var _decimals: Int? + open var decimals: Int? + { + get { return _decimals } + set + { + _decimals = newValue + + if let digits = newValue + { + self.formatter?.minimumFractionDigits = digits + self.formatter?.maximumFractionDigits = digits + self.formatter?.usesGroupingSeparator = true + } + } + } + + public override init() + { + super.init() + + self.formatter = NumberFormatter() + hasAutoDecimals = true + } + + @objc public init(formatter: NumberFormatter) + { + super.init() + + self.formatter = formatter + } + + @objc public init(decimals: Int) + { + super.init() + + self.formatter = NumberFormatter() + self.formatter?.usesGroupingSeparator = true + self.decimals = decimals + hasAutoDecimals = true + } + + @objc public init(block: @escaping Block) + { + super.init() + + self.block = block + } + + @objc public static func with(block: @escaping Block) -> DefaultAxisValueFormatter? + { + return DefaultAxisValueFormatter(block: block) + } + + open func stringForValue(_ value: Double, + axis: AxisBase?) -> String + { + if let block = block { + return block(value, axis) + } else { + return formatter?.string(from: NSNumber(floatLiteral: value)) ?? "" + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Formatters/DefaultFillFormatter.swift b/dydx/Pods/Charts/Source/Charts/Formatters/DefaultFillFormatter.swift new file mode 100644 index 000000000..3afadf35f --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Formatters/DefaultFillFormatter.swift @@ -0,0 +1,58 @@ +// +// DefaultFillFormatter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +/// Default formatter that calculates the position of the filled line. +@objc(ChartDefaultFillFormatter) +open class DefaultFillFormatter: NSObject, IFillFormatter +{ + public typealias Block = ( + _ dataSet: ILineChartDataSet, + _ dataProvider: LineChartDataProvider) -> CGFloat + + @objc open var block: Block? + + public override init() { } + + @objc public init(block: @escaping Block) + { + self.block = block + } + + @objc public static func with(block: @escaping Block) -> DefaultFillFormatter? + { + return DefaultFillFormatter(block: block) + } + + open func getFillLinePosition( + dataSet: ILineChartDataSet, + dataProvider: LineChartDataProvider) -> CGFloat + { + guard block == nil else { return block!(dataSet, dataProvider) } + var fillMin: CGFloat = 0.0 + + if dataSet.yMax > 0.0 && dataSet.yMin < 0.0 + { + fillMin = 0.0 + } + else if let data = dataProvider.data + { + let max = data.yMax > 0.0 ? 0.0 : dataProvider.chartYMax + let min = data.yMin < 0.0 ? 0.0 : dataProvider.chartYMin + + fillMin = CGFloat(dataSet.yMin >= 0.0 ? min : max) + } + + return fillMin + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Formatters/DefaultValueFormatter.swift b/dydx/Pods/Charts/Source/Charts/Formatters/DefaultValueFormatter.swift new file mode 100644 index 000000000..b3fff70ac --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Formatters/DefaultValueFormatter.swift @@ -0,0 +1,103 @@ +// +// DefaultValueFormatter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +@objc(ChartDefaultValueFormatter) +open class DefaultValueFormatter: NSObject, IValueFormatter +{ + public typealias Block = ( + _ value: Double, + _ entry: ChartDataEntry, + _ dataSetIndex: Int, + _ viewPortHandler: ViewPortHandler?) -> String + + @objc open var block: Block? + + @objc open var hasAutoDecimals: Bool = false + + private var _formatter: NumberFormatter? + @objc open var formatter: NumberFormatter? + { + get { return _formatter } + set + { + hasAutoDecimals = false + _formatter = newValue + } + } + + private var _decimals: Int? + open var decimals: Int? + { + get { return _decimals } + set + { + _decimals = newValue + + if let digits = newValue + { + self.formatter?.minimumFractionDigits = digits + self.formatter?.maximumFractionDigits = digits + self.formatter?.usesGroupingSeparator = true + } + } + } + + public override init() + { + super.init() + + self.formatter = NumberFormatter() + hasAutoDecimals = true + } + + @objc public init(formatter: NumberFormatter) + { + super.init() + + self.formatter = formatter + } + + @objc public init(decimals: Int) + { + super.init() + + self.formatter = NumberFormatter() + self.formatter?.usesGroupingSeparator = true + self.decimals = decimals + hasAutoDecimals = true + } + + @objc public init(block: @escaping Block) + { + super.init() + + self.block = block + } + + @objc public static func with(block: @escaping Block) -> DefaultValueFormatter? + { + return DefaultValueFormatter(block: block) + } + + open func stringForValue(_ value: Double, + entry: ChartDataEntry, + dataSetIndex: Int, + viewPortHandler: ViewPortHandler?) -> String + { + if let block = block { + return block(value, entry, dataSetIndex, viewPortHandler) + } else { + return formatter?.string(from: NSNumber(floatLiteral: value)) ?? "" + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Formatters/IAxisValueFormatter.swift b/dydx/Pods/Charts/Source/Charts/Formatters/IAxisValueFormatter.swift new file mode 100644 index 000000000..302eee18f --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Formatters/IAxisValueFormatter.swift @@ -0,0 +1,30 @@ +// +// IAxisValueFormatter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +/// An interface for providing custom axis Strings. +@objc(IChartAxisValueFormatter) +public protocol IAxisValueFormatter: class +{ + + /// Called when a value from an axis is formatted before being drawn. + /// + /// For performance reasons, avoid excessive calculations and memory allocations inside this method. + /// + /// - Parameters: + /// - value: the value that is currently being drawn + /// - axis: the axis that the value belongs to + /// - Returns: The customized label that is drawn on the x-axis. + func stringForValue(_ value: Double, + axis: AxisBase?) -> String + +} diff --git a/dydx/Pods/Charts/Source/Charts/Formatters/IFillFormatter.swift b/dydx/Pods/Charts/Source/Charts/Formatters/IFillFormatter.swift new file mode 100644 index 000000000..7b684fd84 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Formatters/IFillFormatter.swift @@ -0,0 +1,21 @@ +// +// IFillFormatter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +/// Protocol for providing a custom logic to where the filling line of a LineDataSet should end. This of course only works if setFillEnabled(...) is set to true. +@objc(IChartFillFormatter) +public protocol IFillFormatter +{ + /// - Returns: The vertical (y-axis) position where the filled-line of the LineDataSet should end. + func getFillLinePosition(dataSet: ILineChartDataSet, dataProvider: LineChartDataProvider) -> CGFloat +} diff --git a/dydx/Pods/Charts/Source/Charts/Formatters/IValueFormatter.swift b/dydx/Pods/Charts/Source/Charts/Formatters/IValueFormatter.swift new file mode 100644 index 000000000..53ca7a40c --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Formatters/IValueFormatter.swift @@ -0,0 +1,36 @@ +// +// IValueFormatter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +/// Interface that allows custom formatting of all values inside the chart before they are drawn to the screen. +/// +/// Simply create your own formatting class and let it implement ValueFormatter. Then override the stringForValue() +/// method and return whatever you want. + +@objc(IChartValueFormatter) +public protocol IValueFormatter: class +{ + + /// Called when a value (from labels inside the chart) is formatted before being drawn. + /// + /// For performance reasons, avoid excessive calculations and memory allocations inside this method. + /// + /// - Parameters: + /// - value: The value to be formatted + /// - dataSetIndex: The index of the DataSet the entry in focus belongs to + /// - viewPortHandler: provides information about the current chart state (scale, translation, ...) + /// - Returns: The formatted label ready to be drawn + func stringForValue(_ value: Double, + entry: ChartDataEntry, + dataSetIndex: Int, + viewPortHandler: ViewPortHandler?) -> String +} diff --git a/dydx/Pods/Charts/Source/Charts/Formatters/IndexAxisValueFormatter.swift b/dydx/Pods/Charts/Source/Charts/Formatters/IndexAxisValueFormatter.swift new file mode 100644 index 000000000..ae86509d0 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Formatters/IndexAxisValueFormatter.swift @@ -0,0 +1,59 @@ +// +// IndexAxisValueFormatter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +/// This formatter is used for passing an array of x-axis labels, on whole x steps. +@objc(ChartIndexAxisValueFormatter) +open class IndexAxisValueFormatter: NSObject, IAxisValueFormatter +{ + private var _values: [String] = [String]() + private var _valueCount: Int = 0 + + @objc public var values: [String] + { + get + { + return _values + } + set + { + _values = newValue + _valueCount = _values.count + } + } + + public override init() + { + super.init() + + } + + @objc public init(values: [String]) + { + super.init() + + self.values = values + } + + @objc public static func with(values: [String]) -> IndexAxisValueFormatter? + { + return IndexAxisValueFormatter(values: values) + } + + open func stringForValue(_ value: Double, + axis: AxisBase?) -> String + { + let index = Int(value.rounded()) + guard values.indices.contains(index), index == Int(value) else { return "" } + return _values[index] + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Highlight/BarHighlighter.swift b/dydx/Pods/Charts/Source/Charts/Highlight/BarHighlighter.swift new file mode 100644 index 000000000..da9e6da73 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Highlight/BarHighlighter.swift @@ -0,0 +1,108 @@ +// +// BarHighlighter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(BarChartHighlighter) +open class BarHighlighter: ChartHighlighter +{ + open override func getHighlight(x: CGFloat, y: CGFloat) -> Highlight? + { + guard + let barData = (self.chart as? BarChartDataProvider)?.barData, + let high = super.getHighlight(x: x, y: y) + else { return nil } + + let pos = getValsForTouch(x: x, y: y) + + if let set = barData.getDataSetByIndex(high.dataSetIndex) as? IBarChartDataSet, + set.isStacked + { + return getStackedHighlight(high: high, + set: set, + xValue: Double(pos.x), + yValue: Double(pos.y)) + } + else + { + return high + } + } + + internal override func getDistance(x1: CGFloat, y1: CGFloat, x2: CGFloat, y2: CGFloat) -> CGFloat + { + return abs(x1 - x2) + } + + internal override var data: ChartData? + { + return (chart as? BarChartDataProvider)?.barData + } + + /// This method creates the Highlight object that also indicates which value of a stacked BarEntry has been selected. + /// + /// - Parameters: + /// - high: the Highlight to work with looking for stacked values + /// - set: + /// - xIndex: + /// - yValue: + /// - Returns: + @objc open func getStackedHighlight(high: Highlight, + set: IBarChartDataSet, + xValue: Double, + yValue: Double) -> Highlight? + { + guard + let chart = self.chart as? BarLineScatterCandleBubbleChartDataProvider, + let entry = set.entryForXValue(xValue, closestToY: yValue) as? BarChartDataEntry + else { return nil } + + // Not stacked + if entry.yValues == nil + { + return high + } + + guard + let ranges = entry.ranges, + ranges.count > 0 + else { return nil } + + let stackIndex = getClosestStackIndex(ranges: ranges, value: yValue) + let pixel = chart + .getTransformer(forAxis: set.axisDependency) + .pixelForValues(x: high.x, y: ranges[stackIndex].to) + + return Highlight(x: entry.x, + y: entry.y, + xPx: pixel.x, + yPx: pixel.y, + dataSetIndex: high.dataSetIndex, + stackIndex: stackIndex, + axis: high.axis) + } + + /// - Parameters: + /// - entry: + /// - value: + /// - Returns: The index of the closest value inside the values array / ranges (stacked barchart) to the value given as a parameter. + @objc open func getClosestStackIndex(ranges: [Range]?, value: Double) -> Int + { + guard let ranges = ranges else { return 0 } + if let stackIndex = ranges.firstIndex(where: { $0.contains(value) }) { + return stackIndex + } else { + let length = max(ranges.count - 1, 0) + return (value > ranges[length].to) ? length : 0 + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Highlight/ChartHighlighter.swift b/dydx/Pods/Charts/Source/Charts/Highlight/ChartHighlighter.swift new file mode 100644 index 000000000..417ba0d0a --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Highlight/ChartHighlighter.swift @@ -0,0 +1,180 @@ +// +// ChartHighlighter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class ChartHighlighter : NSObject, IHighlighter +{ + /// instance of the data-provider + @objc open weak var chart: ChartDataProvider? + + @objc public init(chart: ChartDataProvider) + { + self.chart = chart + } + + open func getHighlight(x: CGFloat, y: CGFloat) -> Highlight? + { + let xVal = Double(getValsForTouch(x: x, y: y).x) + return getHighlight(xValue: xVal, x: x, y: y) + } + + /// - Parameters: + /// - x: + /// - Returns: The corresponding x-pos for a given touch-position in pixels. + @objc open func getValsForTouch(x: CGFloat, y: CGFloat) -> CGPoint + { + guard let chart = self.chart as? BarLineScatterCandleBubbleChartDataProvider else { return .zero } + + // take any transformer to determine the values + return chart.getTransformer(forAxis: .left).valueForTouchPoint(x: x, y: y) + } + + /// - Parameters: + /// - xValue: + /// - x: + /// - y: + /// - Returns: The corresponding ChartHighlight for a given x-value and xy-touch position in pixels. + @objc open func getHighlight(xValue xVal: Double, x: CGFloat, y: CGFloat) -> Highlight? + { + guard let chart = chart else { return nil } + + let closestValues = getHighlights(xValue: xVal, x: x, y: y) + guard !closestValues.isEmpty else { return nil } + + let leftAxisMinDist = getMinimumDistance(closestValues: closestValues, y: y, axis: .left) + let rightAxisMinDist = getMinimumDistance(closestValues: closestValues, y: y, axis: .right) + + let axis: YAxis.AxisDependency = leftAxisMinDist < rightAxisMinDist ? .left : .right + + let detail = closestSelectionDetailByPixel(closestValues: closestValues, x: x, y: y, axis: axis, minSelectionDistance: chart.maxHighlightDistance) + + return detail + } + + /// - Parameters: + /// - xValue: the transformed x-value of the x-touch position + /// - x: touch position + /// - y: touch position + /// - Returns: A list of Highlight objects representing the entries closest to the given xVal. + /// The returned list contains two objects per DataSet (closest rounding up, closest rounding down). + @objc open func getHighlights(xValue: Double, x: CGFloat, y: CGFloat) -> [Highlight] + { + var vals = [Highlight]() + + guard let data = self.data else { return vals } + + for i in 0 ..< data.dataSetCount + { + guard + let dataSet = data.getDataSetByIndex(i), + dataSet.isHighlightEnabled // don't include datasets that cannot be highlighted + else { continue } + + + // extract all y-values from all DataSets at the given x-value. + // some datasets (i.e bubble charts) make sense to have multiple values for an x-value. We'll have to find a way to handle that later on. It's more complicated now when x-indices are floating point. + vals.append(contentsOf: buildHighlights(dataSet: dataSet, dataSetIndex: i, xValue: xValue, rounding: .closest)) + } + + return vals + } + + /// - Returns: An array of `Highlight` objects corresponding to the selected xValue and dataSetIndex. + internal func buildHighlights( + dataSet set: IChartDataSet, + dataSetIndex: Int, + xValue: Double, + rounding: ChartDataSetRounding) -> [Highlight] + { + guard let chart = self.chart as? BarLineScatterCandleBubbleChartDataProvider else { return [] } + + var entries = set.entriesForXValue(xValue) + if entries.count == 0, let closest = set.entryForXValue(xValue, closestToY: .nan, rounding: rounding) + { + // Try to find closest x-value and take all entries for that x-value + entries = set.entriesForXValue(closest.x) + } + + return entries.map { e in + let px = chart.getTransformer(forAxis: set.axisDependency) + .pixelForValues(x: e.x, y: e.y) + + return Highlight(x: e.x, y: e.y, xPx: px.x, yPx: px.y, dataSetIndex: dataSetIndex, axis: set.axisDependency) + } + } + + // - MARK: - Utilities + + /// - Returns: The `ChartHighlight` of the closest value on the x-y cartesian axes + internal func closestSelectionDetailByPixel( + closestValues: [Highlight], + x: CGFloat, + y: CGFloat, + axis: YAxis.AxisDependency?, + minSelectionDistance: CGFloat) -> Highlight? + { + var distance = minSelectionDistance + var closest: Highlight? + + for high in closestValues + { + if axis == nil || high.axis == axis + { + let cDistance = getDistance(x1: x, y1: y, x2: high.xPx, y2: high.yPx) + + if cDistance < distance + { + closest = high + distance = cDistance + } + } + } + + return closest + } + + /// - Returns: The minimum distance from a touch-y-value (in pixels) to the closest y-value (in pixels) that is displayed in the chart. + internal func getMinimumDistance( + closestValues: [Highlight], + y: CGFloat, + axis: YAxis.AxisDependency + ) -> CGFloat { + var distance = CGFloat.greatestFiniteMagnitude + + for high in closestValues where high.axis == axis + { + let tempDistance = abs(getHighlightPos(high: high) - y) + if tempDistance < distance + { + distance = tempDistance + } + } + + return distance + } + + internal func getHighlightPos(high: Highlight) -> CGFloat + { + return high.yPx + } + + internal func getDistance(x1: CGFloat, y1: CGFloat, x2: CGFloat, y2: CGFloat) -> CGFloat + { + return hypot(x1 - x2, y1 - y2) + } + + internal var data: ChartData? + { + return chart?.data + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Highlight/CombinedHighlighter.swift b/dydx/Pods/Charts/Source/Charts/Highlight/CombinedHighlighter.swift new file mode 100644 index 000000000..7053df09d --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Highlight/CombinedHighlighter.swift @@ -0,0 +1,70 @@ +// +// CombinedHighlighter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(CombinedChartHighlighter) +open class CombinedHighlighter: ChartHighlighter +{ + /// bar highlighter for supporting stacked highlighting + private var barHighlighter: BarHighlighter? + + @objc public init(chart: CombinedChartDataProvider, barDataProvider: BarChartDataProvider) + { + super.init(chart: chart) + + // if there is BarData, create a BarHighlighter + self.barHighlighter = barDataProvider.barData == nil ? nil : BarHighlighter(chart: barDataProvider) + } + + open override func getHighlights(xValue: Double, x: CGFloat, y: CGFloat) -> [Highlight] + { + var vals = [Highlight]() + + guard + let chart = self.chart as? CombinedChartDataProvider, + let dataObjects = chart.combinedData?.allData + else { return vals } + + for i in 0..= 0 } + + /// Sets the x- and y-position (pixels) where this highlight was last drawn. + @objc open func setDraw(x: CGFloat, y: CGFloat) + { + self.drawX = x + self.drawY = y + } + + /// Sets the x- and y-position (pixels) where this highlight was last drawn. + @objc open func setDraw(pt: CGPoint) + { + self.drawX = pt.x + self.drawY = pt.y + } + + // MARK: NSObject + + open override var description: String + { + return "Highlight, x: \(_x), y: \(_y), dataIndex (combined charts): \(dataIndex), dataSetIndex: \(_dataSetIndex), stackIndex (only stacked barentry): \(_stackIndex)" + } +} + + +// MARK: Equatable +extension Highlight /*: Equatable*/ { + open override func isEqual(_ object: Any?) -> Bool { + guard let object = object as? Highlight else { return false } + + if self === object + { + return true + } + + return _x == object._x + && _y == object._y + && dataIndex == object.dataIndex + && _dataSetIndex == object._dataSetIndex + && _stackIndex == object._stackIndex + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Highlight/HorizontalBarHighlighter.swift b/dydx/Pods/Charts/Source/Charts/Highlight/HorizontalBarHighlighter.swift new file mode 100644 index 000000000..103d53f98 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Highlight/HorizontalBarHighlighter.swift @@ -0,0 +1,63 @@ +// +// HorizontalBarHighlighter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(HorizontalBarChartHighlighter) +open class HorizontalBarHighlighter: BarHighlighter +{ + open override func getHighlight(x: CGFloat, y: CGFloat) -> Highlight? + { + guard let barData = self.chart?.data as? BarChartData else { return nil } + + let pos = getValsForTouch(x: y, y: x) + guard let high = getHighlight(xValue: Double(pos.y), x: y, y: x) else { return nil } + + if let set = barData.getDataSetByIndex(high.dataSetIndex) as? IBarChartDataSet, + set.isStacked + { + return getStackedHighlight(high: high, + set: set, + xValue: Double(pos.y), + yValue: Double(pos.x)) + } + + return high + } + + internal override func buildHighlights( + dataSet set: IChartDataSet, + dataSetIndex: Int, + xValue: Double, + rounding: ChartDataSetRounding) -> [Highlight] + { + guard let chart = self.chart as? BarLineScatterCandleBubbleChartDataProvider else { return [] } + + var entries = set.entriesForXValue(xValue) + if entries.count == 0, let closest = set.entryForXValue(xValue, closestToY: .nan, rounding: rounding) + { + // Try to find closest x-value and take all entries for that x-value + entries = set.entriesForXValue(closest.x) + } + + return entries.map { e in + let px = chart.getTransformer(forAxis: set.axisDependency) + .pixelForValues(x: e.y, y: e.x) + return Highlight(x: e.x, y: e.y, xPx: px.x, yPx: px.y, dataSetIndex: dataSetIndex, axis: set.axisDependency) + } + } + + internal override func getDistance(x1: CGFloat, y1: CGFloat, x2: CGFloat, y2: CGFloat) -> CGFloat + { + return abs(y1 - y2) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Highlight/IHighlighter.swift b/dydx/Pods/Charts/Source/Charts/Highlight/IHighlighter.swift new file mode 100644 index 000000000..21ae298c3 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Highlight/IHighlighter.swift @@ -0,0 +1,23 @@ +// +// IHighlighter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(IChartHighlighter) +public protocol IHighlighter: class +{ + /// - Parameters: + /// - x: + /// - y: + /// - Returns: A Highlight object corresponding to the given x- and y- touch positions in pixels. + func getHighlight(x: CGFloat, y: CGFloat) -> Highlight? +} diff --git a/dydx/Pods/Charts/Source/Charts/Highlight/PieHighlighter.swift b/dydx/Pods/Charts/Source/Charts/Highlight/PieHighlighter.swift new file mode 100644 index 000000000..54bb3d7b8 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Highlight/PieHighlighter.swift @@ -0,0 +1,27 @@ +// +// PieHighlighter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(PieChartHighlighter) +open class PieHighlighter: PieRadarHighlighter +{ + open override func closestHighlight(index: Int, x: CGFloat, y: CGFloat) -> Highlight? + { + guard + let set = chart?.data?.dataSets[0], + let entry = set.entryForIndex(index) + else { return nil } + + return Highlight(x: Double(index), y: entry.y, xPx: x, yPx: y, dataSetIndex: 0, axis: set.axisDependency) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Highlight/PieRadarHighlighter.swift b/dydx/Pods/Charts/Source/Charts/Highlight/PieRadarHighlighter.swift new file mode 100644 index 000000000..c55988ab3 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Highlight/PieRadarHighlighter.swift @@ -0,0 +1,61 @@ +// +// PieRadarHighlighter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(PieRadarChartHighlighter) +open class PieRadarHighlighter: ChartHighlighter +{ + open override func getHighlight(x: CGFloat, y: CGFloat) -> Highlight? + { + guard let chart = self.chart as? PieRadarChartViewBase else { return nil } + + let touchDistanceToCenter = chart.distanceToCenter(x: x, y: y) + + // check if a slice was touched + guard touchDistanceToCenter <= chart.radius else + { + // if no slice was touched, highlight nothing + return nil + } + + var angle = chart.angleForPoint(x: x ,y: y) + + if chart is PieChartView + { + angle /= CGFloat(chart.chartAnimator.phaseY) + } + + let index = chart.indexForAngle(angle) + + // check if the index could be found + if index < 0 || index >= chart.data?.maxEntryCountSet?.entryCount ?? 0 + { + return nil + } + else + { + return closestHighlight(index: index, x: x, y: y) + } + + } + + /// - Parameters: + /// - index: + /// - x: + /// - y: + /// - Returns: The closest Highlight object of the given objects based on the touch position inside the chart. + @objc open func closestHighlight(index: Int, x: CGFloat, y: CGFloat) -> Highlight? + { + fatalError("closestHighlight(index, x, y) cannot be called on PieRadarChartHighlighter") + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Highlight/RadarHighlighter.swift b/dydx/Pods/Charts/Source/Charts/Highlight/RadarHighlighter.swift new file mode 100644 index 000000000..cfaf57aec --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Highlight/RadarHighlighter.swift @@ -0,0 +1,78 @@ +// +// RadarHighlighter.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(RadarChartHighlighter) +open class RadarHighlighter: PieRadarHighlighter +{ + open override func closestHighlight(index: Int, x: CGFloat, y: CGFloat) -> Highlight? + { + guard let chart = self.chart as? RadarChartView else { return nil } + + let highlights = getHighlights(forIndex: index) + + let distanceToCenter = Double(chart.distanceToCenter(x: x, y: y) / chart.factor) + + var closest: Highlight? + var distance = Double.greatestFiniteMagnitude + + for high in highlights + { + let cdistance = abs(high.y - distanceToCenter) + if cdistance < distance + { + closest = high + distance = cdistance + } + } + + return closest + } + + /// - Parameters: + /// - index: + /// - Returns: An array of Highlight objects for the given index. + /// The Highlight objects give information about the value at the selected index and DataSet it belongs to. + internal func getHighlights(forIndex index: Int) -> [Highlight] + { + var vals = [Highlight]() + + guard + let chart = self.chart as? RadarChartView, + let chartData = chart.data + else { return vals } + + let phaseX = chart.chartAnimator.phaseX + let phaseY = chart.chartAnimator.phaseY + let sliceangle = chart.sliceAngle + let factor = chart.factor + + for i in chartData.dataSets.indices + { + guard + let dataSet = chartData.getDataSetByIndex(i), + let entry = dataSet.entryForIndex(index) + else { continue } + + let y = (entry.y - chart.chartYMin) + + let p = chart.centerOffsets.moving(distance: CGFloat(y) * factor * CGFloat(phaseY), + atAngle: sliceangle * CGFloat(index) * CGFloat(phaseX) + chart.rotationAngle) + + let highlight = Highlight(x: Double(index), y: entry.y, xPx: p.x, yPx: p.y, dataSetIndex: i, axis: dataSet.axisDependency) + vals.append(highlight) + } + + return vals + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Highlight/Range.swift b/dydx/Pods/Charts/Source/Charts/Highlight/Range.swift new file mode 100644 index 000000000..4b7ead10f --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Highlight/Range.swift @@ -0,0 +1,52 @@ +// +// Range.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +@objc(ChartRange) +open class Range: NSObject +{ + @objc open var from: Double + @objc open var to: Double + + @objc public init(from: Double, to: Double) + { + self.from = from + self.to = to + + super.init() + } + + /// - Parameters: + /// - value: + /// - Returns: `true` if this range contains (if the value is in between) the given value, `false` ifnot. + @objc open func contains(_ value: Double) -> Bool + { + if value > from && value <= to + { + return true + } + else + { + return false + } + } + + @objc open func isLarger(_ value: Double) -> Bool + { + return value > to + } + + @objc open func isSmaller(_ value: Double) -> Bool + { + return value < from + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Interfaces/BarChartDataProvider.swift b/dydx/Pods/Charts/Source/Charts/Interfaces/BarChartDataProvider.swift new file mode 100644 index 000000000..e1d0a8d0b --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Interfaces/BarChartDataProvider.swift @@ -0,0 +1,23 @@ +// +// BarChartDataProvider.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol BarChartDataProvider: BarLineScatterCandleBubbleChartDataProvider +{ + var barData: BarChartData? { get } + + var isDrawBarShadowEnabled: Bool { get } + var isDrawValueAboveBarEnabled: Bool { get } + var isHighlightFullBarEnabled: Bool { get } +} \ No newline at end of file diff --git a/dydx/Pods/Charts/Source/Charts/Interfaces/BarLineScatterCandleBubbleChartDataProvider.swift b/dydx/Pods/Charts/Source/Charts/Interfaces/BarLineScatterCandleBubbleChartDataProvider.swift new file mode 100644 index 000000000..fd5c06529 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Interfaces/BarLineScatterCandleBubbleChartDataProvider.swift @@ -0,0 +1,23 @@ +// +// BarLineScatterCandleBubbleChartDataProvider.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol BarLineScatterCandleBubbleChartDataProvider: ChartDataProvider +{ + func getTransformer(forAxis: YAxis.AxisDependency) -> Transformer + func isInverted(axis: YAxis.AxisDependency) -> Bool + + var lowestVisibleX: Double { get } + var highestVisibleX: Double { get } +} diff --git a/dydx/Pods/Charts/Source/Charts/Interfaces/BubbleChartDataProvider.swift b/dydx/Pods/Charts/Source/Charts/Interfaces/BubbleChartDataProvider.swift new file mode 100644 index 000000000..d9fc9e81b --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Interfaces/BubbleChartDataProvider.swift @@ -0,0 +1,19 @@ +// +// BubbleChartDataProvider.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol BubbleChartDataProvider: BarLineScatterCandleBubbleChartDataProvider +{ + var bubbleData: BubbleChartData? { get } +} \ No newline at end of file diff --git a/dydx/Pods/Charts/Source/Charts/Interfaces/CandleChartDataProvider.swift b/dydx/Pods/Charts/Source/Charts/Interfaces/CandleChartDataProvider.swift new file mode 100644 index 000000000..6e729ff13 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Interfaces/CandleChartDataProvider.swift @@ -0,0 +1,19 @@ +// +// CandleChartDataProvider.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol CandleChartDataProvider: BarLineScatterCandleBubbleChartDataProvider +{ + var candleData: CandleChartData? { get } +} \ No newline at end of file diff --git a/dydx/Pods/Charts/Source/Charts/Interfaces/ChartDataProvider.swift b/dydx/Pods/Charts/Source/Charts/Interfaces/ChartDataProvider.swift new file mode 100644 index 000000000..531f04b75 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Interfaces/ChartDataProvider.swift @@ -0,0 +1,39 @@ +// +// ChartDataProvider.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol ChartDataProvider +{ + /// The minimum x-value of the chart, regardless of zoom or translation. + var chartXMin: Double { get } + + /// The maximum x-value of the chart, regardless of zoom or translation. + var chartXMax: Double { get } + + /// The minimum y-value of the chart, regardless of zoom or translation. + var chartYMin: Double { get } + + /// The maximum y-value of the chart, regardless of zoom or translation. + var chartYMax: Double { get } + + var maxHighlightDistance: CGFloat { get } + + var xRange: Double { get } + + var centerOffsets: CGPoint { get } + + var data: ChartData? { get } + + var maxVisibleCount: Int { get } +} diff --git a/dydx/Pods/Charts/Source/Charts/Interfaces/CombinedChartDataProvider.swift b/dydx/Pods/Charts/Source/Charts/Interfaces/CombinedChartDataProvider.swift new file mode 100644 index 000000000..e360eedd6 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Interfaces/CombinedChartDataProvider.swift @@ -0,0 +1,19 @@ +// +// CombinedChartDataProvider.swoft +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol CombinedChartDataProvider: LineChartDataProvider, BarChartDataProvider, BubbleChartDataProvider, CandleChartDataProvider, ScatterChartDataProvider +{ + var combinedData: CombinedChartData? { get } +} \ No newline at end of file diff --git a/dydx/Pods/Charts/Source/Charts/Interfaces/LineChartDataProvider.swift b/dydx/Pods/Charts/Source/Charts/Interfaces/LineChartDataProvider.swift new file mode 100644 index 000000000..e63548216 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Interfaces/LineChartDataProvider.swift @@ -0,0 +1,21 @@ +// +// LineChartDataProvider.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol LineChartDataProvider: BarLineScatterCandleBubbleChartDataProvider +{ + var lineData: LineChartData? { get } + + func getAxis(_ axis: YAxis.AxisDependency) -> YAxis +} diff --git a/dydx/Pods/Charts/Source/Charts/Interfaces/ScatterChartDataProvider.swift b/dydx/Pods/Charts/Source/Charts/Interfaces/ScatterChartDataProvider.swift new file mode 100644 index 000000000..050bde740 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Interfaces/ScatterChartDataProvider.swift @@ -0,0 +1,19 @@ +// +// ScatterChartDataProvider.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol ScatterChartDataProvider: BarLineScatterCandleBubbleChartDataProvider +{ + var scatterData: ScatterChartData? { get } +} \ No newline at end of file diff --git a/dydx/Pods/Charts/Source/Charts/Jobs/AnimatedMoveViewJob.swift b/dydx/Pods/Charts/Source/Charts/Jobs/AnimatedMoveViewJob.swift new file mode 100644 index 000000000..7a7582216 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Jobs/AnimatedMoveViewJob.swift @@ -0,0 +1,33 @@ +// +// AnimatedMoveViewJob.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class AnimatedMoveViewJob: AnimatedViewPortJob +{ + internal override func animationUpdate() + { + guard + let viewPortHandler = viewPortHandler, + let transformer = transformer, + let view = view + else { return } + + var pt = CGPoint( + x: xOrigin + (CGFloat(xValue) - xOrigin) * phase, + y: yOrigin + (CGFloat(yValue) - yOrigin) * phase + ) + + transformer.pointValueToPixel(&pt) + viewPortHandler.centerViewPort(pt: pt, chart: view) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Jobs/AnimatedViewPortJob.swift b/dydx/Pods/Charts/Source/Charts/Jobs/AnimatedViewPortJob.swift new file mode 100644 index 000000000..1bdfedf5b --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Jobs/AnimatedViewPortJob.swift @@ -0,0 +1,127 @@ +// +// AnimatedViewPortJob.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics +import QuartzCore + +open class AnimatedViewPortJob: ViewPortJob +{ + internal var phase: CGFloat = 1.0 + internal var xOrigin: CGFloat = 0.0 + internal var yOrigin: CGFloat = 0.0 + + private var _startTime: TimeInterval = 0.0 + private var _displayLink: NSUIDisplayLink! + private var _duration: TimeInterval = 0.0 + private var _endTime: TimeInterval = 0.0 + + private var _easing: ChartEasingFunctionBlock? + + @objc public init( + viewPortHandler: ViewPortHandler, + xValue: Double, + yValue: Double, + transformer: Transformer, + view: ChartViewBase, + xOrigin: CGFloat, + yOrigin: CGFloat, + duration: TimeInterval, + easing: ChartEasingFunctionBlock?) + { + super.init(viewPortHandler: viewPortHandler, + xValue: xValue, + yValue: yValue, + transformer: transformer, + view: view) + + self.xOrigin = xOrigin + self.yOrigin = yOrigin + self._duration = duration + self._easing = easing + } + + deinit + { + stop(finish: false) + } + + open override func doJob() + { + start() + } + + @objc open func start() + { + _startTime = CACurrentMediaTime() + _endTime = _startTime + _duration + _endTime = _endTime > _endTime ? _endTime : _endTime + + updateAnimationPhase(_startTime) + + _displayLink = NSUIDisplayLink(target: self, selector: #selector(animationLoop)) + _displayLink.add(to: .main, forMode: RunLoop.Mode.common) + } + + @objc open func stop(finish: Bool) + { + guard _displayLink != nil else { return } + + _displayLink.remove(from: .main, forMode: RunLoop.Mode.common) + _displayLink = nil + + if finish + { + if phase != 1.0 + { + phase = 1.0 + animationUpdate() + } + + animationEnd() + } + } + + private func updateAnimationPhase(_ currentTime: TimeInterval) + { + let elapsedTime = currentTime - _startTime + let duration = _duration + var elapsed = elapsedTime + + elapsed = min(elapsed, duration) + + phase = CGFloat(_easing?(elapsed, duration) ?? elapsed / duration) + } + + @objc private func animationLoop() + { + let currentTime: TimeInterval = CACurrentMediaTime() + + updateAnimationPhase(currentTime) + + animationUpdate() + + if currentTime >= _endTime + { + stop(finish: true) + } + } + + internal func animationUpdate() + { + // Override this + } + + internal func animationEnd() + { + // Override this + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Jobs/AnimatedZoomViewJob.swift b/dydx/Pods/Charts/Source/Charts/Jobs/AnimatedZoomViewJob.swift new file mode 100644 index 000000000..4e9d5fd98 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Jobs/AnimatedZoomViewJob.swift @@ -0,0 +1,96 @@ +// +// AnimatedZoomViewJob.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class AnimatedZoomViewJob: AnimatedViewPortJob +{ + internal var yAxis: YAxis? + internal var xAxisRange: Double = 0.0 + internal var scaleX: CGFloat = 0.0 + internal var scaleY: CGFloat = 0.0 + internal var zoomOriginX: CGFloat = 0.0 + internal var zoomOriginY: CGFloat = 0.0 + internal var zoomCenterX: CGFloat = 0.0 + internal var zoomCenterY: CGFloat = 0.0 + + @objc public init( + viewPortHandler: ViewPortHandler, + transformer: Transformer, + view: ChartViewBase, + yAxis: YAxis, + xAxisRange: Double, + scaleX: CGFloat, + scaleY: CGFloat, + xOrigin: CGFloat, + yOrigin: CGFloat, + zoomCenterX: CGFloat, + zoomCenterY: CGFloat, + zoomOriginX: CGFloat, + zoomOriginY: CGFloat, + duration: TimeInterval, + easing: ChartEasingFunctionBlock?) + { + super.init(viewPortHandler: viewPortHandler, + xValue: 0.0, + yValue: 0.0, + transformer: transformer, + view: view, + xOrigin: xOrigin, + yOrigin: yOrigin, + duration: duration, + easing: easing) + + self.yAxis = yAxis + self.xAxisRange = xAxisRange + self.scaleX = scaleX + self.scaleY = scaleY + self.zoomCenterX = zoomCenterX + self.zoomCenterY = zoomCenterY + self.zoomOriginX = zoomOriginX + self.zoomOriginY = zoomOriginY + } + + internal override func animationUpdate() + { + guard + let viewPortHandler = viewPortHandler, + let transformer = transformer, + let view = view + else { return } + + let scaleX = xOrigin + (self.scaleX - xOrigin) * phase + let scaleY = yOrigin + (self.scaleY - yOrigin) * phase + + var matrix = viewPortHandler.setZoom(scaleX: scaleX, scaleY: scaleY) + viewPortHandler.refresh(newMatrix: matrix, chart: view, invalidate: false) + + let valsInView = CGFloat(yAxis?.axisRange ?? 0.0) / viewPortHandler.scaleY + let xsInView = CGFloat(xAxisRange) / viewPortHandler.scaleX + + var pt = CGPoint( + x: zoomOriginX + ((zoomCenterX - xsInView / 2.0) - zoomOriginX) * phase, + y: zoomOriginY + ((zoomCenterY + valsInView / 2.0) - zoomOriginY) * phase + ) + + transformer.pointValueToPixel(&pt) + + matrix = viewPortHandler.translate(pt: pt) + viewPortHandler.refresh(newMatrix: matrix, chart: view, invalidate: true) + } + + internal override func animationEnd() + { + (view as? BarLineChartViewBase)?.calculateOffsets() + view?.setNeedsDisplay() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Jobs/MoveViewJob.swift b/dydx/Pods/Charts/Source/Charts/Jobs/MoveViewJob.swift new file mode 100644 index 000000000..0b6ca320e --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Jobs/MoveViewJob.swift @@ -0,0 +1,34 @@ +// +// MoveViewJob.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(MoveChartViewJob) +open class MoveViewJob: ViewPortJob +{ + open override func doJob() + { + guard + let viewPortHandler = viewPortHandler, + let transformer = transformer, + let view = view + else { return } + + var pt = CGPoint( + x: xValue, + y: yValue + ) + + transformer.pointValueToPixel(&pt) + viewPortHandler.centerViewPort(pt: pt, chart: view) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Jobs/ViewPortJob.swift b/dydx/Pods/Charts/Source/Charts/Jobs/ViewPortJob.swift new file mode 100644 index 000000000..c52562ed5 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Jobs/ViewPortJob.swift @@ -0,0 +1,46 @@ +// +// ViewPortJob.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +// This defines a viewport modification job, used for delaying or animating viewport changes +@objc(ChartViewPortJob) +open class ViewPortJob: NSObject +{ + internal var point: CGPoint = CGPoint() + internal weak var viewPortHandler: ViewPortHandler? + internal var xValue: Double = 0.0 + internal var yValue: Double = 0.0 + internal weak var transformer: Transformer? + internal weak var view: ChartViewBase? + + @objc public init( + viewPortHandler: ViewPortHandler, + xValue: Double, + yValue: Double, + transformer: Transformer, + view: ChartViewBase) + { + super.init() + + self.viewPortHandler = viewPortHandler + self.xValue = xValue + self.yValue = yValue + self.transformer = transformer + self.view = view + } + + @objc open func doJob() + { + fatalError("`doJob()` must be overridden by subclasses") + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Jobs/ZoomViewJob.swift b/dydx/Pods/Charts/Source/Charts/Jobs/ZoomViewJob.swift new file mode 100644 index 000000000..a6a79394d --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Jobs/ZoomViewJob.swift @@ -0,0 +1,71 @@ +// +// ZoomViewJob.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(ZoomChartViewJob) +open class ZoomViewJob: ViewPortJob +{ + internal var scaleX: CGFloat = 0.0 + internal var scaleY: CGFloat = 0.0 + internal var axisDependency: YAxis.AxisDependency = .left + + @objc public init( + viewPortHandler: ViewPortHandler, + scaleX: CGFloat, + scaleY: CGFloat, + xValue: Double, + yValue: Double, + transformer: Transformer, + axis: YAxis.AxisDependency, + view: ChartViewBase) + { + super.init( + viewPortHandler: viewPortHandler, + xValue: xValue, + yValue: yValue, + transformer: transformer, + view: view) + + self.scaleX = scaleX + self.scaleY = scaleY + self.axisDependency = axis + } + + open override func doJob() + { + guard + let viewPortHandler = viewPortHandler, + let transformer = transformer, + let view = view + else { return } + + var matrix = viewPortHandler.setZoom(scaleX: scaleX, scaleY: scaleY) + viewPortHandler.refresh(newMatrix: matrix, chart: view, invalidate: false) + + let yValsInView = (view as! BarLineChartViewBase).getAxis(axisDependency).axisRange / Double(viewPortHandler.scaleY) + let xValsInView = (view as! BarLineChartViewBase).xAxis.axisRange / Double(viewPortHandler.scaleX) + + var pt = CGPoint( + x: CGFloat(xValue - xValsInView / 2.0), + y: CGFloat(yValue + yValsInView / 2.0) + ) + + transformer.pointValueToPixel(&pt) + + matrix = viewPortHandler.translate(pt: pt) + viewPortHandler.refresh(newMatrix: matrix, chart: view, invalidate: false) + + (view as! BarLineChartViewBase).calculateOffsets() + view.setNeedsDisplay() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/AxisRendererBase.swift b/dydx/Pods/Charts/Source/Charts/Renderers/AxisRendererBase.swift new file mode 100644 index 000000000..302bdf531 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/AxisRendererBase.swift @@ -0,0 +1,218 @@ +// +// AxisRendererBase.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(ChartAxisRendererBase) +open class AxisRendererBase: Renderer +{ + /// base axis this axis renderer works with + @objc open var axis: AxisBase? + + /// transformer to transform values to screen pixels and return + @objc open var transformer: Transformer? + + @objc public init(viewPortHandler: ViewPortHandler, transformer: Transformer?, axis: AxisBase?) + { + super.init(viewPortHandler: viewPortHandler) + + self.transformer = transformer + self.axis = axis + } + + /// Draws the axis labels on the specified context + @objc open func renderAxisLabels(context: CGContext) + { + fatalError("renderAxisLabels() cannot be called on AxisRendererBase") + } + + /// Draws the grid lines belonging to the axis. + @objc open func renderGridLines(context: CGContext) + { + fatalError("renderGridLines() cannot be called on AxisRendererBase") + } + + /// Draws the line that goes alongside the axis. + @objc open func renderAxisLine(context: CGContext) + { + fatalError("renderAxisLine() cannot be called on AxisRendererBase") + } + + /// Draws the LimitLines associated with this axis to the screen. + @objc open func renderLimitLines(context: CGContext) + { + fatalError("renderLimitLines() cannot be called on AxisRendererBase") + } + + /// Computes the axis values. + /// + /// - Parameters: + /// - min: the minimum value in the data object for this axis + /// - max: the maximum value in the data object for this axis + @objc open func computeAxis(min: Double, max: Double, inverted: Bool) + { + var min = min, max = max + + if let transformer = self.transformer + { + // calculate the starting and entry point of the y-labels (depending on zoom / contentrect bounds) + if viewPortHandler.contentWidth > 10.0 && !viewPortHandler.isFullyZoomedOutY + { + let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) + + if !inverted + { + min = Double(p2.y) + max = Double(p1.y) + } + else + { + min = Double(p1.y) + max = Double(p2.y) + } + } + } + + computeAxisValues(min: min, max: max) + } + + /// Sets up the axis values. Computes the desired number of labels between the two given extremes. + @objc open func computeAxisValues(min: Double, max: Double) + { + guard let axis = self.axis else { return } + + let yMin = min + let yMax = max + + let labelCount = axis.labelCount + let range = abs(yMax - yMin) + + if labelCount == 0 || range <= 0 || range.isInfinite + { + axis.entries = [Double]() + axis.centeredEntries = [Double]() + return + } + + // Find out how much spacing (in y value space) between axis values + let rawInterval = range / Double(labelCount) + var interval = rawInterval.roundedToNextSignficant() + + // If granularity is enabled, then do not allow the interval to go below specified granularity. + // This is used to avoid repeated values when rounding values for display. + if axis.granularityEnabled + { + interval = interval < axis.granularity ? axis.granularity : interval + } + + // Normalize interval + let intervalMagnitude = pow(10.0, Double(Int(log10(interval)))).roundedToNextSignficant() + let intervalSigDigit = Int(interval / intervalMagnitude) + if intervalSigDigit > 5 + { + // Use one order of magnitude higher, to avoid intervals like 0.9 or 90 + // if it's 0.0 after floor(), we use the old value + interval = floor(10.0 * intervalMagnitude) == 0.0 ? interval : floor(10.0 * intervalMagnitude) + } + + var n = axis.centerAxisLabelsEnabled ? 1 : 0 + + // force label count + if axis.isForceLabelsEnabled + { + interval = Double(range) / Double(labelCount - 1) + + // Ensure stops contains at least n elements. + axis.entries.removeAll(keepingCapacity: true) + axis.entries.reserveCapacity(labelCount) + + var v = yMin + + for _ in 0 ..< labelCount + { + axis.entries.append(v) + v += interval + } + + n = labelCount + } + else + { + // no forced count + + var first = interval == 0.0 ? 0.0 : ceil(yMin / interval) * interval + + if axis.centerAxisLabelsEnabled + { + first -= interval + } + + let last = interval == 0.0 ? 0.0 : (floor(yMax / interval) * interval).nextUp + + if interval != 0.0 && last != first + { + for _ in stride(from: first, through: last, by: interval) + { + n += 1 + } + } + else if last == first && n == 0 + { + n = 1 + } + + // Ensure stops contains at least n elements. + axis.entries.removeAll(keepingCapacity: true) + axis.entries.reserveCapacity(labelCount) + + var f = first + var i = 0 + while i < n + { + if f == 0.0 + { + // Fix for IEEE negative zero case (Where value == -0.0, and 0.0 == -0.0) + f = 0.0 + } + + axis.entries.append(Double(f)) + + f += interval + i += 1 + } + } + + // set decimals + if interval < 1 + { + axis.decimals = Int(ceil(-log10(interval))) + } + else + { + axis.decimals = 0 + } + + if axis.centerAxisLabelsEnabled + { + axis.centeredEntries.reserveCapacity(n) + axis.centeredEntries.removeAll() + + let offset: Double = interval / 2.0 + + for i in 0 ..< n + { + axis.centeredEntries.append(axis.entries[i] + offset) + } + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/BarChartRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/BarChartRenderer.swift new file mode 100644 index 000000000..c2ff0b108 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/BarChartRenderer.swift @@ -0,0 +1,904 @@ +// +// BarChartRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + +open class BarChartRenderer: BarLineScatterCandleBubbleRenderer +{ + /// A nested array of elements ordered logically (i.e not in visual/drawing order) for use with VoiceOver + /// + /// Its use is apparent when there are multiple data sets, since we want to read bars in left to right order, + /// irrespective of dataset. However, drawing is done per dataset, so using this array and then flattening it prevents us from needing to + /// re-render for the sake of accessibility. + /// + /// In practise, its structure is: + /// + /// ```` + /// [ + /// [dataset1 element1, dataset2 element1], + /// [dataset1 element2, dataset2 element2], + /// [dataset1 element3, dataset2 element3] + /// ... + /// ] + /// ```` + /// This is done to provide numerical inference across datasets to a screenreader user, in the same way that a sighted individual + /// uses a multi-dataset bar chart. + /// + /// The ````internal```` specifier is to allow subclasses (HorizontalBar) to populate the same array + internal lazy var accessibilityOrderedElements: [[NSUIAccessibilityElement]] = accessibilityCreateEmptyOrderedElements() + + private class Buffer + { + var rects = [CGRect]() + } + + @objc open weak var dataProvider: BarChartDataProvider? + + @objc public init(dataProvider: BarChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.dataProvider = dataProvider + } + + // [CGRect] per dataset + private var _buffers = [Buffer]() + + open override func initBuffers() + { + if let barData = dataProvider?.barData + { + // Matche buffers count to dataset count + if _buffers.count != barData.dataSetCount + { + while _buffers.count < barData.dataSetCount + { + _buffers.append(Buffer()) + } + while _buffers.count > barData.dataSetCount + { + _buffers.removeLast() + } + } + + for i in stride(from: 0, to: barData.dataSetCount, by: 1) + { + let set = barData.dataSets[i] as! IBarChartDataSet + let size = set.entryCount * (set.isStacked ? set.stackSize : 1) + if _buffers[i].rects.count != size + { + _buffers[i].rects = [CGRect](repeating: CGRect(), count: size) + } + } + } + else + { + _buffers.removeAll() + } + } + + private func prepareBuffer(dataSet: IBarChartDataSet, index: Int) + { + guard + let dataProvider = dataProvider, + let barData = dataProvider.barData + else { return } + + let barWidthHalf = barData.barWidth / 2.0 + + let buffer = _buffers[index] + var bufferIndex = 0 + let containsStacks = dataSet.isStacked + + let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency) + let phaseY = animator.phaseY + var barRect = CGRect() + var x: Double + var y: Double + + + for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1) + { + guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue } + + let vals = e.yValues + + x = e.x + y = e.y + + if !containsStacks || vals == nil + { + let left = CGFloat(x - barWidthHalf) + let right = CGFloat(x + barWidthHalf) + var top = isInverted + ? (y <= 0.0 ? CGFloat(y) : 0) + : (y >= 0.0 ? CGFloat(y) : 0) + var bottom = isInverted + ? (y >= 0.0 ? CGFloat(y) : 0) + : (y <= 0.0 ? CGFloat(y) : 0) + + /* When drawing each bar, the renderer actually draws each bar from 0 to the required value. + * This drawn bar is then clipped to the visible chart rect in BarLineChartViewBase's draw(rect:) using clipDataToContent. + * While this works fine when calculating the bar rects for drawing, it causes the accessibilityFrames to be oversized in some cases. + * This offset attempts to undo that unnecessary drawing when calculating barRects + * + * +---------------------------------------------------------------+---------------------------------------------------------------+ + * | Situation 1: (!inverted && y >= 0) | Situation 3: (inverted && y >= 0) | + * | | | + * | y -> +--+ <- top | 0 -> ---+--+---+--+------ <- top | + * | |//| } topOffset = y - max | | | |//| } topOffset = min | + * | max -> +---------+--+----+ <- top - topOffset | min -> +--+--+---+--+----+ <- top + topOffset | + * | | +--+ |//| | | | | | |//| | | + * | | | | |//| | | | +--+ |//| | | + * | | | | |//| | | | |//| | | + * | min -> +--+--+---+--+----+ <- bottom + bottomOffset | max -> +---------+--+----+ <- bottom - bottomOffset | + * | | | |//| } bottomOffset = min | |//| } bottomOffset = y - max | + * | 0 -> ---+--+---+--+----- <- bottom | y -> +--+ <- bottom | + * | | | + * +---------------------------------------------------------------+---------------------------------------------------------------+ + * | Situation 2: (!inverted && y < 0) | Situation 4: (inverted && y < 0) | + * | | | + * | 0 -> ---+--+---+--+----- <- top | y -> +--+ <- top | + * | | | |//| } topOffset = -max | |//| } topOffset = min - y | + * | max -> +--+--+---+--+----+ <- top - topOffset | min -> +---------+--+----+ <- top + topOffset | + * | | | | |//| | | | +--+ |//| | | + * | | +--+ |//| | | | | | |//| | | + * | | |//| | | | | | |//| | | + * | min -> +---------+--+----+ <- bottom + bottomOffset | max -> +--+--+---+--+----+ <- bottom - bottomOffset | + * | |//| } bottomOffset = min - y | | | |//| } bottomOffset = -max | + * | y -> +--+ <- bottom | 0 -> ---+--+---+--+------- <- bottom | + * | | | + * +---------------------------------------------------------------+---------------------------------------------------------------+ + */ + var topOffset: CGFloat = 0.0 + var bottomOffset: CGFloat = 0.0 + if let offsetView = dataProvider as? BarChartView + { + let offsetAxis = offsetView.getAxis(dataSet.axisDependency) + if y >= 0 + { + // situation 1 + if offsetAxis.axisMaximum < y + { + topOffset = CGFloat(y - offsetAxis.axisMaximum) + } + if offsetAxis.axisMinimum > 0 + { + bottomOffset = CGFloat(offsetAxis.axisMinimum) + } + } + else // y < 0 + { + //situation 2 + if offsetAxis.axisMaximum < 0 + { + topOffset = CGFloat(offsetAxis.axisMaximum * -1) + } + if offsetAxis.axisMinimum > y + { + bottomOffset = CGFloat(offsetAxis.axisMinimum - y) + } + } + if isInverted + { + // situation 3 and 4 + // exchange topOffset/bottomOffset based on 1 and 2 + // see diagram above + (topOffset, bottomOffset) = (bottomOffset, topOffset) + } + } + //apply offset + top = isInverted ? top + topOffset : top - topOffset + bottom = isInverted ? bottom - bottomOffset : bottom + bottomOffset + + // multiply the height of the rect with the phase + // explicitly add 0 + topOffset to indicate this is changed after adding accessibility support (#3650, #3520) + if top > 0 + topOffset + { + top *= CGFloat(phaseY) + } + else + { + bottom *= CGFloat(phaseY) + } + + barRect.origin.x = left + barRect.origin.y = top + barRect.size.width = right - left + barRect.size.height = bottom - top + buffer.rects[bufferIndex] = barRect + bufferIndex += 1 + } + else + { + var posY = 0.0 + var negY = -e.negativeSum + var yStart = 0.0 + + // fill the stack + for k in 0 ..< vals!.count + { + let value = vals![k] + + if value == 0.0 && (posY == 0.0 || negY == 0.0) + { + // Take care of the situation of a 0.0 value, which overlaps a non-zero bar + y = value + yStart = y + } + else if value >= 0.0 + { + y = posY + yStart = posY + value + posY = yStart + } + else + { + y = negY + yStart = negY + abs(value) + negY += abs(value) + } + + let left = CGFloat(x - barWidthHalf) + let right = CGFloat(x + barWidthHalf) + var top = isInverted + ? (y <= yStart ? CGFloat(y) : CGFloat(yStart)) + : (y >= yStart ? CGFloat(y) : CGFloat(yStart)) + var bottom = isInverted + ? (y >= yStart ? CGFloat(y) : CGFloat(yStart)) + : (y <= yStart ? CGFloat(y) : CGFloat(yStart)) + + // multiply the height of the rect with the phase + top *= CGFloat(phaseY) + bottom *= CGFloat(phaseY) + + barRect.origin.x = left + barRect.size.width = right - left + barRect.origin.y = top + barRect.size.height = bottom - top + + buffer.rects[bufferIndex] = barRect + bufferIndex += 1 + } + } + } + } + + open override func drawData(context: CGContext) + { + guard + let dataProvider = dataProvider, + let barData = dataProvider.barData + else { return } + + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + accessibilityOrderedElements = accessibilityCreateEmptyOrderedElements() + + // Make the chart header the first element in the accessible elements array + if let chart = dataProvider as? BarChartView { + let element = createAccessibleHeader(usingChart: chart, + andData: barData, + withDefaultDescription: "Bar Chart") + accessibleChartElements.append(element) + } + + // Populate logically ordered nested elements into accessibilityOrderedElements in drawDataSet() + for i in 0 ..< barData.dataSetCount + { + guard let set = barData.getDataSetByIndex(i) else { continue } + + if set.isVisible + { + if !(set is IBarChartDataSet) + { + fatalError("Datasets for BarChartRenderer must conform to IBarChartDataset") + } + + drawDataSet(context: context, dataSet: set as! IBarChartDataSet, index: i) + } + } + + // Merge nested ordered arrays into the single accessibleChartElements. + accessibleChartElements.append(contentsOf: accessibilityOrderedElements.flatMap { $0 } ) + accessibilityPostLayoutChangedNotification() + } + + private var _barShadowRectBuffer: CGRect = CGRect() + + @objc open func drawDataSet(context: CGContext, dataSet: IBarChartDataSet, index: Int) + { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + prepareBuffer(dataSet: dataSet, index: index) + trans.rectValuesToPixel(&_buffers[index].rects) + + let borderWidth = dataSet.barBorderWidth + let borderColor = dataSet.barBorderColor + let drawBorder = borderWidth > 0.0 + + context.saveGState() + + // draw the bar shadow before the values + if dataProvider.isDrawBarShadowEnabled + { + guard let barData = dataProvider.barData else { return } + + let barWidth = barData.barWidth + let barWidthHalf = barWidth / 2.0 + var x: Double = 0.0 + + for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1) + { + guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue } + + x = e.x + + _barShadowRectBuffer.origin.x = CGFloat(x - barWidthHalf) + _barShadowRectBuffer.size.width = CGFloat(barWidth) + + trans.rectValueToPixel(&_barShadowRectBuffer) + + if !viewPortHandler.isInBoundsLeft(_barShadowRectBuffer.origin.x + _barShadowRectBuffer.size.width) + { + continue + } + + if !viewPortHandler.isInBoundsRight(_barShadowRectBuffer.origin.x) + { + break + } + + _barShadowRectBuffer.origin.y = viewPortHandler.contentTop + _barShadowRectBuffer.size.height = viewPortHandler.contentHeight + + context.setFillColor(dataSet.barShadowColor.cgColor) + context.fill(_barShadowRectBuffer) + } + } + + let buffer = _buffers[index] + + // draw the bar shadow before the values + if dataProvider.isDrawBarShadowEnabled + { + for j in stride(from: 0, to: buffer.rects.count, by: 1) + { + let barRect = buffer.rects[j] + + if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width)) + { + continue + } + + if (!viewPortHandler.isInBoundsRight(barRect.origin.x)) + { + break + } + + context.setFillColor(dataSet.barShadowColor.cgColor) + context.fill(barRect) + } + } + + let isSingleColor = dataSet.colors.count == 1 + + if isSingleColor + { + context.setFillColor(dataSet.color(atIndex: 0).cgColor) + } + + // In case the chart is stacked, we need to accomodate individual bars within accessibilityOrdereredElements + let isStacked = dataSet.isStacked + let stackSize = isStacked ? dataSet.stackSize : 1 + + for j in stride(from: 0, to: buffer.rects.count, by: 1) + { + let barRect = buffer.rects[j] + + if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width)) + { + continue + } + + if (!viewPortHandler.isInBoundsRight(barRect.origin.x)) + { + break + } + + if !isSingleColor + { + // Set the color for the currently drawn value. If the index is out of bounds, reuse colors. + context.setFillColor(dataSet.color(atIndex: j).cgColor) + } + + context.fill(barRect) + + if drawBorder + { + context.setStrokeColor(borderColor.cgColor) + context.setLineWidth(borderWidth) + context.stroke(barRect) + } + + // Create and append the corresponding accessibility element to accessibilityOrderedElements + if let chart = dataProvider as? BarChartView + { + let element = createAccessibleElement(withIndex: j, + container: chart, + dataSet: dataSet, + dataSetIndex: index, + stackSize: stackSize) + { (element) in + element.accessibilityFrame = barRect + } + + accessibilityOrderedElements[j/stackSize].append(element) + } + } + + context.restoreGState() + } + + open func prepareBarHighlight( + x: Double, + y1: Double, + y2: Double, + barWidthHalf: Double, + trans: Transformer, + rect: inout CGRect) + { + let left = x - barWidthHalf + let right = x + barWidthHalf + let top = y1 + let bottom = y2 + + rect.origin.x = CGFloat(left) + rect.origin.y = CGFloat(top) + rect.size.width = CGFloat(right - left) + rect.size.height = CGFloat(bottom - top) + + trans.rectValueToPixel(&rect, phaseY: animator.phaseY ) + } + + open override func drawValues(context: CGContext) + { + // if values are drawn + if isDrawingValuesAllowed(dataProvider: dataProvider) + { + guard + let dataProvider = dataProvider, + let barData = dataProvider.barData + else { return } + + let dataSets = barData.dataSets + + let valueOffsetPlus: CGFloat = 4.5 + var posOffset: CGFloat + var negOffset: CGFloat + let drawValueAboveBar = dataProvider.isDrawValueAboveBarEnabled + + for dataSetIndex in 0 ..< barData.dataSetCount + { + guard let + dataSet = dataSets[dataSetIndex] as? IBarChartDataSet, + shouldDrawValues(forDataSet: dataSet) + else { continue } + + let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency) + + // calculate the correct offset depending on the draw position of the value + let valueFont = dataSet.valueFont + let valueTextHeight = valueFont.lineHeight + posOffset = (drawValueAboveBar ? -(valueTextHeight + valueOffsetPlus) : valueOffsetPlus) + negOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextHeight + valueOffsetPlus)) + + if isInverted + { + posOffset = -posOffset - valueTextHeight + negOffset = -negOffset - valueTextHeight + } + + let buffer = _buffers[dataSetIndex] + + guard let formatter = dataSet.valueFormatter else { continue } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + + let iconsOffset = dataSet.iconsOffset + + // if only single values are drawn (sum) + if !dataSet.isStacked + { + for j in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX)) + { + guard let e = dataSet.entryForIndex(j) as? BarChartDataEntry else { continue } + + let rect = buffer.rects[j] + + let x = rect.origin.x + rect.size.width / 2.0 + + if !viewPortHandler.isInBoundsRight(x) + { + break + } + + if !viewPortHandler.isInBoundsY(rect.origin.y) + || !viewPortHandler.isInBoundsLeft(x) + { + continue + } + + let val = e.y + + if dataSet.isDrawValuesEnabled + { + drawValue( + context: context, + value: formatter.stringForValue( + val, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler), + xPos: x, + yPos: val >= 0.0 + ? (rect.origin.y + posOffset) + : (rect.origin.y + rect.size.height + negOffset), + font: valueFont, + align: .center, + color: dataSet.valueTextColorAt(j)) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled + { + var px = x + var py = val >= 0.0 + ? (rect.origin.y + posOffset) + : (rect.origin.y + rect.size.height + negOffset) + + px += iconsOffset.x + py += iconsOffset.y + + ChartUtils.drawImage( + context: context, + image: icon, + x: px, + y: py, + size: icon.size) + } + } + } + else + { + // if we have stacks + + var bufferIndex = 0 + + for index in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX)) + { + guard let e = dataSet.entryForIndex(index) as? BarChartDataEntry else { continue } + + let vals = e.yValues + + let rect = buffer.rects[bufferIndex] + + let x = rect.origin.x + rect.size.width / 2.0 + + // we still draw stacked bars, but there is one non-stacked in between + if vals == nil + { + if !viewPortHandler.isInBoundsRight(x) + { + break + } + + if !viewPortHandler.isInBoundsY(rect.origin.y) + || !viewPortHandler.isInBoundsLeft(x) + { + continue + } + + if dataSet.isDrawValuesEnabled + { + drawValue( + context: context, + value: formatter.stringForValue( + e.y, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler), + xPos: x, + yPos: rect.origin.y + + (e.y >= 0 ? posOffset : negOffset), + font: valueFont, + align: .center, + color: dataSet.valueTextColorAt(index)) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled + { + var px = x + var py = rect.origin.y + + (e.y >= 0 ? posOffset : negOffset) + + px += iconsOffset.x + py += iconsOffset.y + + ChartUtils.drawImage( + context: context, + image: icon, + x: px, + y: py, + size: icon.size) + } + } + else + { + // draw stack values + + let vals = vals! + var transformed = [CGPoint]() + + var posY = 0.0 + var negY = -e.negativeSum + + for k in 0 ..< vals.count + { + let value = vals[k] + var y: Double + + if value == 0.0 && (posY == 0.0 || negY == 0.0) + { + // Take care of the situation of a 0.0 value, which overlaps a non-zero bar + y = value + } + else if value >= 0.0 + { + posY += value + y = posY + } + else + { + y = negY + negY -= value + } + + transformed.append(CGPoint(x: 0.0, y: CGFloat(y * phaseY))) + } + + trans.pointValuesToPixel(&transformed) + + for k in 0 ..< transformed.count + { + let val = vals[k] + let drawBelow = (val == 0.0 && negY == 0.0 && posY > 0.0) || val < 0.0 + let y = transformed[k].y + (drawBelow ? negOffset : posOffset) + + if !viewPortHandler.isInBoundsRight(x) + { + break + } + + if !viewPortHandler.isInBoundsY(y) || !viewPortHandler.isInBoundsLeft(x) + { + continue + } + + if dataSet.isDrawValuesEnabled + { + drawValue( + context: context, + value: formatter.stringForValue( + vals[k], + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler), + xPos: x, + yPos: y, + font: valueFont, + align: .center, + color: dataSet.valueTextColorAt(index)) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled + { + ChartUtils.drawImage( + context: context, + image: icon, + x: x + iconsOffset.x, + y: y + iconsOffset.y, + size: icon.size) + } + } + } + + bufferIndex = vals == nil ? (bufferIndex + 1) : (bufferIndex + vals!.count) + } + } + } + } + } + + /// Draws a value at the specified x and y position. + @objc open func drawValue(context: CGContext, value: String, xPos: CGFloat, yPos: CGFloat, font: NSUIFont, align: NSTextAlignment, color: NSUIColor) + { + ChartUtils.drawText(context: context, text: value, point: CGPoint(x: xPos, y: yPos), align: align, attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color]) + } + + open override func drawExtras(context: CGContext) + { + + } + + open override func drawHighlighted(context: CGContext, indices: [Highlight]) + { + guard + let dataProvider = dataProvider, + let barData = dataProvider.barData + else { return } + + context.saveGState() + + var barRect = CGRect() + + for high in indices + { + guard + let set = barData.getDataSetByIndex(high.dataSetIndex) as? IBarChartDataSet, + set.isHighlightEnabled + else { continue } + + if let e = set.entryForXValue(high.x, closestToY: high.y) as? BarChartDataEntry + { + if !isInBoundsX(entry: e, dataSet: set) + { + continue + } + + let trans = dataProvider.getTransformer(forAxis: set.axisDependency) + + context.setFillColor(set.highlightColor.cgColor) + context.setAlpha(set.highlightAlpha) + + let isStack = high.stackIndex >= 0 && e.isStacked + + let y1: Double + let y2: Double + + if isStack + { + if dataProvider.isHighlightFullBarEnabled + { + y1 = e.positiveSum + y2 = -e.negativeSum + } + else + { + let range = e.ranges?[high.stackIndex] + + y1 = range?.from ?? 0.0 + y2 = range?.to ?? 0.0 + } + } + else + { + y1 = e.y + y2 = 0.0 + } + + prepareBarHighlight(x: e.x, y1: y1, y2: y2, barWidthHalf: barData.barWidth / 2.0, trans: trans, rect: &barRect) + + setHighlightDrawPos(highlight: high, barRect: barRect) + + context.fill(barRect) + } + } + + context.restoreGState() + } + + /// Sets the drawing position of the highlight object based on the given bar-rect. + internal func setHighlightDrawPos(highlight high: Highlight, barRect: CGRect) + { + high.setDraw(x: barRect.midX, y: barRect.origin.y) + } + + /// Creates a nested array of empty subarrays each of which will be populated with NSUIAccessibilityElements. + /// This is marked internal to support HorizontalBarChartRenderer as well. + internal func accessibilityCreateEmptyOrderedElements() -> [[NSUIAccessibilityElement]] + { + guard let chart = dataProvider as? BarChartView else { return [] } + + // Unlike Bubble & Line charts, here we use the maximum entry count to account for stacked bars + let maxEntryCount = chart.data?.maxEntryCountSet?.entryCount ?? 0 + + return Array(repeating: [NSUIAccessibilityElement](), + count: maxEntryCount) + } + + /// Creates an NSUIAccessibleElement representing the smallest meaningful bar of the chart + /// i.e. in case of a stacked chart, this returns each stack, not the combined bar. + /// Note that it is marked internal to support subclass modification in the HorizontalBarChart. + internal func createAccessibleElement(withIndex idx: Int, + container: BarChartView, + dataSet: IBarChartDataSet, + dataSetIndex: Int, + stackSize: Int, + modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement + { + let element = NSUIAccessibilityElement(accessibilityContainer: container) + let xAxis = container.xAxis + + guard let e = dataSet.entryForIndex(idx/stackSize) as? BarChartDataEntry else { return element } + guard let dataProvider = dataProvider else { return element } + + // NOTE: The formatter can cause issues when the x-axis labels are consecutive ints. + // i.e. due to the Double conversion, if there are more than one data set that are grouped, + // there is the possibility of some labels being rounded up. A floor() might fix this, but seems to be a brute force solution. + let label = xAxis.valueFormatter?.stringForValue(e.x, axis: xAxis) ?? "\(e.x)" + + var elementValueText = dataSet.valueFormatter?.stringForValue( + e.y, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler) ?? "\(e.y)" + + if dataSet.isStacked, let vals = e.yValues + { + let labelCount = min(dataSet.colors.count, stackSize) + + let stackLabel: String? + if (dataSet.stackLabels.count > 0 && labelCount > 0) { + let labelIndex = idx % labelCount + stackLabel = dataSet.stackLabels.indices.contains(labelIndex) ? dataSet.stackLabels[labelIndex] : nil + } else { + stackLabel = nil + } + + //Handles empty array of yValues + let yValue = vals.isEmpty ? 0.0 : vals[idx % vals.count] + + elementValueText = dataSet.valueFormatter?.stringForValue( + yValue, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler) ?? "\(e.y)" + + if let stackLabel = stackLabel { + elementValueText = stackLabel + " \(elementValueText)" + } else { + elementValueText = "\(elementValueText)" + } + } + + let dataSetCount = dataProvider.barData?.dataSetCount ?? -1 + let doesContainMultipleDataSets = dataSetCount > 1 + + element.accessibilityLabel = "\(doesContainMultipleDataSets ? (dataSet.label ?? "") + ", " : "") \(label): \(elementValueText)" + + modifier(element) + + return element + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/BarLineScatterCandleBubbleRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/BarLineScatterCandleBubbleRenderer.swift new file mode 100644 index 000000000..82e6df9c8 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/BarLineScatterCandleBubbleRenderer.swift @@ -0,0 +1,127 @@ +// +// BarLineScatterCandleBubbleRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(BarLineScatterCandleBubbleChartRenderer) +open class BarLineScatterCandleBubbleRenderer: DataRenderer +{ + internal var _xBounds = XBounds() // Reusable XBounds object + + public override init(animator: Animator, viewPortHandler: ViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler) + } + + /// Checks if the provided entry object is in bounds for drawing considering the current animation phase. + internal func isInBoundsX(entry e: ChartDataEntry, dataSet: IBarLineScatterCandleBubbleChartDataSet) -> Bool + { + let entryIndex = dataSet.entryIndex(entry: e) + return Double(entryIndex) < Double(dataSet.entryCount) * animator.phaseX + } + + /// Calculates and returns the x-bounds for the given DataSet in terms of index in their values array. + /// This includes minimum and maximum visible x, as well as range. + internal func xBounds(chart: BarLineScatterCandleBubbleChartDataProvider, + dataSet: IBarLineScatterCandleBubbleChartDataSet, + animator: Animator?) -> XBounds + { + return XBounds(chart: chart, dataSet: dataSet, animator: animator) + } + + /// - Returns: `true` if the DataSet values should be drawn, `false` if not. + internal func shouldDrawValues(forDataSet set: IChartDataSet) -> Bool + { + return set.isVisible && (set.isDrawValuesEnabled || set.isDrawIconsEnabled) + } + + /// Class representing the bounds of the current viewport in terms of indices in the values array of a DataSet. + open class XBounds + { + /// minimum visible entry index + open var min: Int = 0 + + /// maximum visible entry index + open var max: Int = 0 + + /// range of visible entry indices + open var range: Int = 0 + + public init() + { + + } + + public init(chart: BarLineScatterCandleBubbleChartDataProvider, + dataSet: IBarLineScatterCandleBubbleChartDataSet, + animator: Animator?) + { + self.set(chart: chart, dataSet: dataSet, animator: animator) + } + + /// Calculates the minimum and maximum x values as well as the range between them. + open func set(chart: BarLineScatterCandleBubbleChartDataProvider, + dataSet: IBarLineScatterCandleBubbleChartDataSet, + animator: Animator?) + { + let phaseX = Swift.max(0.0, Swift.min(1.0, animator?.phaseX ?? 1.0)) + + let low = chart.lowestVisibleX + let high = chart.highestVisibleX + + let entryFrom = dataSet.entryForXValue(low, closestToY: .nan, rounding: .down) + let entryTo = dataSet.entryForXValue(high, closestToY: .nan, rounding: .up) + + self.min = entryFrom == nil ? 0 : dataSet.entryIndex(entry: entryFrom!) + self.max = entryTo == nil ? 0 : dataSet.entryIndex(entry: entryTo!) + range = Int(Double(self.max - self.min) * phaseX) + } + } +} + +extension BarLineScatterCandleBubbleRenderer.XBounds: RangeExpression { + public func relative(to collection: C) -> Swift.Range + where C : Collection, Bound == C.Index + { + return Swift.Range(min...min + range) + } + + public func contains(_ element: Int) -> Bool { + return (min...min + range).contains(element) + } +} + +extension BarLineScatterCandleBubbleRenderer.XBounds: Sequence { + public struct Iterator: IteratorProtocol { + private var iterator: IndexingIterator> + + fileprivate init(min: Int, max: Int) { + self.iterator = (min...max).makeIterator() + } + + public mutating func next() -> Int? { + return self.iterator.next() + } + } + + public func makeIterator() -> Iterator { + return Iterator(min: self.min, max: self.min + self.range) + } +} + +extension BarLineScatterCandleBubbleRenderer.XBounds: CustomDebugStringConvertible +{ + public var debugDescription: String + { + return "min:\(self.min), max:\(self.max), range:\(self.range)" + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/BubbleChartRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/BubbleChartRenderer.swift new file mode 100644 index 000000000..1a089e9f3 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/BubbleChartRenderer.swift @@ -0,0 +1,359 @@ +// +// BubbleChartRenderer.swift +// Charts +// +// Bubble chart implementation: +// Copyright 2015 Pierre-Marc Airoldi +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class BubbleChartRenderer: BarLineScatterCandleBubbleRenderer +{ + /// A nested array of elements ordered logically (i.e not in visual/drawing order) for use with VoiceOver. + private lazy var accessibilityOrderedElements: [[NSUIAccessibilityElement]] = accessibilityCreateEmptyOrderedElements() + + @objc open weak var dataProvider: BubbleChartDataProvider? + + @objc public init(dataProvider: BubbleChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.dataProvider = dataProvider + } + + open override func drawData(context: CGContext) + { + guard + let dataProvider = dataProvider, + let bubbleData = dataProvider.bubbleData + else { return } + + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + accessibilityOrderedElements = accessibilityCreateEmptyOrderedElements() + + // Make the chart header the first element in the accessible elements array + if let chart = dataProvider as? BubbleChartView { + let element = createAccessibleHeader(usingChart: chart, + andData: bubbleData, + withDefaultDescription: "Bubble Chart") + accessibleChartElements.append(element) + } + + for (i, set) in (bubbleData.dataSets as! [IBubbleChartDataSet]).enumerated() where set.isVisible + { + drawDataSet(context: context, dataSet: set, dataSetIndex: i) + } + + // Merge nested ordered arrays into the single accessibleChartElements. + accessibleChartElements.append(contentsOf: accessibilityOrderedElements.flatMap { $0 } ) + accessibilityPostLayoutChangedNotification() + } + + private func getShapeSize( + entrySize: CGFloat, + maxSize: CGFloat, + reference: CGFloat, + normalizeSize: Bool) -> CGFloat + { + let factor: CGFloat = normalizeSize + ? ((maxSize == 0.0) ? 1.0 : sqrt(entrySize / maxSize)) + : entrySize + let shapeSize: CGFloat = reference * factor + return shapeSize + } + + private var _pointBuffer = CGPoint() + private var _sizeBuffer = [CGPoint](repeating: CGPoint(), count: 2) + + @objc open func drawDataSet(context: CGContext, dataSet: IBubbleChartDataSet, dataSetIndex: Int) + { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + let valueToPixelMatrix = trans.valueToPixelMatrix + + _sizeBuffer[0].x = 0.0 + _sizeBuffer[0].y = 0.0 + _sizeBuffer[1].x = 1.0 + _sizeBuffer[1].y = 0.0 + + trans.pointValuesToPixel(&_sizeBuffer) + + context.saveGState() + defer { context.restoreGState() } + + let normalizeSize = dataSet.isNormalizeSizeEnabled + + // calcualte the full width of 1 step on the x-axis + let maxBubbleWidth: CGFloat = abs(_sizeBuffer[1].x - _sizeBuffer[0].x) + let maxBubbleHeight: CGFloat = abs(viewPortHandler.contentBottom - viewPortHandler.contentTop) + let referenceSize: CGFloat = min(maxBubbleHeight, maxBubbleWidth) + + for j in _xBounds + { + guard let entry = dataSet.entryForIndex(j) as? BubbleChartDataEntry else { continue } + + _pointBuffer.x = CGFloat(entry.x) + _pointBuffer.y = CGFloat(entry.y * phaseY) + _pointBuffer = _pointBuffer.applying(valueToPixelMatrix) + + let shapeSize = getShapeSize(entrySize: entry.size, maxSize: dataSet.maxSize, reference: referenceSize, normalizeSize: normalizeSize) + let shapeHalf = shapeSize / 2.0 + + guard + viewPortHandler.isInBoundsTop(_pointBuffer.y + shapeHalf), + viewPortHandler.isInBoundsBottom(_pointBuffer.y - shapeHalf), + viewPortHandler.isInBoundsLeft(_pointBuffer.x + shapeHalf) + else { continue } + + guard viewPortHandler.isInBoundsRight(_pointBuffer.x - shapeHalf) else { break } + + let color = dataSet.color(atIndex: j) + + let rect = CGRect( + x: _pointBuffer.x - shapeHalf, + y: _pointBuffer.y - shapeHalf, + width: shapeSize, + height: shapeSize + ) + + context.setFillColor(color.cgColor) + context.fillEllipse(in: rect) + + // Create and append the corresponding accessibility element to accessibilityOrderedElements + if let chart = dataProvider as? BubbleChartView + { + let element = createAccessibleElement(withIndex: j, + container: chart, + dataSet: dataSet, + dataSetIndex: dataSetIndex, + shapeSize: shapeSize) + { (element) in + element.accessibilityFrame = rect + } + + accessibilityOrderedElements[dataSetIndex].append(element) + } + } + } + + open override func drawValues(context: CGContext) + { + guard let + dataProvider = dataProvider, + let bubbleData = dataProvider.bubbleData, + isDrawingValuesAllowed(dataProvider: dataProvider), + let dataSets = bubbleData.dataSets as? [IBubbleChartDataSet] + else { return } + + let phaseX = max(0.0, min(1.0, animator.phaseX)) + let phaseY = animator.phaseY + + var pt = CGPoint() + + for i in 0.. [[NSUIAccessibilityElement]] + { + guard let chart = dataProvider as? BubbleChartView else { return [] } + + let dataSetCount = chart.bubbleData?.dataSetCount ?? 0 + + return Array(repeating: [NSUIAccessibilityElement](), + count: dataSetCount) + } + + /// Creates an NSUIAccessibleElement representing individual bubbles location and relative size. + private func createAccessibleElement(withIndex idx: Int, + container: BubbleChartView, + dataSet: IBubbleChartDataSet, + dataSetIndex: Int, + shapeSize: CGFloat, + modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement + { + let element = NSUIAccessibilityElement(accessibilityContainer: container) + let xAxis = container.xAxis + + guard let e = dataSet.entryForIndex(idx) else { return element } + guard let dataProvider = dataProvider else { return element } + + // NOTE: The formatter can cause issues when the x-axis labels are consecutive ints. + // i.e. due to the Double conversion, if there are more than one data set that are grouped, + // there is the possibility of some labels being rounded up. A floor() might fix this, but seems to be a brute force solution. + let label = xAxis.valueFormatter?.stringForValue(e.x, axis: xAxis) ?? "\(e.x)" + + let elementValueText = dataSet.valueFormatter?.stringForValue(e.y, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler) ?? "\(e.y)" + + let dataSetCount = dataProvider.bubbleData?.dataSetCount ?? -1 + let doesContainMultipleDataSets = dataSetCount > 1 + + element.accessibilityLabel = "\(doesContainMultipleDataSets ? (dataSet.label ?? "") + ", " : "") \(label): \(elementValueText), bubble size: \(String(format: "%.2f", (shapeSize/dataSet.maxSize) * 100)) %" + + modifier(element) + + return element + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/CandleStickChartRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/CandleStickChartRenderer.swift new file mode 100644 index 000000000..4314ab552 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/CandleStickChartRenderer.swift @@ -0,0 +1,426 @@ +// +// CandleStickChartRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class CandleStickChartRenderer: LineScatterCandleRadarRenderer +{ + @objc open weak var dataProvider: CandleChartDataProvider? + + @objc public init(dataProvider: CandleChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.dataProvider = dataProvider + } + + open override func drawData(context: CGContext) + { + guard let dataProvider = dataProvider, let candleData = dataProvider.candleData else { return } + + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + + // Make the chart header the first element in the accessible elements array + if let chart = dataProvider as? CandleStickChartView { + let element = createAccessibleHeader(usingChart: chart, + andData: candleData, + withDefaultDescription: "CandleStick Chart") + accessibleChartElements.append(element) + } + + for set in candleData.dataSets as! [ICandleChartDataSet] where set.isVisible + { + drawDataSet(context: context, dataSet: set) + } + } + + private var _shadowPoints = [CGPoint](repeating: CGPoint(), count: 4) + private var _rangePoints = [CGPoint](repeating: CGPoint(), count: 2) + private var _openPoints = [CGPoint](repeating: CGPoint(), count: 2) + private var _closePoints = [CGPoint](repeating: CGPoint(), count: 2) + private var _bodyRect = CGRect() + private var _lineSegments = [CGPoint](repeating: CGPoint(), count: 2) + + @objc open func drawDataSet(context: CGContext, dataSet: ICandleChartDataSet) + { + guard + let dataProvider = dataProvider + else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + let barSpace = dataSet.barSpace + let showCandleBar = dataSet.showCandleBar + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + context.saveGState() + + context.setLineWidth(dataSet.shadowWidth) + + for j in _xBounds + { + // get the entry + guard let e = dataSet.entryForIndex(j) as? CandleChartDataEntry else { continue } + + let xPos = e.x + + let open = e.open + let close = e.close + let high = e.high + let low = e.low + + var previous: CandleChartDataEntry? + if j > 0 { + previous = dataSet.entryForIndex(j - 1) as? CandleChartDataEntry + } + let previousClose = previous?.close ?? open + + let doesContainMultipleDataSets = (dataProvider.candleData?.dataSets.count ?? 1) > 1 + var accessibilityMovementDescription = "neutral" + var accessibilityRect = CGRect(x: CGFloat(xPos) + 0.5 - barSpace, + y: CGFloat(low * phaseY), + width: (2 * barSpace) - 1.0, + height: (CGFloat(abs(high - low) * phaseY))) + trans.rectValueToPixel(&accessibilityRect) + + if showCandleBar + { + // calculate the shadow + + _shadowPoints[0].x = CGFloat(xPos) + _shadowPoints[1].x = CGFloat(xPos) + _shadowPoints[2].x = CGFloat(xPos) + _shadowPoints[3].x = CGFloat(xPos) + + if open > close + { + _shadowPoints[0].y = CGFloat(high * phaseY) + _shadowPoints[1].y = CGFloat(open * phaseY) + _shadowPoints[2].y = CGFloat(low * phaseY) + _shadowPoints[3].y = CGFloat(close * phaseY) + } + else if open < close + { + _shadowPoints[0].y = CGFloat(high * phaseY) + _shadowPoints[1].y = CGFloat(close * phaseY) + _shadowPoints[2].y = CGFloat(low * phaseY) + _shadowPoints[3].y = CGFloat(open * phaseY) + } + else + { + _shadowPoints[0].y = CGFloat(high * phaseY) + _shadowPoints[1].y = CGFloat(open * phaseY) + _shadowPoints[2].y = CGFloat(low * phaseY) + _shadowPoints[3].y = _shadowPoints[1].y + } + + trans.pointValuesToPixel(&_shadowPoints) + + // draw the shadows + + var shadowColor: NSUIColor! = nil + if dataSet.shadowColorSameAsCandle + { + if previousClose > close + { + shadowColor = dataSet.decreasingColor ?? dataSet.color(atIndex: j) + } + else if previousClose < close + { + shadowColor = dataSet.increasingColor ?? dataSet.color(atIndex: j) + } + else + { + shadowColor = dataSet.neutralColor ?? dataSet.color(atIndex: j) + } + } + + if shadowColor === nil + { + shadowColor = dataSet.shadowColor ?? dataSet.color(atIndex: j) + } + + context.setStrokeColor(shadowColor.cgColor) + context.strokeLineSegments(between: _shadowPoints) + + // calculate the body + + _bodyRect.origin.x = CGFloat(xPos) - 0.5 + barSpace + _bodyRect.origin.y = CGFloat(close * phaseY) + _bodyRect.size.width = (CGFloat(xPos) + 0.5 - barSpace) - _bodyRect.origin.x + _bodyRect.size.height = CGFloat(open * phaseY) - _bodyRect.origin.y + + trans.rectValueToPixel(&_bodyRect) + + // draw body differently for increasing and decreasing entry + + if open > close + { + accessibilityMovementDescription = "decreasing" + + let color = dataSet.decreasingColor ?? dataSet.color(atIndex: j) + + if dataSet.isDecreasingFilled + { + context.setFillColor(shadowColor.cgColor) + context.fill(_bodyRect) + } + else + { + context.setStrokeColor(shadowColor.cgColor) + context.stroke(_bodyRect) + } + } + else if open < close + { + accessibilityMovementDescription = "increasing" + + let color = dataSet.increasingColor ?? dataSet.color(atIndex: j) + + if dataSet.isIncreasingFilled + { + context.setFillColor(shadowColor.cgColor) + context.fill(_bodyRect) + } + else + { + context.setStrokeColor(shadowColor.cgColor) + context.stroke(_bodyRect) + } + } + else + { + let color = dataSet.neutralColor ?? dataSet.color(atIndex: j) + + context.setStrokeColor(shadowColor.cgColor) + context.stroke(_bodyRect) + } + } + else + { + _rangePoints[0].x = CGFloat(xPos) + _rangePoints[0].y = CGFloat(high * phaseY) + _rangePoints[1].x = CGFloat(xPos) + _rangePoints[1].y = CGFloat(low * phaseY) + + _openPoints[0].x = CGFloat(xPos) - 0.5 + barSpace + _openPoints[0].y = CGFloat(open * phaseY) + _openPoints[1].x = CGFloat(xPos) + _openPoints[1].y = CGFloat(open * phaseY) + + _closePoints[0].x = CGFloat(xPos) + 0.5 - barSpace + _closePoints[0].y = CGFloat(close * phaseY) + _closePoints[1].x = CGFloat(xPos) + _closePoints[1].y = CGFloat(close * phaseY) + + trans.pointValuesToPixel(&_rangePoints) + trans.pointValuesToPixel(&_openPoints) + trans.pointValuesToPixel(&_closePoints) + + // draw the ranges + var barColor: NSUIColor! = nil + + if open > close + { + accessibilityMovementDescription = "decreasing" + barColor = dataSet.decreasingColor ?? dataSet.color(atIndex: j) + } + else if open < close + { + accessibilityMovementDescription = "increasing" + barColor = dataSet.increasingColor ?? dataSet.color(atIndex: j) + } + else + { + barColor = dataSet.neutralColor ?? dataSet.color(atIndex: j) + } + + context.setStrokeColor(barColor.cgColor) + context.strokeLineSegments(between: _rangePoints) + context.strokeLineSegments(between: _openPoints) + context.strokeLineSegments(between: _closePoints) + } + + let axElement = createAccessibleElement(withIndex: j, + container: dataProvider, + dataSet: dataSet) + { (element) in + element.accessibilityLabel = "\(doesContainMultipleDataSets ? "\(dataSet.label ?? "Dataset")" : "") " + "\(xPos) - \(accessibilityMovementDescription). low: \(low), high: \(high), opening: \(open), closing: \(close)" + element.accessibilityFrame = accessibilityRect + } + + accessibleChartElements.append(axElement) + + } + + // Post this notification to let VoiceOver account for the redrawn frames + accessibilityPostLayoutChangedNotification() + + context.restoreGState() + } + + open override func drawValues(context: CGContext) + { + guard + let dataProvider = dataProvider, + let candleData = dataProvider.candleData + else { return } + + // if values are drawn + if isDrawingValuesAllowed(dataProvider: dataProvider) + { + let dataSets = candleData.dataSets + + let phaseY = animator.phaseY + + var pt = CGPoint() + + for i in 0 ..< dataSets.count + { + guard let + dataSet = dataSets[i] as? IBarLineScatterCandleBubbleChartDataSet, + shouldDrawValues(forDataSet: dataSet) + else { continue } + + let valueFont = dataSet.valueFont + + guard let formatter = dataSet.valueFormatter else { continue } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + let valueToPixelMatrix = trans.valueToPixelMatrix + + let iconsOffset = dataSet.iconsOffset + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + let lineHeight = valueFont.lineHeight + let yOffset: CGFloat = lineHeight + 5.0 + + for j in _xBounds + { + guard let e = dataSet.entryForIndex(j) as? CandleChartDataEntry else { break } + + pt.x = CGFloat(e.x) + pt.y = CGFloat(e.high * phaseY) + pt = pt.applying(valueToPixelMatrix) + + if (!viewPortHandler.isInBoundsRight(pt.x)) + { + break + } + + if (!viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y)) + { + continue + } + + if dataSet.isDrawValuesEnabled + { + ChartUtils.drawText( + context: context, + text: formatter.stringForValue( + e.high, + entry: e, + dataSetIndex: i, + viewPortHandler: viewPortHandler), + point: CGPoint( + x: pt.x, + y: pt.y - yOffset), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: dataSet.valueTextColorAt(j)]) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled + { + ChartUtils.drawImage(context: context, + image: icon, + x: pt.x + iconsOffset.x, + y: pt.y + iconsOffset.y, + size: icon.size) + } + } + } + } + } + + open override func drawExtras(context: CGContext) + { + } + + open override func drawHighlighted(context: CGContext, indices: [Highlight]) + { + guard + let dataProvider = dataProvider, + let candleData = dataProvider.candleData + else { return } + + context.saveGState() + + for high in indices + { + guard + let set = candleData.getDataSetByIndex(high.dataSetIndex) as? ICandleChartDataSet, + set.isHighlightEnabled + else { continue } + + guard let e = set.entryForXValue(high.x, closestToY: high.y) as? CandleChartDataEntry else { continue } + + if !isInBoundsX(entry: e, dataSet: set) + { + continue + } + + let trans = dataProvider.getTransformer(forAxis: set.axisDependency) + + context.setStrokeColor(set.highlightColor.cgColor) + context.setLineWidth(set.highlightLineWidth) + + if set.highlightLineDashLengths != nil + { + context.setLineDash(phase: set.highlightLineDashPhase, lengths: set.highlightLineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + let lowValue = e.low * Double(animator.phaseY) + let highValue = e.high * Double(animator.phaseY) + let y = (lowValue + highValue) / 2.0 + + let pt = trans.pixelForValues(x: e.x, y: y) + + high.setDraw(pt: pt) + + // draw the lines + drawHighlightLines(context: context, point: pt, set: set) + } + + context.restoreGState() + } + + private func createAccessibleElement(withIndex idx: Int, + container: CandleChartDataProvider, + dataSet: ICandleChartDataSet, + modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement { + + let element = NSUIAccessibilityElement(accessibilityContainer: container) + + // The modifier allows changing of traits and frame depending on highlight, rotation, etc + modifier(element) + + return element + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/ChartDataRendererBase.swift b/dydx/Pods/Charts/Source/Charts/Renderers/ChartDataRendererBase.swift new file mode 100644 index 000000000..abbbcb0df --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/ChartDataRendererBase.swift @@ -0,0 +1,102 @@ +// +// DataRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + +@objc(ChartDataRendererBase) +open class DataRenderer: Renderer +{ + /// An array of accessibility elements that are presented to the ChartViewBase accessibility methods. + /// + /// Note that the order of elements in this array determines the order in which they are presented and navigated by + /// Accessibility clients such as VoiceOver. + /// + /// Renderers should ensure that the order of elements makes sense to a client presenting an audio-only interface to a user. + /// Subclasses should populate this array in drawData() or drawDataSet() to make the chart accessible. + @objc final var accessibleChartElements: [NSUIAccessibilityElement] = [] + + @objc public let animator: Animator + + @objc public init(animator: Animator, viewPortHandler: ViewPortHandler) + { + self.animator = animator + + super.init(viewPortHandler: viewPortHandler) + } + + @objc open func drawData(context: CGContext) + { + fatalError("drawData() cannot be called on DataRenderer") + } + + @objc open func drawValues(context: CGContext) + { + fatalError("drawValues() cannot be called on DataRenderer") + } + + @objc open func drawExtras(context: CGContext) + { + fatalError("drawExtras() cannot be called on DataRenderer") + } + + /// Draws all highlight indicators for the values that are currently highlighted. + /// + /// - Parameters: + /// - indices: the highlighted values + @objc open func drawHighlighted(context: CGContext, indices: [Highlight]) + { + fatalError("drawHighlighted() cannot be called on DataRenderer") + } + + /// An opportunity for initializing internal buffers used for rendering with a new size. + /// Since this might do memory allocations, it should only be called if necessary. + @objc open func initBuffers() { } + + @objc open func isDrawingValuesAllowed(dataProvider: ChartDataProvider?) -> Bool + { + guard let data = dataProvider?.data else { return false } + return data.entryCount < Int(CGFloat(dataProvider?.maxVisibleCount ?? 0) * viewPortHandler.scaleX) + } + + /// Creates an ```NSUIAccessibilityElement``` that acts as the first and primary header describing a chart view. + /// + /// - Parameters: + /// - chart: The chartView object being described + /// - data: A non optional data source about the chart + /// - defaultDescription: A simple string describing the type/design of Chart. + /// - Returns: A header ```NSUIAccessibilityElement``` that can be added to accessibleChartElements. + @objc internal func createAccessibleHeader(usingChart chart: ChartViewBase, + andData data: ChartData, + withDefaultDescription defaultDescription: String = "Chart") -> NSUIAccessibilityElement + { + let chartDescriptionText = chart.chartDescription?.text ?? defaultDescription + let dataSetDescriptions = data.dataSets.map { $0.label ?? "" } + let dataSetDescriptionText = dataSetDescriptions.joined(separator: ", ") + let dataSetCount = data.dataSets.count + + let + element = NSUIAccessibilityElement(accessibilityContainer: chart) + element.accessibilityLabel = chartDescriptionText + ". \(dataSetCount) dataset\(dataSetCount == 1 ? "" : "s"). \(dataSetDescriptionText)" + element.accessibilityFrame = chart.bounds + element.isHeader = true + + return element + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/CombinedChartRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/CombinedChartRenderer.swift new file mode 100644 index 000000000..8446c9b63 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/CombinedChartRenderer.swift @@ -0,0 +1,209 @@ +// +// CombinedChartRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class CombinedChartRenderer: DataRenderer +{ + @objc open weak var chart: CombinedChartView? + + /// if set to true, all values are drawn above their bars, instead of below their top + @objc open var drawValueAboveBarEnabled = true + + /// if set to true, a grey area is drawn behind each bar that indicates the maximum value + @objc open var drawBarShadowEnabled = false + + internal var _renderers = [DataRenderer]() + + internal var _drawOrder: [CombinedChartView.DrawOrder] = [.bar, .bubble, .line, .candle, .scatter] + + @objc public init(chart: CombinedChartView, animator: Animator, viewPortHandler: ViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.chart = chart + + createRenderers() + } + + /// Creates the renderers needed for this combined-renderer in the required order. Also takes the DrawOrder into consideration. + internal func createRenderers() + { + _renderers = [DataRenderer]() + + guard let chart = chart else { return } + + for order in drawOrder + { + switch (order) + { + case .bar: + if chart.barData !== nil + { + _renderers.append(BarChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) + } + break + + case .line: + if chart.lineData !== nil + { + _renderers.append(LineChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) + } + break + + case .candle: + if chart.candleData !== nil + { + _renderers.append(CandleStickChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) + } + break + + case .scatter: + if chart.scatterData !== nil + { + _renderers.append(ScatterChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) + } + break + + case .bubble: + if chart.bubbleData !== nil + { + _renderers.append(BubbleChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) + } + break + } + } + + } + + open override func initBuffers() + { + _renderers.forEach { $0.initBuffers() } + } + + open override func drawData(context: CGContext) + { + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + + if + let combinedChart = chart, + let data = combinedChart.data { + // Make the chart header the first element in the accessible elements array + let element = createAccessibleHeader(usingChart: combinedChart, + andData: data, + withDefaultDescription: "Combined Chart") + accessibleChartElements.append(element) + } + + // TODO: Due to the potential complexity of data presented in Combined charts, a more usable way + // for VO accessibility would be to use axis based traversal rather than by dataset. + // Hence, accessibleChartElements is not populated below. (Individual renderers guard against dataSource being their respective views) + _renderers.forEach { $0.drawData(context: context) } + } + + open override func drawValues(context: CGContext) + { + _renderers.forEach { $0.drawValues(context: context) } + } + + open override func drawExtras(context: CGContext) + { + _renderers.forEach { $0.drawExtras(context: context) } + } + + open override func drawHighlighted(context: CGContext, indices: [Highlight]) + { + for renderer in _renderers + { + var data: ChartData? + + if renderer is BarChartRenderer + { + data = (renderer as! BarChartRenderer).dataProvider?.barData + } + else if renderer is LineChartRenderer + { + data = (renderer as! LineChartRenderer).dataProvider?.lineData + } + else if renderer is CandleStickChartRenderer + { + data = (renderer as! CandleStickChartRenderer).dataProvider?.candleData + } + else if renderer is ScatterChartRenderer + { + data = (renderer as! ScatterChartRenderer).dataProvider?.scatterData + } + else if renderer is BubbleChartRenderer + { + data = (renderer as! BubbleChartRenderer).dataProvider?.bubbleData + } + + let dataIndex: Int? = { + guard let data = data else { return nil } + return (chart?.data as? CombinedChartData)? + .allData + .firstIndex(of: data) + }() + + let dataIndices = indices.filter{ $0.dataIndex == dataIndex || $0.dataIndex == -1 } + + renderer.drawHighlighted(context: context, indices: dataIndices) + } + } + + /// - Returns: The sub-renderer object at the specified index. + @objc open func getSubRenderer(index: Int) -> DataRenderer? + { + if index >= _renderers.count || index < 0 + { + return nil + } + else + { + return _renderers[index] + } + } + + /// All sub-renderers. + @objc open var subRenderers: [DataRenderer] + { + get { return _renderers } + set { _renderers = newValue } + } + + // MARK: Accessors + + /// `true` if drawing values above bars is enabled, `false` ifnot + @objc open var isDrawValueAboveBarEnabled: Bool { return drawValueAboveBarEnabled } + + /// `true` if drawing shadows (maxvalue) for each bar is enabled, `false` ifnot + @objc open var isDrawBarShadowEnabled: Bool { return drawBarShadowEnabled } + + /// the order in which the provided data objects should be drawn. + /// The earlier you place them in the provided array, the further they will be in the background. + /// e.g. if you provide [DrawOrder.Bar, DrawOrder.Line], the bars will be drawn behind the lines. + open var drawOrder: [CombinedChartView.DrawOrder] + { + get + { + return _drawOrder + } + set + { + if newValue.count > 0 + { + _drawOrder = newValue + } + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/HorizontalBarChartRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/HorizontalBarChartRenderer.swift new file mode 100644 index 000000000..36f4a6c84 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/HorizontalBarChartRenderer.swift @@ -0,0 +1,632 @@ +// +// HorizontalBarChartRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + +open class HorizontalBarChartRenderer: BarChartRenderer +{ + private class Buffer + { + var rects = [CGRect]() + } + + public override init(dataProvider: BarChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) + { + super.init(dataProvider: dataProvider, animator: animator, viewPortHandler: viewPortHandler) + } + + // [CGRect] per dataset + private var _buffers = [Buffer]() + + open override func initBuffers() + { + if let barData = dataProvider?.barData + { + // Matche buffers count to dataset count + if _buffers.count != barData.dataSetCount + { + while _buffers.count < barData.dataSetCount + { + _buffers.append(Buffer()) + } + while _buffers.count > barData.dataSetCount + { + _buffers.removeLast() + } + } + + for i in stride(from: 0, to: barData.dataSetCount, by: 1) + { + let set = barData.dataSets[i] as! IBarChartDataSet + let size = set.entryCount * (set.isStacked ? set.stackSize : 1) + if _buffers[i].rects.count != size + { + _buffers[i].rects = [CGRect](repeating: CGRect(), count: size) + } + } + } + else + { + _buffers.removeAll() + } + } + + private func prepareBuffer(dataSet: IBarChartDataSet, index: Int) + { + guard let + dataProvider = dataProvider, + let barData = dataProvider.barData + else { return } + + let barWidthHalf = barData.barWidth / 2.0 + + let buffer = _buffers[index] + var bufferIndex = 0 + let containsStacks = dataSet.isStacked + + let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency) + let phaseY = animator.phaseY + var barRect = CGRect() + var x: Double + var y: Double + + for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1) + { + guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue } + + let vals = e.yValues + + x = e.x + y = e.y + + if !containsStacks || vals == nil + { + let bottom = CGFloat(x - barWidthHalf) + let top = CGFloat(x + barWidthHalf) + var right = isInverted + ? (y <= 0.0 ? CGFloat(y) : 0) + : (y >= 0.0 ? CGFloat(y) : 0) + var left = isInverted + ? (y >= 0.0 ? CGFloat(y) : 0) + : (y <= 0.0 ? CGFloat(y) : 0) + + // multiply the height of the rect with the phase + if right > 0 + { + right *= CGFloat(phaseY) + } + else + { + left *= CGFloat(phaseY) + } + + barRect.origin.x = left + barRect.size.width = right - left + barRect.origin.y = top + barRect.size.height = bottom - top + + buffer.rects[bufferIndex] = barRect + bufferIndex += 1 + } + else + { + var posY = 0.0 + var negY = -e.negativeSum + var yStart = 0.0 + + // fill the stack + for k in 0 ..< vals!.count + { + let value = vals![k] + + if value == 0.0 && (posY == 0.0 || negY == 0.0) + { + // Take care of the situation of a 0.0 value, which overlaps a non-zero bar + y = value + yStart = y + } + else if value >= 0.0 + { + y = posY + yStart = posY + value + posY = yStart + } + else + { + y = negY + yStart = negY + abs(value) + negY += abs(value) + } + + let bottom = CGFloat(x - barWidthHalf) + let top = CGFloat(x + barWidthHalf) + var right = isInverted + ? (y <= yStart ? CGFloat(y) : CGFloat(yStart)) + : (y >= yStart ? CGFloat(y) : CGFloat(yStart)) + var left = isInverted + ? (y >= yStart ? CGFloat(y) : CGFloat(yStart)) + : (y <= yStart ? CGFloat(y) : CGFloat(yStart)) + + // multiply the height of the rect with the phase + right *= CGFloat(phaseY) + left *= CGFloat(phaseY) + + barRect.origin.x = left + barRect.size.width = right - left + barRect.origin.y = top + barRect.size.height = bottom - top + + buffer.rects[bufferIndex] = barRect + bufferIndex += 1 + } + } + } + } + + private var _barShadowRectBuffer: CGRect = CGRect() + + open override func drawDataSet(context: CGContext, dataSet: IBarChartDataSet, index: Int) + { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + prepareBuffer(dataSet: dataSet, index: index) + trans.rectValuesToPixel(&_buffers[index].rects) + + let borderWidth = dataSet.barBorderWidth + let borderColor = dataSet.barBorderColor + let drawBorder = borderWidth > 0.0 + + context.saveGState() + + // draw the bar shadow before the values + if dataProvider.isDrawBarShadowEnabled + { + guard let barData = dataProvider.barData else { return } + + let barWidth = barData.barWidth + let barWidthHalf = barWidth / 2.0 + var x: Double = 0.0 + + for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1) + { + guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue } + + x = e.x + + _barShadowRectBuffer.origin.y = CGFloat(x - barWidthHalf) + _barShadowRectBuffer.size.height = CGFloat(barWidth) + + trans.rectValueToPixel(&_barShadowRectBuffer) + + if !viewPortHandler.isInBoundsTop(_barShadowRectBuffer.origin.y + _barShadowRectBuffer.size.height) + { + break + } + + if !viewPortHandler.isInBoundsBottom(_barShadowRectBuffer.origin.y) + { + continue + } + + _barShadowRectBuffer.origin.x = viewPortHandler.contentLeft + _barShadowRectBuffer.size.width = viewPortHandler.contentWidth + + context.setFillColor(dataSet.barShadowColor.cgColor) + context.fill(_barShadowRectBuffer) + } + } + + let buffer = _buffers[index] + + let isSingleColor = dataSet.colors.count == 1 + + if isSingleColor + { + context.setFillColor(dataSet.color(atIndex: 0).cgColor) + } + + // In case the chart is stacked, we need to accomodate individual bars within accessibilityOrdereredElements + let isStacked = dataSet.isStacked + let stackSize = isStacked ? dataSet.stackSize : 1 + + for j in stride(from: 0, to: buffer.rects.count, by: 1) + { + let barRect = buffer.rects[j] + + if (!viewPortHandler.isInBoundsTop(barRect.origin.y + barRect.size.height)) + { + break + } + + if (!viewPortHandler.isInBoundsBottom(barRect.origin.y)) + { + continue + } + + if !isSingleColor + { + // Set the color for the currently drawn value. If the index is out of bounds, reuse colors. + context.setFillColor(dataSet.color(atIndex: j).cgColor) + } + + context.fill(barRect) + + if drawBorder + { + context.setStrokeColor(borderColor.cgColor) + context.setLineWidth(borderWidth) + context.stroke(barRect) + } + + // Create and append the corresponding accessibility element to accessibilityOrderedElements (see BarChartRenderer) + if let chart = dataProvider as? BarChartView + { + let element = createAccessibleElement(withIndex: j, + container: chart, + dataSet: dataSet, + dataSetIndex: index, + stackSize: stackSize) + { (element) in + element.accessibilityFrame = barRect + } + + accessibilityOrderedElements[j/stackSize].append(element) + } + } + + context.restoreGState() + } + + open override func prepareBarHighlight( + x: Double, + y1: Double, + y2: Double, + barWidthHalf: Double, + trans: Transformer, + rect: inout CGRect) + { + let top = x - barWidthHalf + let bottom = x + barWidthHalf + let left = y1 + let right = y2 + + rect.origin.x = CGFloat(left) + rect.origin.y = CGFloat(top) + rect.size.width = CGFloat(right - left) + rect.size.height = CGFloat(bottom - top) + + trans.rectValueToPixelHorizontal(&rect, phaseY: animator.phaseY) + } + + open override func drawValues(context: CGContext) + { + // if values are drawn + if isDrawingValuesAllowed(dataProvider: dataProvider) + { + guard + let dataProvider = dataProvider, + let barData = dataProvider.barData + else { return } + + let dataSets = barData.dataSets + + let textAlign = NSTextAlignment.left + + let valueOffsetPlus: CGFloat = 5.0 + var posOffset: CGFloat + var negOffset: CGFloat + let drawValueAboveBar = dataProvider.isDrawValueAboveBarEnabled + + for dataSetIndex in 0 ..< barData.dataSetCount + { + guard let + dataSet = dataSets[dataSetIndex] as? IBarChartDataSet, + shouldDrawValues(forDataSet: dataSet) + else { continue } + + let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency) + + let valueFont = dataSet.valueFont + let yOffset = -valueFont.lineHeight / 2.0 + + guard let formatter = dataSet.valueFormatter else { continue } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + + let iconsOffset = dataSet.iconsOffset + + let buffer = _buffers[dataSetIndex] + + // if only single values are drawn (sum) + if !dataSet.isStacked + { + for j in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX)) + { + guard let e = dataSet.entryForIndex(j) as? BarChartDataEntry else { continue } + + let rect = buffer.rects[j] + + let y = rect.origin.y + rect.size.height / 2.0 + + if !viewPortHandler.isInBoundsTop(rect.origin.y) + { + break + } + + if !viewPortHandler.isInBoundsX(rect.origin.x) + { + continue + } + + if !viewPortHandler.isInBoundsBottom(rect.origin.y) + { + continue + } + + let val = e.y + let valueText = formatter.stringForValue( + val, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler) + + // calculate the correct offset depending on the draw position of the value + let valueTextWidth = valueText.size(withAttributes: [NSAttributedString.Key.font: valueFont]).width + posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus)) + negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus) - rect.size.width + + if isInverted + { + posOffset = -posOffset - valueTextWidth + negOffset = -negOffset - valueTextWidth + } + + if dataSet.isDrawValuesEnabled + { + drawValue( + context: context, + value: valueText, + xPos: (rect.origin.x + rect.size.width) + + (val >= 0.0 ? posOffset : negOffset), + yPos: y + yOffset, + font: valueFont, + align: textAlign, + color: dataSet.valueTextColorAt(j)) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled + { + var px = (rect.origin.x + rect.size.width) + + (val >= 0.0 ? posOffset : negOffset) + var py = y + + px += iconsOffset.x + py += iconsOffset.y + + ChartUtils.drawImage( + context: context, + image: icon, + x: px, + y: py, + size: icon.size) + } + } + } + else + { + // if each value of a potential stack should be drawn + + var bufferIndex = 0 + + for index in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX)) + { + guard let e = dataSet.entryForIndex(index) as? BarChartDataEntry else { continue } + + let rect = buffer.rects[bufferIndex] + + let vals = e.yValues + + // we still draw stacked bars, but there is one non-stacked in between + if vals == nil + { + if !viewPortHandler.isInBoundsTop(rect.origin.y) + { + break + } + + if !viewPortHandler.isInBoundsX(rect.origin.x) + { + continue + } + + if !viewPortHandler.isInBoundsBottom(rect.origin.y) + { + continue + } + + let val = e.y + let valueText = formatter.stringForValue( + val, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler) + + // calculate the correct offset depending on the draw position of the value + let valueTextWidth = valueText.size(withAttributes: [NSAttributedString.Key.font: valueFont]).width + posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus)) + negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus) + + if isInverted + { + posOffset = -posOffset - valueTextWidth + negOffset = -negOffset - valueTextWidth + } + + if dataSet.isDrawValuesEnabled + { + drawValue( + context: context, + value: valueText, + xPos: (rect.origin.x + rect.size.width) + + (val >= 0.0 ? posOffset : negOffset), + yPos: rect.origin.y + yOffset, + font: valueFont, + align: textAlign, + color: dataSet.valueTextColorAt(index)) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled + { + var px = (rect.origin.x + rect.size.width) + + (val >= 0.0 ? posOffset : negOffset) + var py = rect.origin.y + + px += iconsOffset.x + py += iconsOffset.y + + ChartUtils.drawImage( + context: context, + image: icon, + x: px, + y: py, + size: icon.size) + } + } + else + { + let vals = vals! + var transformed = [CGPoint]() + + var posY = 0.0 + var negY = -e.negativeSum + + for k in 0 ..< vals.count + { + let value = vals[k] + var y: Double + + if value == 0.0 && (posY == 0.0 || negY == 0.0) + { + // Take care of the situation of a 0.0 value, which overlaps a non-zero bar + y = value + } + else if value >= 0.0 + { + posY += value + y = posY + } + else + { + y = negY + negY -= value + } + + transformed.append(CGPoint(x: CGFloat(y * phaseY), y: 0.0)) + } + + trans.pointValuesToPixel(&transformed) + + for k in 0 ..< transformed.count + { + let val = vals[k] + let valueText = formatter.stringForValue( + val, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler) + + // calculate the correct offset depending on the draw position of the value + let valueTextWidth = valueText.size(withAttributes: [NSAttributedString.Key.font: valueFont]).width + posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus)) + negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus) + + if isInverted + { + posOffset = -posOffset - valueTextWidth + negOffset = -negOffset - valueTextWidth + } + + let drawBelow = (val == 0.0 && negY == 0.0 && posY > 0.0) || val < 0.0 + + let x = transformed[k].x + (drawBelow ? negOffset : posOffset) + let y = rect.origin.y + rect.size.height / 2.0 + + if (!viewPortHandler.isInBoundsTop(y)) + { + break + } + + if (!viewPortHandler.isInBoundsX(x)) + { + continue + } + + if (!viewPortHandler.isInBoundsBottom(y)) + { + continue + } + + if dataSet.isDrawValuesEnabled + { + drawValue(context: context, + value: valueText, + xPos: x, + yPos: y + yOffset, + font: valueFont, + align: textAlign, + color: dataSet.valueTextColorAt(index)) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled + { + ChartUtils.drawImage( + context: context, + image: icon, + x: x + iconsOffset.x, + y: y + iconsOffset.y, + size: icon.size) + } + } + } + + bufferIndex = vals == nil ? (bufferIndex + 1) : (bufferIndex + vals!.count) + } + } + } + } + } + + open override func isDrawingValuesAllowed(dataProvider: ChartDataProvider?) -> Bool + { + guard let data = dataProvider?.data + else { return false } + return data.entryCount < Int(CGFloat(dataProvider?.maxVisibleCount ?? 0) * self.viewPortHandler.scaleY) + } + + /// Sets the drawing position of the highlight object based on the riven bar-rect. + internal override func setHighlightDrawPos(highlight high: Highlight, barRect: CGRect) + { + high.setDraw(x: barRect.midY, y: barRect.origin.x + barRect.size.width) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/LegendRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/LegendRenderer.swift new file mode 100755 index 000000000..b9514b4fc --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/LegendRenderer.swift @@ -0,0 +1,578 @@ +// +// LegendRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(ChartLegendRenderer) +open class LegendRenderer: Renderer +{ + /// the legend object this renderer renders + @objc open var legend: Legend? + + @objc public init(viewPortHandler: ViewPortHandler, legend: Legend?) + { + super.init(viewPortHandler: viewPortHandler) + + self.legend = legend + } + + /// Prepares the legend and calculates all needed forms, labels and colors. + @objc open func computeLegend(data: ChartData) + { + guard let legend = legend else { return } + + if !legend.isLegendCustom + { + var entries: [LegendEntry] = [] + + // loop for building up the colors and labels used in the legend + for i in 0.. 0) + { + let labelIndex = j % minEntries + label = sLabels.indices.contains(labelIndex) ? sLabels[labelIndex] : nil + } + else + { + label = nil + } + + entries.append( + LegendEntry( + label: label, + form: dataSet.form, + formSize: dataSet.formSize, + formLineWidth: dataSet.formLineWidth, + formLineDashPhase: dataSet.formLineDashPhase, + formLineDashLengths: dataSet.formLineDashLengths, + formColor: clrs[j] + ) + ) + } + + if dataSet.label != nil + { + // add the legend description label + + entries.append( + LegendEntry( + label: dataSet.label, + form: .none, + formSize: CGFloat.nan, + formLineWidth: CGFloat.nan, + formLineDashPhase: 0.0, + formLineDashLengths: nil, + formColor: nil + ) + ) + } + } + else if dataSet is IPieChartDataSet + { + let pds = dataSet as! IPieChartDataSet + + for j in 0.. 0 + { + context.setLineDash(phase: formLineDashPhase, lengths: formLineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.setStrokeColor(formColor.cgColor) + + _formLineSegmentsBuffer[0].x = x + _formLineSegmentsBuffer[0].y = y + _formLineSegmentsBuffer[1].x = x + formSize + _formLineSegmentsBuffer[1].y = y + context.strokeLineSegments(between: _formLineSegmentsBuffer) + } + } + + /// Draws the provided label at the given position. + @objc open func drawLabel(context: CGContext, x: CGFloat, y: CGFloat, label: String, font: NSUIFont, textColor: NSUIColor) + { + ChartUtils.drawText(context: context, text: label, point: CGPoint(x: x, y: y), align: .left, attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: textColor]) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/LineChartRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/LineChartRenderer.swift new file mode 100644 index 000000000..ce3a25915 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/LineChartRenderer.swift @@ -0,0 +1,792 @@ +// +// LineChartRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class LineChartRenderer: LineRadarRenderer +{ + // TODO: Currently, this nesting isn't necessary for LineCharts. However, it will make it much easier to add a custom rotor + // that navigates between datasets. + // NOTE: Unlike the other renderers, LineChartRenderer populates accessibleChartElements in drawCircles due to the nature of its drawing options. + /// A nested array of elements ordered logically (i.e not in visual/drawing order) for use with VoiceOver. + private lazy var accessibilityOrderedElements: [[NSUIAccessibilityElement]] = accessibilityCreateEmptyOrderedElements() + + @objc open weak var dataProvider: LineChartDataProvider? + + @objc public init(dataProvider: LineChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.dataProvider = dataProvider + } + + open override func drawData(context: CGContext) + { + guard let lineData = dataProvider?.lineData else { return } + + for i in 0 ..< lineData.dataSetCount + { + guard let set = lineData.getDataSetByIndex(i) else { continue } + + if set.isVisible + { + if !(set is ILineChartDataSet) + { + fatalError("Datasets for LineChartRenderer must conform to ILineChartDataSet") + } + + drawDataSet(context: context, dataSet: set as! ILineChartDataSet) + } + } + } + + @objc open func drawDataSet(context: CGContext, dataSet: ILineChartDataSet) + { + if dataSet.entryCount < 1 + { + return + } + + context.saveGState() + + context.setLineWidth(dataSet.lineWidth) + if dataSet.lineDashLengths != nil + { + context.setLineDash(phase: dataSet.lineDashPhase, lengths: dataSet.lineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.setLineCap(dataSet.lineCapType) + + // if drawing cubic lines is enabled + switch dataSet.mode + { + case .linear: fallthrough + case .stepped: + drawLinear(context: context, dataSet: dataSet) + + case .cubicBezier: + drawCubicBezier(context: context, dataSet: dataSet) + + case .horizontalBezier: + drawHorizontalBezier(context: context, dataSet: dataSet) + } + + context.restoreGState() + } + + @objc open func drawCubicBezier(context: CGContext, dataSet: ILineChartDataSet) + { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + // get the color that is specified for this position from the DataSet + let drawingColor = dataSet.colors.first! + + let intensity = dataSet.cubicIntensity + + // the path for the cubic-spline + let cubicPath = CGMutablePath() + + let valueToPixelMatrix = trans.valueToPixelMatrix + + if _xBounds.range >= 1 + { + var prevDx: CGFloat = 0.0 + var prevDy: CGFloat = 0.0 + var curDx: CGFloat = 0.0 + var curDy: CGFloat = 0.0 + + // Take an extra point from the left, and an extra from the right. + // That's because we need 4 points for a cubic bezier (cubic=4), otherwise we get lines moving and doing weird stuff on the edges of the chart. + // So in the starting `prev` and `cur`, go -2, -1 + + let firstIndex = _xBounds.min + 1 + + var prevPrev: ChartDataEntry! = nil + var prev: ChartDataEntry! = dataSet.entryForIndex(max(firstIndex - 2, 0)) + var cur: ChartDataEntry! = dataSet.entryForIndex(max(firstIndex - 1, 0)) + var next: ChartDataEntry! = cur + var nextIndex: Int = -1 + + if cur == nil { return } + + // let the spline start + cubicPath.move(to: CGPoint(x: CGFloat(cur.x), y: CGFloat(cur.y * phaseY)), transform: valueToPixelMatrix) + + for j in _xBounds.dropFirst() // same as firstIndex + { + prevPrev = prev + prev = cur + cur = nextIndex == j ? next : dataSet.entryForIndex(j) + + nextIndex = j + 1 < dataSet.entryCount ? j + 1 : j + next = dataSet.entryForIndex(nextIndex) + + if next == nil { break } + + prevDx = CGFloat(cur.x - prevPrev.x) * intensity + prevDy = CGFloat(cur.y - prevPrev.y) * intensity + curDx = CGFloat(next.x - prev.x) * intensity + curDy = CGFloat(next.y - prev.y) * intensity + + cubicPath.addCurve( + to: CGPoint( + x: CGFloat(cur.x), + y: CGFloat(cur.y) * CGFloat(phaseY)), + control1: CGPoint( + x: CGFloat(prev.x) + prevDx, + y: (CGFloat(prev.y) + prevDy) * CGFloat(phaseY)), + control2: CGPoint( + x: CGFloat(cur.x) - curDx, + y: (CGFloat(cur.y) - curDy) * CGFloat(phaseY)), + transform: valueToPixelMatrix) + } + } + + context.saveGState() + + if dataSet.isDrawFilledEnabled + { + // Copy this path because we make changes to it + let fillPath = cubicPath.mutableCopy() + + drawCubicFill(context: context, dataSet: dataSet, spline: fillPath!, matrix: valueToPixelMatrix, bounds: _xBounds) + } + + context.beginPath() + context.addPath(cubicPath) + context.setStrokeColor(drawingColor.cgColor) + context.strokePath() + + context.restoreGState() + } + + @objc open func drawHorizontalBezier(context: CGContext, dataSet: ILineChartDataSet) + { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + // get the color that is specified for this position from the DataSet + let drawingColor = dataSet.colors.first! + + // the path for the cubic-spline + let cubicPath = CGMutablePath() + + let valueToPixelMatrix = trans.valueToPixelMatrix + + if _xBounds.range >= 1 + { + var prev: ChartDataEntry! = dataSet.entryForIndex(_xBounds.min) + var cur: ChartDataEntry! = prev + + if cur == nil { return } + + // let the spline start + cubicPath.move(to: CGPoint(x: CGFloat(cur.x), y: CGFloat(cur.y * phaseY)), transform: valueToPixelMatrix) + + for j in _xBounds.dropFirst() + { + prev = cur + cur = dataSet.entryForIndex(j) + + let cpx = CGFloat(prev.x + (cur.x - prev.x) / 2.0) + + cubicPath.addCurve( + to: CGPoint( + x: CGFloat(cur.x), + y: CGFloat(cur.y * phaseY)), + control1: CGPoint( + x: cpx, + y: CGFloat(prev.y * phaseY)), + control2: CGPoint( + x: cpx, + y: CGFloat(cur.y * phaseY)), + transform: valueToPixelMatrix) + } + } + + context.saveGState() + + if dataSet.isDrawFilledEnabled + { + // Copy this path because we make changes to it + let fillPath = cubicPath.mutableCopy() + + drawCubicFill(context: context, dataSet: dataSet, spline: fillPath!, matrix: valueToPixelMatrix, bounds: _xBounds) + } + + context.beginPath() + context.addPath(cubicPath) + context.setStrokeColor(drawingColor.cgColor) + context.strokePath() + + context.restoreGState() + } + + open func drawCubicFill( + context: CGContext, + dataSet: ILineChartDataSet, + spline: CGMutablePath, + matrix: CGAffineTransform, + bounds: XBounds) + { + guard + let dataProvider = dataProvider + else { return } + + if bounds.range <= 0 + { + return + } + + let fillMin = dataSet.fillFormatter?.getFillLinePosition(dataSet: dataSet, dataProvider: dataProvider) ?? 0.0 + + var pt1 = CGPoint(x: CGFloat(dataSet.entryForIndex(bounds.min + bounds.range)?.x ?? 0.0), y: fillMin) + var pt2 = CGPoint(x: CGFloat(dataSet.entryForIndex(bounds.min)?.x ?? 0.0), y: fillMin) + pt1 = pt1.applying(matrix) + pt2 = pt2.applying(matrix) + + spline.addLine(to: pt1) + spline.addLine(to: pt2) + spline.closeSubpath() + + if dataSet.fill != nil + { + drawFilledPath(context: context, path: spline, fill: dataSet.fill!, fillAlpha: dataSet.fillAlpha) + } + else + { + drawFilledPath(context: context, path: spline, fillColor: dataSet.fillColor, fillAlpha: dataSet.fillAlpha) + } + } + + private var _lineSegments = [CGPoint](repeating: CGPoint(), count: 2) + + @objc open func drawLinear(context: CGContext, dataSet: ILineChartDataSet) + { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let valueToPixelMatrix = trans.valueToPixelMatrix + + let entryCount = dataSet.entryCount + let isDrawSteppedEnabled = dataSet.mode == .stepped + let pointsPerEntryPair = isDrawSteppedEnabled ? 4 : 2 + + let phaseY = animator.phaseY + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + // if drawing filled is enabled + if dataSet.isDrawFilledEnabled && entryCount > 0 + { + drawLinearFill(context: context, dataSet: dataSet, trans: trans, bounds: _xBounds) + } + + context.saveGState() + + if _lineSegments.count != pointsPerEntryPair + { + // Allocate once in correct size + _lineSegments = [CGPoint](repeating: CGPoint(), count: pointsPerEntryPair) + } + + for j in _xBounds.dropLast() + { + var e: ChartDataEntry! = dataSet.entryForIndex(j) + + if e == nil { continue } + + _lineSegments[0].x = CGFloat(e.x) + _lineSegments[0].y = CGFloat(e.y * phaseY) + + if j < _xBounds.max + { + // TODO: remove the check. + // With the new XBounds iterator, j is always smaller than _xBounds.max + // Keeping this check for a while, if xBounds have no further breaking changes, it should be safe to remove the check + e = dataSet.entryForIndex(j + 1) + + if e == nil { break } + + if isDrawSteppedEnabled + { + _lineSegments[1] = CGPoint(x: CGFloat(e.x), y: _lineSegments[0].y) + _lineSegments[2] = _lineSegments[1] + _lineSegments[3] = CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)) + } + else + { + _lineSegments[1] = CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)) + } + } + else + { + _lineSegments[1] = _lineSegments[0] + } + + for i in 0..<_lineSegments.count + { + _lineSegments[i] = _lineSegments[i].applying(valueToPixelMatrix) + } + + if !viewPortHandler.isInBoundsRight(_lineSegments[0].x) + { + break + } + + // Determine the start and end coordinates of the line, and make sure they differ. + guard + let firstCoordinate = _lineSegments.first, + let lastCoordinate = _lineSegments.last, + firstCoordinate != lastCoordinate else { continue } + + // make sure the lines don't do shitty things outside bounds + if !viewPortHandler.isInBoundsLeft(lastCoordinate.x) || + !viewPortHandler.isInBoundsTop(max(firstCoordinate.y, lastCoordinate.y)) || + !viewPortHandler.isInBoundsBottom(min(firstCoordinate.y, lastCoordinate.y)) + { + continue + } + + // get the color that is set for this line-segment + context.setStrokeColor(dataSet.color(atIndex: j).cgColor) + context.strokeLineSegments(between: _lineSegments) + } + + context.restoreGState() + } + + open func drawLinearFill(context: CGContext, dataSet: ILineChartDataSet, trans: Transformer, bounds: XBounds) + { + guard let dataProvider = dataProvider else { return } + + let filled = generateFilledPath( + dataSet: dataSet, + fillMin: dataSet.fillFormatter?.getFillLinePosition(dataSet: dataSet, dataProvider: dataProvider) ?? 0.0, + bounds: bounds, + matrix: trans.valueToPixelMatrix) + + if dataSet.fill != nil + { + drawFilledPath(context: context, path: filled, fill: dataSet.fill!, fillAlpha: dataSet.fillAlpha) + } + else + { + drawFilledPath(context: context, path: filled, fillColor: dataSet.fillColor, fillAlpha: dataSet.fillAlpha) + } + } + + /// Generates the path that is used for filled drawing. + private func generateFilledPath(dataSet: ILineChartDataSet, fillMin: CGFloat, bounds: XBounds, matrix: CGAffineTransform) -> CGPath + { + let phaseY = animator.phaseY + let isDrawSteppedEnabled = dataSet.mode == .stepped + let matrix = matrix + + var e: ChartDataEntry! + + let filled = CGMutablePath() + + e = dataSet.entryForIndex(bounds.min) + if e != nil + { + filled.move(to: CGPoint(x: CGFloat(e.x), y: fillMin), transform: matrix) + filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)), transform: matrix) + } + + // create a new path + for x in stride(from: (bounds.min + 1), through: bounds.range + bounds.min, by: 1) + { + guard let e = dataSet.entryForIndex(x) else { continue } + + if isDrawSteppedEnabled + { + guard let ePrev = dataSet.entryForIndex(x-1) else { continue } + filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(ePrev.y * phaseY)), transform: matrix) + } + + filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)), transform: matrix) + } + + // close up + e = dataSet.entryForIndex(bounds.range + bounds.min) + if e != nil + { + filled.addLine(to: CGPoint(x: CGFloat(e.x), y: fillMin), transform: matrix) + } + filled.closeSubpath() + + return filled + } + + open override func drawValues(context: CGContext) + { + guard + let dataProvider = dataProvider, + let lineData = dataProvider.lineData + else { return } + + if isDrawingValuesAllowed(dataProvider: dataProvider) + { + let dataSets = lineData.dataSets + + let phaseY = animator.phaseY + + var pt = CGPoint() + + for i in 0 ..< dataSets.count + { + guard let + dataSet = dataSets[i] as? ILineChartDataSet, + shouldDrawValues(forDataSet: dataSet) + else { continue } + + let valueFont = dataSet.valueFont + + guard let formatter = dataSet.valueFormatter else { continue } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + let valueToPixelMatrix = trans.valueToPixelMatrix + + let iconsOffset = dataSet.iconsOffset + + // make sure the values do not interfear with the circles + var valOffset = Int(dataSet.circleRadius * 1.75) + + if !dataSet.isDrawCirclesEnabled + { + valOffset = valOffset / 2 + } + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + for j in _xBounds + { + guard let e = dataSet.entryForIndex(j) else { break } + + pt.x = CGFloat(e.x) + pt.y = CGFloat(e.y * phaseY) + pt = pt.applying(valueToPixelMatrix) + + if (!viewPortHandler.isInBoundsRight(pt.x)) + { + break + } + + if (!viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y)) + { + continue + } + + if dataSet.isDrawValuesEnabled { + ChartUtils.drawText( + context: context, + text: formatter.stringForValue( + e.y, + entry: e, + dataSetIndex: i, + viewPortHandler: viewPortHandler), + point: CGPoint( + x: pt.x, + y: pt.y - CGFloat(valOffset) - valueFont.lineHeight), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: dataSet.valueTextColorAt(j)]) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled + { + ChartUtils.drawImage(context: context, + image: icon, + x: pt.x + iconsOffset.x, + y: pt.y + iconsOffset.y, + size: icon.size) + } + } + } + } + } + + open override func drawExtras(context: CGContext) + { + drawCircles(context: context) + } + + private func drawCircles(context: CGContext) + { + guard + let dataProvider = dataProvider, + let lineData = dataProvider.lineData + else { return } + + let phaseY = animator.phaseY + + let dataSets = lineData.dataSets + + var pt = CGPoint() + var rect = CGRect() + + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + accessibilityOrderedElements = accessibilityCreateEmptyOrderedElements() + + // Make the chart header the first element in the accessible elements array + if let chart = dataProvider as? LineChartView { + let element = createAccessibleHeader(usingChart: chart, + andData: lineData, + withDefaultDescription: "Line Chart") + accessibleChartElements.append(element) + } + + context.saveGState() + + for i in 0 ..< dataSets.count + { + guard let dataSet = lineData.getDataSetByIndex(i) as? ILineChartDataSet else { continue } + + if !dataSet.isVisible || dataSet.entryCount == 0 + { + continue + } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + let valueToPixelMatrix = trans.valueToPixelMatrix + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + let circleRadius = dataSet.circleRadius + let circleDiameter = circleRadius * 2.0 + let circleHoleRadius = dataSet.circleHoleRadius + let circleHoleDiameter = circleHoleRadius * 2.0 + + let drawCircleHole = dataSet.isDrawCircleHoleEnabled && + circleHoleRadius < circleRadius && + circleHoleRadius > 0.0 + let drawTransparentCircleHole = drawCircleHole && + (dataSet.circleHoleColor == nil || + dataSet.circleHoleColor == NSUIColor.clear) + + for j in _xBounds + { + guard let e = dataSet.entryForIndex(j) else { break } + + pt.x = CGFloat(e.x) + pt.y = CGFloat(e.y * phaseY) + pt = pt.applying(valueToPixelMatrix) + + if (!viewPortHandler.isInBoundsRight(pt.x)) + { + break + } + + // make sure the circles don't do shitty things outside bounds + if (!viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y)) + { + continue + } + + + // Skip Circles and Accessibility if not enabled, + // reduces CPU significantly if not needed + if !dataSet.isDrawCirclesEnabled + { + continue + } + + // Accessibility element geometry + let scaleFactor: CGFloat = 3 + let accessibilityRect = CGRect(x: pt.x - (scaleFactor * circleRadius), + y: pt.y - (scaleFactor * circleRadius), + width: scaleFactor * circleDiameter, + height: scaleFactor * circleDiameter) + // Create and append the corresponding accessibility element to accessibilityOrderedElements + if let chart = dataProvider as? LineChartView + { + let element = createAccessibleElement(withIndex: j, + container: chart, + dataSet: dataSet, + dataSetIndex: i) + { (element) in + element.accessibilityFrame = accessibilityRect + } + + accessibilityOrderedElements[i].append(element) + } + + context.setFillColor(dataSet.getCircleColor(atIndex: j)!.cgColor) + + rect.origin.x = pt.x - circleRadius + rect.origin.y = pt.y - circleRadius + rect.size.width = circleDiameter + rect.size.height = circleDiameter + + if drawTransparentCircleHole + { + // Begin path for circle with hole + context.beginPath() + context.addEllipse(in: rect) + + // Cut hole in path + rect.origin.x = pt.x - circleHoleRadius + rect.origin.y = pt.y - circleHoleRadius + rect.size.width = circleHoleDiameter + rect.size.height = circleHoleDiameter + context.addEllipse(in: rect) + + // Fill in-between + context.fillPath(using: .evenOdd) + } + else + { + context.fillEllipse(in: rect) + + if drawCircleHole + { + context.setFillColor(dataSet.circleHoleColor!.cgColor) + + // The hole rect + rect.origin.x = pt.x - circleHoleRadius + rect.origin.y = pt.y - circleHoleRadius + rect.size.width = circleHoleDiameter + rect.size.height = circleHoleDiameter + + context.fillEllipse(in: rect) + } + } + } + } + + context.restoreGState() + + // Merge nested ordered arrays into the single accessibleChartElements. + accessibleChartElements.append(contentsOf: accessibilityOrderedElements.flatMap { $0 } ) + accessibilityPostLayoutChangedNotification() + } + + open override func drawHighlighted(context: CGContext, indices: [Highlight]) + { + guard + let dataProvider = dataProvider, + let lineData = dataProvider.lineData + else { return } + + let chartXMax = dataProvider.chartXMax + + context.saveGState() + + for high in indices + { + guard let set = lineData.getDataSetByIndex(high.dataSetIndex) as? ILineChartDataSet + , set.isHighlightEnabled + else { continue } + + guard let e = set.entryForXValue(high.x, closestToY: high.y) else { continue } + + if !isInBoundsX(entry: e, dataSet: set) + { + continue + } + + context.setStrokeColor(set.highlightColor.cgColor) + context.setLineWidth(set.highlightLineWidth) + if set.highlightLineDashLengths != nil + { + context.setLineDash(phase: set.highlightLineDashPhase, lengths: set.highlightLineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + let x = e.x // get the x-position + let y = e.y * Double(animator.phaseY) + + if x > chartXMax * animator.phaseX + { + continue + } + + let trans = dataProvider.getTransformer(forAxis: set.axisDependency) + + let pt = trans.pixelForValues(x: x, y: y) + + high.setDraw(pt: pt) + + // draw the lines + drawHighlightLines(context: context, point: pt, set: set) + } + + context.restoreGState() + } + + /// Creates a nested array of empty subarrays each of which will be populated with NSUIAccessibilityElements. + /// This is marked internal to support HorizontalBarChartRenderer as well. + private func accessibilityCreateEmptyOrderedElements() -> [[NSUIAccessibilityElement]] + { + guard let chart = dataProvider as? LineChartView else { return [] } + + let dataSetCount = chart.lineData?.dataSetCount ?? 0 + + return Array(repeating: [NSUIAccessibilityElement](), + count: dataSetCount) + } + + /// Creates an NSUIAccessibleElement representing the smallest meaningful bar of the chart + /// i.e. in case of a stacked chart, this returns each stack, not the combined bar. + /// Note that it is marked internal to support subclass modification in the HorizontalBarChart. + private func createAccessibleElement(withIndex idx: Int, + container: LineChartView, + dataSet: ILineChartDataSet, + dataSetIndex: Int, + modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement + { + let element = NSUIAccessibilityElement(accessibilityContainer: container) + let xAxis = container.xAxis + + guard let e = dataSet.entryForIndex(idx) else { return element } + guard let dataProvider = dataProvider else { return element } + + // NOTE: The formatter can cause issues when the x-axis labels are consecutive ints. + // i.e. due to the Double conversion, if there are more than one data set that are grouped, + // there is the possibility of some labels being rounded up. A floor() might fix this, but seems to be a brute force solution. + let label = xAxis.valueFormatter?.stringForValue(e.x, axis: xAxis) ?? "\(e.x)" + + let elementValueText = dataSet.valueFormatter?.stringForValue(e.y, + entry: e, + dataSetIndex: dataSetIndex, + viewPortHandler: viewPortHandler) ?? "\(e.y)" + + let dataSetCount = dataProvider.lineData?.dataSetCount ?? -1 + let doesContainMultipleDataSets = dataSetCount > 1 + + element.accessibilityLabel = "\(doesContainMultipleDataSets ? (dataSet.label ?? "") + ", " : "") \(label): \(elementValueText)" + + modifier(element) + + return element + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/LineRadarRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/LineRadarRenderer.swift new file mode 100644 index 000000000..6b0282505 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/LineRadarRenderer.swift @@ -0,0 +1,54 @@ +// +// LineRadarRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(LineRadarChartRenderer) +open class LineRadarRenderer: LineScatterCandleRadarRenderer +{ + public override init(animator: Animator, viewPortHandler: ViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler) + } + + /// Draws the provided path in filled mode with the provided drawable. + @objc open func drawFilledPath(context: CGContext, path: CGPath, fill: Fill, fillAlpha: CGFloat) + { + + context.saveGState() + context.beginPath() + context.addPath(path) + + // filled is usually drawn with less alpha + context.setAlpha(fillAlpha) + + fill.fillPath(context: context, rect: viewPortHandler.contentRect) + + context.restoreGState() + } + + /// Draws the provided path in filled mode with the provided color and alpha. + @objc open func drawFilledPath(context: CGContext, path: CGPath, fillColor: NSUIColor, fillAlpha: CGFloat) + { + context.saveGState() + context.beginPath() + context.addPath(path) + + // filled is usually drawn with less alpha + context.setAlpha(fillAlpha) + + context.setFillColor(fillColor.cgColor) + context.fillPath() + + context.restoreGState() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/LineScatterCandleRadarRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/LineScatterCandleRadarRenderer.swift new file mode 100644 index 000000000..05203caf9 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/LineScatterCandleRadarRenderer.swift @@ -0,0 +1,49 @@ +// +// LineScatterCandleRadarRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(LineScatterCandleRadarChartRenderer) +open class LineScatterCandleRadarRenderer: BarLineScatterCandleBubbleRenderer +{ + public override init(animator: Animator, viewPortHandler: ViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler) + } + + /// Draws vertical & horizontal highlight-lines if enabled. + /// :param: context + /// :param: points + /// :param: horizontal + /// :param: vertical + @objc open func drawHighlightLines(context: CGContext, point: CGPoint, set: ILineScatterCandleRadarChartDataSet) + { + + // draw vertical highlight lines + if set.isVerticalHighlightIndicatorEnabled + { + context.beginPath() + context.move(to: CGPoint(x: point.x, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: point.x, y: viewPortHandler.contentBottom)) + context.strokePath() + } + + // draw horizontal highlight lines + if set.isHorizontalHighlightIndicatorEnabled + { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: point.y)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: point.y)) + context.strokePath() + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/PieChartRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/PieChartRenderer.swift new file mode 100644 index 000000000..19ea0d460 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/PieChartRenderer.swift @@ -0,0 +1,941 @@ +// +// PieChartRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + +open class PieChartRenderer: DataRenderer +{ + @objc open weak var chart: PieChartView? + + @objc public init(chart: PieChartView, animator: Animator, viewPortHandler: ViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.chart = chart + } + + open override func drawData(context: CGContext) + { + guard let chart = chart else { return } + + let pieData = chart.data + + if pieData != nil + { + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + + for set in pieData!.dataSets as! [IPieChartDataSet] + where set.isVisible && set.entryCount > 0 + { + drawDataSet(context: context, dataSet: set) + } + } + } + + @objc open func calculateMinimumRadiusForSpacedSlice( + center: CGPoint, + radius: CGFloat, + angle: CGFloat, + arcStartPointX: CGFloat, + arcStartPointY: CGFloat, + startAngle: CGFloat, + sweepAngle: CGFloat) -> CGFloat + { + let angleMiddle = startAngle + sweepAngle / 2.0 + + // Other point of the arc + let arcEndPointX = center.x + radius * cos((startAngle + sweepAngle).DEG2RAD) + let arcEndPointY = center.y + radius * sin((startAngle + sweepAngle).DEG2RAD) + + // Middle point on the arc + let arcMidPointX = center.x + radius * cos(angleMiddle.DEG2RAD) + let arcMidPointY = center.y + radius * sin(angleMiddle.DEG2RAD) + + // This is the base of the contained triangle + let basePointsDistance = sqrt( + pow(arcEndPointX - arcStartPointX, 2) + + pow(arcEndPointY - arcStartPointY, 2)) + + // After reducing space from both sides of the "slice", + // the angle of the contained triangle should stay the same. + // So let's find out the height of that triangle. + let containedTriangleHeight = (basePointsDistance / 2.0 * + tan((180.0 - angle).DEG2RAD / 2.0)) + + // Now we subtract that from the radius + var spacedRadius = radius - containedTriangleHeight + + // And now subtract the height of the arc that's between the triangle and the outer circle + spacedRadius -= sqrt( + pow(arcMidPointX - (arcEndPointX + arcStartPointX) / 2.0, 2) + + pow(arcMidPointY - (arcEndPointY + arcStartPointY) / 2.0, 2)) + + return spacedRadius + } + + /// Calculates the sliceSpace to use based on visible values and their size compared to the set sliceSpace. + @objc open func getSliceSpace(dataSet: IPieChartDataSet) -> CGFloat + { + guard + dataSet.automaticallyDisableSliceSpacing, + let data = chart?.data as? PieChartData + else { return dataSet.sliceSpace } + + let spaceSizeRatio = dataSet.sliceSpace / min(viewPortHandler.contentWidth, viewPortHandler.contentHeight) + let minValueRatio = dataSet.yMin / data.yValueSum * 2.0 + + let sliceSpace = spaceSizeRatio > CGFloat(minValueRatio) + ? 0.0 + : dataSet.sliceSpace + + return sliceSpace + } + + @objc open func drawDataSet(context: CGContext, dataSet: IPieChartDataSet) + { + guard let chart = chart else {return } + + var angle: CGFloat = 0.0 + let rotationAngle = chart.rotationAngle + + let phaseX = animator.phaseX + let phaseY = animator.phaseY + + let entryCount = dataSet.entryCount + let drawAngles = chart.drawAngles + let center = chart.centerCircleBox + let radius = chart.radius + let drawInnerArc = chart.drawHoleEnabled && !chart.drawSlicesUnderHoleEnabled + let userInnerRadius = drawInnerArc ? radius * chart.holeRadiusPercent : 0.0 + + var visibleAngleCount = 0 + for j in 0 ..< entryCount + { + guard let e = dataSet.entryForIndex(j) else { continue } + if ((abs(e.y) > Double.ulpOfOne)) + { + visibleAngleCount += 1 + } + } + + let sliceSpace = visibleAngleCount <= 1 ? 0.0 : getSliceSpace(dataSet: dataSet) + + context.saveGState() + + // Make the chart header the first element in the accessible elements array + // We can do this in drawDataSet, since we know PieChartView can have only 1 dataSet + // Also since there's only 1 dataset, we don't use the typical createAccessibleHeader() here. + // NOTE: - Since we want to summarize the total count of slices/portions/elements, use a default string here + // This is unlike when we are naming individual slices, wherein it's alright to not use a prefix as descriptor. + // i.e. We want to VO to say "3 Elements" even if the developer didn't specify an accessibility prefix + // If prefix is unspecified it is safe to assume they did not want to use "Element 1", so that uses a default empty string + let prefix: String = chart.data?.accessibilityEntryLabelPrefix ?? "Element" + let description = chart.chartDescription?.text ?? dataSet.label ?? chart.centerText ?? "Pie Chart" + + let + element = NSUIAccessibilityElement(accessibilityContainer: chart) + element.accessibilityLabel = description + ". \(entryCount) \(prefix + (entryCount == 1 ? "" : "s"))" + element.accessibilityFrame = chart.bounds + element.isHeader = true + accessibleChartElements.append(element) + + for j in 0 ..< entryCount + { + let sliceAngle = drawAngles[j] + var innerRadius = userInnerRadius + + guard let e = dataSet.entryForIndex(j) else { continue } + + defer + { + // From here on, even when skipping (i.e for highlight), + // increase the angle + angle += sliceAngle * CGFloat(phaseX) + } + + // draw only if the value is greater than zero + if abs(e.y) < Double.ulpOfOne { continue } + + // Skip if highlighted + if dataSet.isHighlightEnabled && chart.needsHighlight(index: j) + { + continue + } + + let accountForSliceSpacing = sliceSpace > 0.0 && sliceAngle <= 180.0 + + context.setFillColor(dataSet.color(atIndex: j).cgColor) + + let sliceSpaceAngleOuter = visibleAngleCount == 1 ? + 0.0 : + sliceSpace / radius.DEG2RAD + let startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.0) * CGFloat(phaseY) + var sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * CGFloat(phaseY) + if sweepAngleOuter < 0.0 + { + sweepAngleOuter = 0.0 + } + + let arcStartPointX = center.x + radius * cos(startAngleOuter.DEG2RAD) + let arcStartPointY = center.y + radius * sin(startAngleOuter.DEG2RAD) + + let path = CGMutablePath() + + path.move(to: CGPoint(x: arcStartPointX, + y: arcStartPointY)) + + path.addRelativeArc(center: center, radius: radius, startAngle: startAngleOuter.DEG2RAD, delta: sweepAngleOuter.DEG2RAD) + + if drawInnerArc && + (innerRadius > 0.0 || accountForSliceSpacing) + { + if accountForSliceSpacing + { + var minSpacedRadius = calculateMinimumRadiusForSpacedSlice( + center: center, + radius: radius, + angle: sliceAngle * CGFloat(phaseY), + arcStartPointX: arcStartPointX, + arcStartPointY: arcStartPointY, + startAngle: startAngleOuter, + sweepAngle: sweepAngleOuter) + if minSpacedRadius < 0.0 + { + minSpacedRadius = -minSpacedRadius + } + innerRadius = min(max(innerRadius, minSpacedRadius), radius) + } + + let sliceSpaceAngleInner = visibleAngleCount == 1 || innerRadius == 0.0 ? + 0.0 : + sliceSpace / innerRadius.DEG2RAD + let startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2.0) * CGFloat(phaseY) + var sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * CGFloat(phaseY) + if sweepAngleInner < 0.0 + { + sweepAngleInner = 0.0 + } + let endAngleInner = startAngleInner + sweepAngleInner + + path.addLine( + to: CGPoint( + x: center.x + innerRadius * cos(endAngleInner.DEG2RAD), + y: center.y + innerRadius * sin(endAngleInner.DEG2RAD))) + + path.addRelativeArc(center: center, radius: innerRadius, startAngle: endAngleInner.DEG2RAD, delta: -sweepAngleInner.DEG2RAD) + } + else + { + if accountForSliceSpacing + { + let angleMiddle = startAngleOuter + sweepAngleOuter / 2.0 + + let sliceSpaceOffset = + calculateMinimumRadiusForSpacedSlice( + center: center, + radius: radius, + angle: sliceAngle * CGFloat(phaseY), + arcStartPointX: arcStartPointX, + arcStartPointY: arcStartPointY, + startAngle: startAngleOuter, + sweepAngle: sweepAngleOuter) + + let arcEndPointX = center.x + sliceSpaceOffset * cos(angleMiddle.DEG2RAD) + let arcEndPointY = center.y + sliceSpaceOffset * sin(angleMiddle.DEG2RAD) + + path.addLine( + to: CGPoint( + x: arcEndPointX, + y: arcEndPointY)) + } + else + { + path.addLine(to: center) + } + } + + path.closeSubpath() + + context.beginPath() + context.addPath(path) + context.fillPath(using: .evenOdd) + + let axElement = createAccessibleElement(withIndex: j, + container: chart, + dataSet: dataSet) + { (element) in + element.accessibilityFrame = path.boundingBoxOfPath + } + + accessibleChartElements.append(axElement) + } + + // Post this notification to let VoiceOver account for the redrawn frames + accessibilityPostLayoutChangedNotification() + + context.restoreGState() + } + + open override func drawValues(context: CGContext) + { + guard + let chart = chart, + let data = chart.data + else { return } + + let center = chart.centerCircleBox + + // get whole the radius + let radius = chart.radius + let rotationAngle = chart.rotationAngle + let drawAngles = chart.drawAngles + let absoluteAngles = chart.absoluteAngles + + let phaseX = animator.phaseX + let phaseY = animator.phaseY + + var labelRadiusOffset = radius / 10.0 * 3.0 + + if chart.drawHoleEnabled + { + labelRadiusOffset = (radius - (radius * chart.holeRadiusPercent)) / 2.0 + } + + let labelRadius = radius - labelRadiusOffset + + let dataSets = data.dataSets + + let yValueSum = (data as! PieChartData).yValueSum + + let drawEntryLabels = chart.isDrawEntryLabelsEnabled + let usePercentValuesEnabled = chart.usePercentValuesEnabled + + var angle: CGFloat = 0.0 + var xIndex = 0 + + context.saveGState() + defer { context.restoreGState() } + + for i in 0 ..< dataSets.count + { + guard let dataSet = dataSets[i] as? IPieChartDataSet else { continue } + + let drawValues = dataSet.isDrawValuesEnabled + + if !drawValues && !drawEntryLabels && !dataSet.isDrawIconsEnabled + { + continue + } + + let iconsOffset = dataSet.iconsOffset + + let xValuePosition = dataSet.xValuePosition + let yValuePosition = dataSet.yValuePosition + + let valueFont = dataSet.valueFont + let entryLabelFont = dataSet.entryLabelFont ?? chart.entryLabelFont + let lineHeight = valueFont.lineHeight + + guard let formatter = dataSet.valueFormatter else { continue } + + for j in 0 ..< dataSet.entryCount + { + guard let e = dataSet.entryForIndex(j) else { continue } + let pe = e as? PieChartDataEntry + + if xIndex == 0 + { + angle = 0.0 + } + else + { + angle = absoluteAngles[xIndex - 1] * CGFloat(phaseX) + } + + let sliceAngle = drawAngles[xIndex] + let sliceSpace = getSliceSpace(dataSet: dataSet) + let sliceSpaceMiddleAngle = sliceSpace / labelRadius.DEG2RAD + + // offset needed to center the drawn text in the slice + let angleOffset = (sliceAngle - sliceSpaceMiddleAngle / 2.0) / 2.0 + + angle = angle + angleOffset + + let transformedAngle = rotationAngle + angle * CGFloat(phaseY) + + let value = usePercentValuesEnabled ? e.y / yValueSum * 100.0 : e.y + let valueText = formatter.stringForValue( + value, + entry: e, + dataSetIndex: i, + viewPortHandler: viewPortHandler) + + let sliceXBase = cos(transformedAngle.DEG2RAD) + let sliceYBase = sin(transformedAngle.DEG2RAD) + + let drawXOutside = drawEntryLabels && xValuePosition == .outsideSlice + let drawYOutside = drawValues && yValuePosition == .outsideSlice + let drawXInside = drawEntryLabels && xValuePosition == .insideSlice + let drawYInside = drawValues && yValuePosition == .insideSlice + + let valueTextColor = dataSet.valueTextColorAt(j) + let entryLabelColor = dataSet.entryLabelColor ?? chart.entryLabelColor + + if drawXOutside || drawYOutside + { + let valueLineLength1 = dataSet.valueLinePart1Length + let valueLineLength2 = dataSet.valueLinePart2Length + let valueLinePart1OffsetPercentage = dataSet.valueLinePart1OffsetPercentage + + var pt2: CGPoint + var labelPoint: CGPoint + var align: NSTextAlignment + + var line1Radius: CGFloat + + if chart.drawHoleEnabled + { + line1Radius = (radius - (radius * chart.holeRadiusPercent)) * valueLinePart1OffsetPercentage + (radius * chart.holeRadiusPercent) + } + else + { + line1Radius = radius * valueLinePart1OffsetPercentage + } + + let polyline2Length = dataSet.valueLineVariableLength + ? labelRadius * valueLineLength2 * abs(sin(transformedAngle.DEG2RAD)) + : labelRadius * valueLineLength2 + + let pt0 = CGPoint( + x: line1Radius * sliceXBase + center.x, + y: line1Radius * sliceYBase + center.y) + + let pt1 = CGPoint( + x: labelRadius * (1 + valueLineLength1) * sliceXBase + center.x, + y: labelRadius * (1 + valueLineLength1) * sliceYBase + center.y) + + if transformedAngle.truncatingRemainder(dividingBy: 360.0) >= 90.0 && transformedAngle.truncatingRemainder(dividingBy: 360.0) <= 270.0 + { + pt2 = CGPoint(x: pt1.x - polyline2Length, y: pt1.y) + align = .right + labelPoint = CGPoint(x: pt2.x - 5, y: pt2.y - lineHeight) + } + else + { + pt2 = CGPoint(x: pt1.x + polyline2Length, y: pt1.y) + align = .left + labelPoint = CGPoint(x: pt2.x + 5, y: pt2.y - lineHeight) + } + + DrawLine: do + { + if dataSet.useValueColorForLine + { + context.setStrokeColor(dataSet.color(atIndex: j).cgColor) + } + else if let valueLineColor = dataSet.valueLineColor + { + context.setStrokeColor(valueLineColor.cgColor) + } + else + { + return + } + context.setLineWidth(dataSet.valueLineWidth) + + context.move(to: CGPoint(x: pt0.x, y: pt0.y)) + context.addLine(to: CGPoint(x: pt1.x, y: pt1.y)) + context.addLine(to: CGPoint(x: pt2.x, y: pt2.y)) + + context.drawPath(using: CGPathDrawingMode.stroke) + } + + if drawXOutside && drawYOutside + { + ChartUtils.drawText( + context: context, + text: valueText, + point: labelPoint, + align: align, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: valueTextColor] + ) + + if j < data.entryCount && pe?.label != nil + { + ChartUtils.drawText( + context: context, + text: pe!.label!, + point: CGPoint(x: labelPoint.x, y: labelPoint.y + lineHeight), + align: align, + attributes: [ + NSAttributedString.Key.font: entryLabelFont ?? valueFont, + NSAttributedString.Key.foregroundColor: entryLabelColor ?? valueTextColor] + ) + } + } + else if drawXOutside + { + if j < data.entryCount && pe?.label != nil + { + ChartUtils.drawText( + context: context, + text: pe!.label!, + point: CGPoint(x: labelPoint.x, y: labelPoint.y + lineHeight / 2.0), + align: align, + attributes: [ + NSAttributedString.Key.font: entryLabelFont ?? valueFont, + NSAttributedString.Key.foregroundColor: entryLabelColor ?? valueTextColor] + ) + } + } + else if drawYOutside + { + ChartUtils.drawText( + context: context, + text: valueText, + point: CGPoint(x: labelPoint.x, y: labelPoint.y + lineHeight / 2.0), + align: align, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: valueTextColor] + ) + } + } + + if drawXInside || drawYInside + { + // calculate the text position + let x = labelRadius * sliceXBase + center.x + let y = labelRadius * sliceYBase + center.y - lineHeight + + if drawXInside && drawYInside + { + ChartUtils.drawText( + context: context, + text: valueText, + point: CGPoint(x: x, y: y), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: valueTextColor] + ) + + if j < data.entryCount && pe?.label != nil + { + ChartUtils.drawText( + context: context, + text: pe!.label!, + point: CGPoint(x: x, y: y + lineHeight), + align: .center, + attributes: [ + NSAttributedString.Key.font: entryLabelFont ?? valueFont, + NSAttributedString.Key.foregroundColor: entryLabelColor ?? valueTextColor] + ) + } + } + else if drawXInside + { + if j < data.entryCount && pe?.label != nil + { + ChartUtils.drawText( + context: context, + text: pe!.label!, + point: CGPoint(x: x, y: y + lineHeight / 2.0), + align: .center, + attributes: [ + NSAttributedString.Key.font: entryLabelFont ?? valueFont, + NSAttributedString.Key.foregroundColor: entryLabelColor ?? valueTextColor] + ) + } + } + else if drawYInside + { + ChartUtils.drawText( + context: context, + text: valueText, + point: CGPoint(x: x, y: y + lineHeight / 2.0), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: valueTextColor] + ) + } + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled + { + // calculate the icon's position + + let x = (labelRadius + iconsOffset.y) * sliceXBase + center.x + var y = (labelRadius + iconsOffset.y) * sliceYBase + center.y + y += iconsOffset.x + + ChartUtils.drawImage(context: context, + image: icon, + x: x, + y: y, + size: icon.size) + } + + xIndex += 1 + } + } + } + + open override func drawExtras(context: CGContext) + { + drawHole(context: context) + drawCenterText(context: context) + } + + /// draws the hole in the center of the chart and the transparent circle / hole + private func drawHole(context: CGContext) + { + guard let chart = chart else { return } + + if chart.drawHoleEnabled + { + context.saveGState() + + let radius = chart.radius + let holeRadius = radius * chart.holeRadiusPercent + let center = chart.centerCircleBox + + if let holeColor = chart.holeColor + { + if holeColor != NSUIColor.clear + { + // draw the hole-circle + context.setFillColor(chart.holeColor!.cgColor) + context.fillEllipse(in: CGRect(x: center.x - holeRadius, y: center.y - holeRadius, width: holeRadius * 2.0, height: holeRadius * 2.0)) + } + } + + // only draw the circle if it can be seen (not covered by the hole) + if let transparentCircleColor = chart.transparentCircleColor + { + if transparentCircleColor != NSUIColor.clear && + chart.transparentCircleRadiusPercent > chart.holeRadiusPercent + { + let alpha = animator.phaseX * animator.phaseY + let secondHoleRadius = radius * chart.transparentCircleRadiusPercent + + // make transparent + context.setAlpha(CGFloat(alpha)) + context.setFillColor(transparentCircleColor.cgColor) + + // draw the transparent-circle + context.beginPath() + context.addEllipse(in: CGRect( + x: center.x - secondHoleRadius, + y: center.y - secondHoleRadius, + width: secondHoleRadius * 2.0, + height: secondHoleRadius * 2.0)) + context.addEllipse(in: CGRect( + x: center.x - holeRadius, + y: center.y - holeRadius, + width: holeRadius * 2.0, + height: holeRadius * 2.0)) + context.fillPath(using: .evenOdd) + } + } + + context.restoreGState() + } + } + + /// draws the description text in the center of the pie chart makes most sense when center-hole is enabled + private func drawCenterText(context: CGContext) + { + guard + let chart = chart, + let centerAttributedText = chart.centerAttributedText + else { return } + + if chart.drawCenterTextEnabled && centerAttributedText.length > 0 + { + let center = chart.centerCircleBox + let offset = chart.centerTextOffset + let innerRadius = chart.drawHoleEnabled && !chart.drawSlicesUnderHoleEnabled ? chart.radius * chart.holeRadiusPercent : chart.radius + + let x = center.x + offset.x + let y = center.y + offset.y + + let holeRect = CGRect( + x: x - innerRadius, + y: y - innerRadius, + width: innerRadius * 2.0, + height: innerRadius * 2.0) + var boundingRect = holeRect + + if chart.centerTextRadiusPercent > 0.0 + { + boundingRect = boundingRect.insetBy(dx: (boundingRect.width - boundingRect.width * chart.centerTextRadiusPercent) / 2.0, dy: (boundingRect.height - boundingRect.height * chart.centerTextRadiusPercent) / 2.0) + } + + let textBounds = centerAttributedText.boundingRect(with: boundingRect.size, options: [.usesLineFragmentOrigin, .usesFontLeading, .truncatesLastVisibleLine], context: nil) + + var drawingRect = boundingRect + drawingRect.origin.x += (boundingRect.size.width - textBounds.size.width) / 2.0 + drawingRect.origin.y += (boundingRect.size.height - textBounds.size.height) / 2.0 + drawingRect.size = textBounds.size + + context.saveGState() + + let clippingPath = CGPath(ellipseIn: holeRect, transform: nil) + context.beginPath() + context.addPath(clippingPath) + context.clip() + + centerAttributedText.draw(with: drawingRect, options: [.usesLineFragmentOrigin, .usesFontLeading, .truncatesLastVisibleLine], context: nil) + + context.restoreGState() + } + } + + open override func drawHighlighted(context: CGContext, indices: [Highlight]) + { + guard + let chart = chart, + let data = chart.data + else { return } + + context.saveGState() + + let phaseX = animator.phaseX + let phaseY = animator.phaseY + + var angle: CGFloat = 0.0 + let rotationAngle = chart.rotationAngle + + let drawAngles = chart.drawAngles + let absoluteAngles = chart.absoluteAngles + let center = chart.centerCircleBox + let radius = chart.radius + let drawInnerArc = chart.drawHoleEnabled && !chart.drawSlicesUnderHoleEnabled + let userInnerRadius = drawInnerArc ? radius * chart.holeRadiusPercent : 0.0 + + // Append highlighted accessibility slices into this array, so we can prioritize them over unselected slices + var highlightedAccessibleElements: [NSUIAccessibilityElement] = [] + + for i in 0 ..< indices.count + { + // get the index to highlight + let index = Int(indices[i].x) + if index >= drawAngles.count + { + continue + } + + guard let set = data.getDataSetByIndex(indices[i].dataSetIndex) as? IPieChartDataSet else { continue } + + if !set.isHighlightEnabled { continue } + + let entryCount = set.entryCount + var visibleAngleCount = 0 + for j in 0 ..< entryCount + { + guard let e = set.entryForIndex(j) else { continue } + if ((abs(e.y) > Double.ulpOfOne)) + { + visibleAngleCount += 1 + } + } + + if index == 0 + { + angle = 0.0 + } + else + { + angle = absoluteAngles[index - 1] * CGFloat(phaseX) + } + + let sliceSpace = visibleAngleCount <= 1 ? 0.0 : set.sliceSpace + + let sliceAngle = drawAngles[index] + var innerRadius = userInnerRadius + + let shift = set.selectionShift + let highlightedRadius = radius + shift + + let accountForSliceSpacing = sliceSpace > 0.0 && sliceAngle <= 180.0 + + context.setFillColor(set.highlightColor?.cgColor ?? set.color(atIndex: index).cgColor) + + let sliceSpaceAngleOuter = visibleAngleCount == 1 ? + 0.0 : + sliceSpace / radius.DEG2RAD + + let sliceSpaceAngleShifted = visibleAngleCount == 1 ? + 0.0 : + sliceSpace / highlightedRadius.DEG2RAD + + let startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.0) * CGFloat(phaseY) + var sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * CGFloat(phaseY) + if sweepAngleOuter < 0.0 + { + sweepAngleOuter = 0.0 + } + + let startAngleShifted = rotationAngle + (angle + sliceSpaceAngleShifted / 2.0) * CGFloat(phaseY) + var sweepAngleShifted = (sliceAngle - sliceSpaceAngleShifted) * CGFloat(phaseY) + if sweepAngleShifted < 0.0 + { + sweepAngleShifted = 0.0 + } + + let path = CGMutablePath() + + path.move(to: CGPoint(x: center.x + highlightedRadius * cos(startAngleShifted.DEG2RAD), + y: center.y + highlightedRadius * sin(startAngleShifted.DEG2RAD))) + + path.addRelativeArc(center: center, radius: highlightedRadius, startAngle: startAngleShifted.DEG2RAD, + delta: sweepAngleShifted.DEG2RAD) + + var sliceSpaceRadius: CGFloat = 0.0 + if accountForSliceSpacing + { + sliceSpaceRadius = calculateMinimumRadiusForSpacedSlice( + center: center, + radius: radius, + angle: sliceAngle * CGFloat(phaseY), + arcStartPointX: center.x + radius * cos(startAngleOuter.DEG2RAD), + arcStartPointY: center.y + radius * sin(startAngleOuter.DEG2RAD), + startAngle: startAngleOuter, + sweepAngle: sweepAngleOuter) + } + + if drawInnerArc && + (innerRadius > 0.0 || accountForSliceSpacing) + { + if accountForSliceSpacing + { + var minSpacedRadius = sliceSpaceRadius + if minSpacedRadius < 0.0 + { + minSpacedRadius = -minSpacedRadius + } + innerRadius = min(max(innerRadius, minSpacedRadius), radius) + } + + let sliceSpaceAngleInner = visibleAngleCount == 1 || innerRadius == 0.0 ? + 0.0 : + sliceSpace / innerRadius.DEG2RAD + let startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2.0) * CGFloat(phaseY) + var sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * CGFloat(phaseY) + if sweepAngleInner < 0.0 + { + sweepAngleInner = 0.0 + } + let endAngleInner = startAngleInner + sweepAngleInner + + path.addLine( + to: CGPoint( + x: center.x + innerRadius * cos(endAngleInner.DEG2RAD), + y: center.y + innerRadius * sin(endAngleInner.DEG2RAD))) + + path.addRelativeArc(center: center, radius: innerRadius, + startAngle: endAngleInner.DEG2RAD, + delta: -sweepAngleInner.DEG2RAD) + } + else + { + if accountForSliceSpacing + { + let angleMiddle = startAngleOuter + sweepAngleOuter / 2.0 + + let arcEndPointX = center.x + sliceSpaceRadius * cos(angleMiddle.DEG2RAD) + let arcEndPointY = center.y + sliceSpaceRadius * sin(angleMiddle.DEG2RAD) + + path.addLine( + to: CGPoint( + x: arcEndPointX, + y: arcEndPointY)) + } + else + { + path.addLine(to: center) + } + } + + path.closeSubpath() + + context.beginPath() + context.addPath(path) + context.fillPath(using: .evenOdd) + + let axElement = createAccessibleElement(withIndex: index, + container: chart, + dataSet: set) + { (element) in + element.accessibilityFrame = path.boundingBoxOfPath + element.isSelected = true + } + + highlightedAccessibleElements.append(axElement) + } + + // Prepend selected slices before the already rendered unselected ones. + // NOTE: - This relies on drawDataSet() being called before drawHighlighted in PieChartView. + if !accessibleChartElements.isEmpty { + accessibleChartElements.insert(contentsOf: highlightedAccessibleElements, at: 1) + } + + context.restoreGState() + } + + /// Creates an NSUIAccessibilityElement representing a slice of the PieChart. + /// The element only has it's container and label set based on the chart and dataSet. Use the modifier to alter traits and frame. + private func createAccessibleElement(withIndex idx: Int, + container: PieChartView, + dataSet: IPieChartDataSet, + modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement { + + let element = NSUIAccessibilityElement(accessibilityContainer: container) + + guard let e = dataSet.entryForIndex(idx) else { return element } + guard let formatter = dataSet.valueFormatter else { return element } + guard let data = container.data as? PieChartData else { return element } + + var elementValueText = formatter.stringForValue( + e.y, + entry: e, + dataSetIndex: idx, + viewPortHandler: viewPortHandler) + + if container.usePercentValuesEnabled { + let value = e.y / data.yValueSum * 100.0 + let valueText = formatter.stringForValue( + value, + entry: e, + dataSetIndex: idx, + viewPortHandler: viewPortHandler) + + elementValueText = valueText + } + + let pieChartDataEntry = (dataSet.entryForIndex(idx) as? PieChartDataEntry) + let isCount = data.accessibilityEntryLabelSuffixIsCount + let prefix = data.accessibilityEntryLabelPrefix?.appending("\(idx + 1)") ?? pieChartDataEntry?.label ?? "" + let suffix = data.accessibilityEntryLabelSuffix ?? "" + element.accessibilityLabel = "\(prefix) : \(elementValueText) \(suffix + (isCount ? (e.y == 1.0 ? "" : "s") : "") )" + + // The modifier allows changing of traits and frame depending on highlight, rotation, etc + modifier(element) + + return element + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/RadarChartRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/RadarChartRenderer.swift new file mode 100644 index 000000000..c2ab79fcc --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/RadarChartRenderer.swift @@ -0,0 +1,475 @@ +// +// RadarChartRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class RadarChartRenderer: LineRadarRenderer +{ + private lazy var accessibilityXLabels: [String] = { + guard let chart = chart else { return [] } + guard let formatter = chart.xAxis.valueFormatter else { return [] } + + let maxEntryCount = chart.data?.maxEntryCountSet?.entryCount ?? 0 + return stride(from: 0, to: maxEntryCount, by: 1).map { + formatter.stringForValue(Double($0), axis: chart.xAxis) + } + }() + + @objc open weak var chart: RadarChartView? + + @objc public init(chart: RadarChartView, animator: Animator, viewPortHandler: ViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.chart = chart + } + + open override func drawData(context: CGContext) + { + guard let chart = chart else { return } + + let radarData = chart.data + + if radarData != nil + { + let mostEntries = radarData?.maxEntryCountSet?.entryCount ?? 0 + + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + self.accessibleChartElements.removeAll() + + // Make the chart header the first element in the accessible elements array + if let accessibilityHeaderData = radarData as? RadarChartData { + let element = createAccessibleHeader(usingChart: chart, + andData: accessibilityHeaderData, + withDefaultDescription: "Radar Chart") + self.accessibleChartElements.append(element) + } + + for set in radarData!.dataSets as! [IRadarChartDataSet] where set.isVisible + { + drawDataSet(context: context, dataSet: set, mostEntries: mostEntries) + } + } + } + + /// Draws the RadarDataSet + /// + /// - Parameters: + /// - context: + /// - dataSet: + /// - mostEntries: the entry count of the dataset with the most entries + internal func drawDataSet(context: CGContext, dataSet: IRadarChartDataSet, mostEntries: Int) + { + guard let chart = chart else { return } + + context.saveGState() + + let phaseX = animator.phaseX + let phaseY = animator.phaseY + + let sliceangle = chart.sliceAngle + + // calculate the factor that is needed for transforming the value to pixels + let factor = chart.factor + + let center = chart.centerOffsets + let entryCount = dataSet.entryCount + let path = CGMutablePath() + var hasMovedToPoint = false + + let prefix: String = chart.data?.accessibilityEntryLabelPrefix ?? "Item" + let description = dataSet.label ?? "" + + // Make a tuple of (xLabels, value, originalIndex) then sort it + // This is done, so that the labels are narrated in decreasing order of their corresponding value + // Otherwise, there is no non-visual logic to the data presented + let accessibilityEntryValues = Array(0 ..< entryCount).map { (dataSet.entryForIndex($0)?.y ?? 0, $0) } + let accessibilityAxisLabelValueTuples = zip(accessibilityXLabels, accessibilityEntryValues).map { ($0, $1.0, $1.1) }.sorted { $0.1 > $1.1 } + let accessibilityDataSetDescription: String = description + ". \(entryCount) \(prefix + (entryCount == 1 ? "" : "s")). " + let accessibilityFrameWidth: CGFloat = 22.0 // To allow a tap target of 44x44 + + var accessibilityEntryElements: [NSUIAccessibilityElement] = [] + + for j in 0 ..< entryCount + { + guard let e = dataSet.entryForIndex(j) else { continue } + + let p = center.moving(distance: CGFloat((e.y - chart.chartYMin) * Double(factor) * phaseY), + atAngle: sliceangle * CGFloat(j) * CGFloat(phaseX) + chart.rotationAngle) + + if p.x.isNaN + { + continue + } + + if !hasMovedToPoint + { + path.move(to: p) + hasMovedToPoint = true + } + else + { + path.addLine(to: p) + } + + let accessibilityLabel = accessibilityAxisLabelValueTuples[j].0 + let accessibilityValue = accessibilityAxisLabelValueTuples[j].1 + let accessibilityValueIndex = accessibilityAxisLabelValueTuples[j].2 + + let axp = center.moving(distance: CGFloat((accessibilityValue - chart.chartYMin) * Double(factor) * phaseY), + atAngle: sliceangle * CGFloat(accessibilityValueIndex) * CGFloat(phaseX) + chart.rotationAngle) + + let axDescription = description + " - " + accessibilityLabel + ": \(accessibilityValue) \(chart.data?.accessibilityEntryLabelSuffix ?? "")" + let axElement = createAccessibleElement(withDescription: axDescription, + container: chart, + dataSet: dataSet) + { (element) in + element.accessibilityFrame = CGRect(x: axp.x - accessibilityFrameWidth, + y: axp.y - accessibilityFrameWidth, + width: 2 * accessibilityFrameWidth, + height: 2 * accessibilityFrameWidth) + } + + accessibilityEntryElements.append(axElement) + } + + // if this is the largest set, close it + if dataSet.entryCount < mostEntries + { + // if this is not the largest set, draw a line to the center before closing + path.addLine(to: center) + } + + path.closeSubpath() + + // draw filled + if dataSet.isDrawFilledEnabled + { + if dataSet.fill != nil + { + drawFilledPath(context: context, path: path, fill: dataSet.fill!, fillAlpha: dataSet.fillAlpha) + } + else + { + drawFilledPath(context: context, path: path, fillColor: dataSet.fillColor, fillAlpha: dataSet.fillAlpha) + } + } + + // draw the line (only if filled is disabled or alpha is below 255) + if !dataSet.isDrawFilledEnabled || dataSet.fillAlpha < 1.0 + { + context.setStrokeColor(dataSet.color(atIndex: 0).cgColor) + context.setLineWidth(dataSet.lineWidth) + context.setAlpha(1.0) + + context.beginPath() + context.addPath(path) + context.strokePath() + + let axElement = createAccessibleElement(withDescription: accessibilityDataSetDescription, + container: chart, + dataSet: dataSet) + { (element) in + element.isHeader = true + element.accessibilityFrame = path.boundingBoxOfPath + } + + accessibleChartElements.append(axElement) + accessibleChartElements.append(contentsOf: accessibilityEntryElements) + } + + accessibilityPostLayoutChangedNotification() + + context.restoreGState() + } + + open override func drawValues(context: CGContext) + { + guard + let chart = chart, + let data = chart.data + else { return } + + let phaseX = animator.phaseX + let phaseY = animator.phaseY + + let sliceangle = chart.sliceAngle + + // calculate the factor that is needed for transforming the value to pixels + let factor = chart.factor + + let center = chart.centerOffsets + + let yoffset = CGFloat(5.0) + + for i in 0 ..< data.dataSetCount + { + guard let + dataSet = data.getDataSetByIndex(i) as? IRadarChartDataSet, + shouldDrawValues(forDataSet: dataSet) + else { continue } + + let entryCount = dataSet.entryCount + + let iconsOffset = dataSet.iconsOffset + + for j in 0 ..< entryCount + { + guard let e = dataSet.entryForIndex(j) else { continue } + + let p = center.moving(distance: CGFloat(e.y - chart.chartYMin) * factor * CGFloat(phaseY), + atAngle: sliceangle * CGFloat(j) * CGFloat(phaseX) + chart.rotationAngle) + + let valueFont = dataSet.valueFont + + guard let formatter = dataSet.valueFormatter else { continue } + + if dataSet.isDrawValuesEnabled + { + ChartUtils.drawText( + context: context, + text: formatter.stringForValue( + e.y, + entry: e, + dataSetIndex: i, + viewPortHandler: viewPortHandler), + point: CGPoint(x: p.x, y: p.y - yoffset - valueFont.lineHeight), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, + NSAttributedString.Key.foregroundColor: dataSet.valueTextColorAt(j)] + ) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled + { + var pIcon = center.moving(distance: CGFloat(e.y) * factor * CGFloat(phaseY) + iconsOffset.y, + atAngle: sliceangle * CGFloat(j) * CGFloat(phaseX) + chart.rotationAngle) + pIcon.y += iconsOffset.x + + ChartUtils.drawImage(context: context, + image: icon, + x: pIcon.x, + y: pIcon.y, + size: icon.size) + } + } + } + } + + open override func drawExtras(context: CGContext) + { + drawWeb(context: context) + } + + private var _webLineSegmentsBuffer = [CGPoint](repeating: CGPoint(), count: 2) + + @objc open func drawWeb(context: CGContext) + { + guard + let chart = chart, + let data = chart.data + else { return } + + let sliceangle = chart.sliceAngle + + context.saveGState() + + // calculate the factor that is needed for transforming the value to + // pixels + let factor = chart.factor + let rotationangle = chart.rotationAngle + + let center = chart.centerOffsets + + // draw the web lines that come from the center + context.setLineWidth(chart.webLineWidth) + context.setStrokeColor(chart.webColor.cgColor) + context.setAlpha(chart.webAlpha) + + let xIncrements = 1 + chart.skipWebLineCount + let maxEntryCount = chart.data?.maxEntryCountSet?.entryCount ?? 0 + + for i in stride(from: 0, to: maxEntryCount, by: xIncrements) + { + let p = center.moving(distance: CGFloat(chart.yRange) * factor, + atAngle: sliceangle * CGFloat(i) + rotationangle) + + _webLineSegmentsBuffer[0].x = center.x + _webLineSegmentsBuffer[0].y = center.y + _webLineSegmentsBuffer[1].x = p.x + _webLineSegmentsBuffer[1].y = p.y + + context.strokeLineSegments(between: _webLineSegmentsBuffer) + } + + // draw the inner-web + context.setLineWidth(chart.innerWebLineWidth) + context.setStrokeColor(chart.innerWebColor.cgColor) + context.setAlpha(chart.webAlpha) + + let labelCount = chart.yAxis.entryCount + + for j in 0 ..< labelCount + { + for i in 0 ..< data.entryCount + { + let r = CGFloat(chart.yAxis.entries[j] - chart.chartYMin) * factor + + let p1 = center.moving(distance: r, atAngle: sliceangle * CGFloat(i) + rotationangle) + let p2 = center.moving(distance: r, atAngle: sliceangle * CGFloat(i + 1) + rotationangle) + + _webLineSegmentsBuffer[0].x = p1.x + _webLineSegmentsBuffer[0].y = p1.y + _webLineSegmentsBuffer[1].x = p2.x + _webLineSegmentsBuffer[1].y = p2.y + + context.strokeLineSegments(between: _webLineSegmentsBuffer) + } + } + + context.restoreGState() + } + + private var _highlightPointBuffer = CGPoint() + + open override func drawHighlighted(context: CGContext, indices: [Highlight]) + { + guard + let chart = chart, + let radarData = chart.data as? RadarChartData + else { return } + + context.saveGState() + + let sliceangle = chart.sliceAngle + + // calculate the factor that is needed for transforming the value pixels + let factor = chart.factor + + let center = chart.centerOffsets + + for high in indices + { + guard + let set = chart.data?.getDataSetByIndex(high.dataSetIndex) as? IRadarChartDataSet, + set.isHighlightEnabled + else { continue } + + guard let e = set.entryForIndex(Int(high.x)) as? RadarChartDataEntry + else { continue } + + if !isInBoundsX(entry: e, dataSet: set) + { + continue + } + + context.setLineWidth(radarData.highlightLineWidth) + if radarData.highlightLineDashLengths != nil + { + context.setLineDash(phase: radarData.highlightLineDashPhase, lengths: radarData.highlightLineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.setStrokeColor(set.highlightColor.cgColor) + + let y = e.y - chart.chartYMin + + _highlightPointBuffer = center.moving(distance: CGFloat(y) * factor * CGFloat(animator.phaseY), + atAngle: sliceangle * CGFloat(high.x) * CGFloat(animator.phaseX) + chart.rotationAngle) + + high.setDraw(pt: _highlightPointBuffer) + + // draw the lines + drawHighlightLines(context: context, point: _highlightPointBuffer, set: set) + + if set.isDrawHighlightCircleEnabled + { + if !_highlightPointBuffer.x.isNaN && !_highlightPointBuffer.y.isNaN + { + var strokeColor = set.highlightCircleStrokeColor + if strokeColor == nil + { + strokeColor = set.color(atIndex: 0) + } + if set.highlightCircleStrokeAlpha < 1.0 + { + strokeColor = strokeColor?.withAlphaComponent(set.highlightCircleStrokeAlpha) + } + + drawHighlightCircle( + context: context, + atPoint: _highlightPointBuffer, + innerRadius: set.highlightCircleInnerRadius, + outerRadius: set.highlightCircleOuterRadius, + fillColor: set.highlightCircleFillColor, + strokeColor: strokeColor, + strokeWidth: set.highlightCircleStrokeWidth) + } + } + } + + context.restoreGState() + } + + internal func drawHighlightCircle( + context: CGContext, + atPoint point: CGPoint, + innerRadius: CGFloat, + outerRadius: CGFloat, + fillColor: NSUIColor?, + strokeColor: NSUIColor?, + strokeWidth: CGFloat) + { + context.saveGState() + + if let fillColor = fillColor + { + context.beginPath() + context.addEllipse(in: CGRect(x: point.x - outerRadius, y: point.y - outerRadius, width: outerRadius * 2.0, height: outerRadius * 2.0)) + if innerRadius > 0.0 + { + context.addEllipse(in: CGRect(x: point.x - innerRadius, y: point.y - innerRadius, width: innerRadius * 2.0, height: innerRadius * 2.0)) + } + + context.setFillColor(fillColor.cgColor) + context.fillPath(using: .evenOdd) + } + + if let strokeColor = strokeColor + { + context.beginPath() + context.addEllipse(in: CGRect(x: point.x - outerRadius, y: point.y - outerRadius, width: outerRadius * 2.0, height: outerRadius * 2.0)) + context.setStrokeColor(strokeColor.cgColor) + context.setLineWidth(strokeWidth) + context.strokePath() + } + + context.restoreGState() + } + + private func createAccessibleElement(withDescription description: String, + container: RadarChartView, + dataSet: IRadarChartDataSet, + modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement { + + let element = NSUIAccessibilityElement(accessibilityContainer: container) + element.accessibilityLabel = description + + // The modifier allows changing of traits and frame depending on highlight, rotation, etc + modifier(element) + + return element + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/Renderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/Renderer.swift new file mode 100644 index 000000000..e57a9b853 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/Renderer.swift @@ -0,0 +1,26 @@ +// +// Renderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(ChartRenderer) +open class Renderer: NSObject +{ + /// the component that handles the drawing area of the chart and it's offsets + @objc public let viewPortHandler: ViewPortHandler + + @objc public init(viewPortHandler: ViewPortHandler) + { + self.viewPortHandler = viewPortHandler + super.init() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/ChevronDownShapeRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/ChevronDownShapeRenderer.swift new file mode 100644 index 000000000..2d035bf35 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/ChevronDownShapeRenderer.swift @@ -0,0 +1,36 @@ +// +// ChevronDownShapeRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// +import Foundation +import CoreGraphics + +open class ChevronDownShapeRenderer : NSObject, IShapeRenderer +{ + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler: ViewPortHandler, + point: CGPoint, + color: NSUIColor) + { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + + context.setLineWidth(1.0) + context.setStrokeColor(color.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: point.x, y: point.y + 2 * shapeHalf)) + context.addLine(to: CGPoint(x: point.x + 2 * shapeHalf, y: point.y)) + context.move(to: CGPoint(x: point.x, y: point.y + 2 * shapeHalf)) + context.addLine(to: CGPoint(x: point.x - 2 * shapeHalf, y: point.y)) + context.strokePath() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/ChevronUpShapeRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/ChevronUpShapeRenderer.swift new file mode 100644 index 000000000..725533e78 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/ChevronUpShapeRenderer.swift @@ -0,0 +1,36 @@ +// +// ChevronUpShapeRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// +import Foundation +import CoreGraphics + +open class ChevronUpShapeRenderer : NSObject, IShapeRenderer +{ + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler: ViewPortHandler, + point: CGPoint, + color: NSUIColor) + { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + + context.setLineWidth(1.0) + context.setStrokeColor(color.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: point.x, y: point.y - 2 * shapeHalf)) + context.addLine(to: CGPoint(x: point.x + 2 * shapeHalf, y: point.y)) + context.move(to: CGPoint(x: point.x, y: point.y - 2 * shapeHalf)) + context.addLine(to: CGPoint(x: point.x - 2 * shapeHalf, y: point.y)) + context.strokePath() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/CircleShapeRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/CircleShapeRenderer.swift new file mode 100644 index 000000000..b94a51137 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/CircleShapeRenderer.swift @@ -0,0 +1,63 @@ +// +// CircleShapeRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// +import Foundation +import CoreGraphics + +open class CircleShapeRenderer : NSObject, IShapeRenderer +{ + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler: ViewPortHandler, + point: CGPoint, + color: NSUIColor) + { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + let shapeHoleSizeHalf = dataSet.scatterShapeHoleRadius + let shapeHoleSize = shapeHoleSizeHalf * 2.0 + let shapeHoleColor = dataSet.scatterShapeHoleColor + let shapeStrokeSize = (shapeSize - shapeHoleSize) / 2.0 + let shapeStrokeSizeHalf = shapeStrokeSize / 2.0 + + if shapeHoleSize > 0.0 + { + context.setStrokeColor(color.cgColor) + context.setLineWidth(shapeStrokeSize) + var rect = CGRect() + rect.origin.x = point.x - shapeHoleSizeHalf - shapeStrokeSizeHalf + rect.origin.y = point.y - shapeHoleSizeHalf - shapeStrokeSizeHalf + rect.size.width = shapeHoleSize + shapeStrokeSize + rect.size.height = shapeHoleSize + shapeStrokeSize + context.strokeEllipse(in: rect) + + if let shapeHoleColor = shapeHoleColor + { + context.setFillColor(shapeHoleColor.cgColor) + rect.origin.x = point.x - shapeHoleSizeHalf + rect.origin.y = point.y - shapeHoleSizeHalf + rect.size.width = shapeHoleSize + rect.size.height = shapeHoleSize + context.fillEllipse(in: rect) + } + } + else + { + context.setFillColor(color.cgColor) + var rect = CGRect() + rect.origin.x = point.x - shapeHalf + rect.origin.y = point.y - shapeHalf + rect.size.width = shapeSize + rect.size.height = shapeSize + context.fillEllipse(in: rect) + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/CrossShapeRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/CrossShapeRenderer.swift new file mode 100644 index 000000000..18785d3de --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/CrossShapeRenderer.swift @@ -0,0 +1,36 @@ +// +// CrossShapeRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// +import Foundation +import CoreGraphics + +open class CrossShapeRenderer : NSObject, IShapeRenderer +{ + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler: ViewPortHandler, + point: CGPoint, + color: NSUIColor) + { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + + context.setLineWidth(1.0) + context.setStrokeColor(color.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: point.x - shapeHalf, y: point.y)) + context.addLine(to: CGPoint(x: point.x + shapeHalf, y: point.y)) + context.move(to: CGPoint(x: point.x, y: point.y - shapeHalf)) + context.addLine(to: CGPoint(x: point.x, y: point.y + shapeHalf)) + context.strokePath() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/IShapeRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/IShapeRenderer.swift new file mode 100644 index 000000000..ff12ee3fb --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/IShapeRenderer.swift @@ -0,0 +1,32 @@ +// +// IShapeRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc +public protocol IShapeRenderer: class +{ + /// Renders the provided ScatterDataSet with a shape. + /// + /// - Parameters: + /// - context: CGContext for drawing on + /// - dataSet: The DataSet to be drawn + /// - viewPortHandler: Contains information about the current state of the view + /// - point: Position to draw the shape at + /// - color: Color to draw the shape + func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler: ViewPortHandler, + point: CGPoint, + color: NSUIColor) +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/SquareShapeRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/SquareShapeRenderer.swift new file mode 100644 index 000000000..ea692e33f --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/SquareShapeRenderer.swift @@ -0,0 +1,63 @@ +// +// SquareShapeRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// +import Foundation +import CoreGraphics + +open class SquareShapeRenderer : NSObject, IShapeRenderer +{ + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler: ViewPortHandler, + point: CGPoint, + color: NSUIColor) + { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + let shapeHoleSizeHalf = dataSet.scatterShapeHoleRadius + let shapeHoleSize = shapeHoleSizeHalf * 2.0 + let shapeHoleColor = dataSet.scatterShapeHoleColor + let shapeStrokeSize = (shapeSize - shapeHoleSize) / 2.0 + let shapeStrokeSizeHalf = shapeStrokeSize / 2.0 + + if shapeHoleSize > 0.0 + { + context.setStrokeColor(color.cgColor) + context.setLineWidth(shapeStrokeSize) + var rect = CGRect() + rect.origin.x = point.x - shapeHoleSizeHalf - shapeStrokeSizeHalf + rect.origin.y = point.y - shapeHoleSizeHalf - shapeStrokeSizeHalf + rect.size.width = shapeHoleSize + shapeStrokeSize + rect.size.height = shapeHoleSize + shapeStrokeSize + context.stroke(rect) + + if let shapeHoleColor = shapeHoleColor + { + context.setFillColor(shapeHoleColor.cgColor) + rect.origin.x = point.x - shapeHoleSizeHalf + rect.origin.y = point.y - shapeHoleSizeHalf + rect.size.width = shapeHoleSize + rect.size.height = shapeHoleSize + context.fill(rect) + } + } + else + { + context.setFillColor(color.cgColor) + var rect = CGRect() + rect.origin.x = point.x - shapeHalf + rect.origin.y = point.y - shapeHalf + rect.size.width = shapeSize + rect.size.height = shapeSize + context.fill(rect) + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/TriangleShapeRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/TriangleShapeRenderer.swift new file mode 100644 index 000000000..fa313b765 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/TriangleShapeRenderer.swift @@ -0,0 +1,66 @@ +// +// TriangleShapeRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// +import Foundation +import CoreGraphics + +open class TriangleShapeRenderer : NSObject, IShapeRenderer +{ + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler: ViewPortHandler, + point: CGPoint, + color: NSUIColor) + { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + let shapeHoleSizeHalf = dataSet.scatterShapeHoleRadius + let shapeHoleSize = shapeHoleSizeHalf * 2.0 + let shapeHoleColor = dataSet.scatterShapeHoleColor + let shapeStrokeSize = (shapeSize - shapeHoleSize) / 2.0 + + context.setFillColor(color.cgColor) + + // create a triangle path + context.beginPath() + context.move(to: CGPoint(x: point.x, y: point.y - shapeHalf)) + context.addLine(to: CGPoint(x: point.x + shapeHalf, y: point.y + shapeHalf)) + context.addLine(to: CGPoint(x: point.x - shapeHalf, y: point.y + shapeHalf)) + + if shapeHoleSize > 0.0 + { + context.addLine(to: CGPoint(x: point.x, y: point.y - shapeHalf)) + + context.move(to: CGPoint(x: point.x - shapeHalf + shapeStrokeSize, y: point.y + shapeHalf - shapeStrokeSize)) + context.addLine(to: CGPoint(x: point.x + shapeHalf - shapeStrokeSize, y: point.y + shapeHalf - shapeStrokeSize)) + context.addLine(to: CGPoint(x: point.x, y: point.y - shapeHalf + shapeStrokeSize)) + context.addLine(to: CGPoint(x: point.x - shapeHalf + shapeStrokeSize, y: point.y + shapeHalf - shapeStrokeSize)) + } + + context.closePath() + + context.fillPath() + + if shapeHoleSize > 0.0 && shapeHoleColor != nil + { + context.setFillColor(shapeHoleColor!.cgColor) + + // create a triangle path + context.beginPath() + context.move(to: CGPoint(x: point.x, y: point.y - shapeHalf + shapeStrokeSize)) + context.addLine(to: CGPoint(x: point.x + shapeHalf - shapeStrokeSize, y: point.y + shapeHalf - shapeStrokeSize)) + context.addLine(to: CGPoint(x: point.x - shapeHalf + shapeStrokeSize, y: point.y + shapeHalf - shapeStrokeSize)) + context.closePath() + + context.fillPath() + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/XShapeRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/XShapeRenderer.swift new file mode 100644 index 000000000..8a546c5f0 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/Scatter/XShapeRenderer.swift @@ -0,0 +1,36 @@ +// +// XShapeRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// +import Foundation +import CoreGraphics + +open class XShapeRenderer : NSObject, IShapeRenderer +{ + open func renderShape( + context: CGContext, + dataSet: IScatterChartDataSet, + viewPortHandler: ViewPortHandler, + point: CGPoint, + color: NSUIColor) + { + let shapeSize = dataSet.scatterShapeSize + let shapeHalf = shapeSize / 2.0 + + context.setLineWidth(1.0) + context.setStrokeColor(color.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: point.x - shapeHalf, y: point.y - shapeHalf)) + context.addLine(to: CGPoint(x: point.x + shapeHalf, y: point.y + shapeHalf)) + context.move(to: CGPoint(x: point.x + shapeHalf, y: point.y - shapeHalf)) + context.addLine(to: CGPoint(x: point.x - shapeHalf, y: point.y + shapeHalf)) + context.strokePath() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/ScatterChartRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/ScatterChartRenderer.swift new file mode 100644 index 000000000..57d348b28 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/ScatterChartRenderer.swift @@ -0,0 +1,249 @@ +// +// ScatterChartRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class ScatterChartRenderer: LineScatterCandleRadarRenderer +{ + @objc open weak var dataProvider: ScatterChartDataProvider? + + @objc public init(dataProvider: ScatterChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler) + + self.dataProvider = dataProvider + } + + open override func drawData(context: CGContext) + { + guard let scatterData = dataProvider?.scatterData else { return } + + // If we redraw the data, remove and repopulate accessible elements to update label values and frames + accessibleChartElements.removeAll() + + if let chart = dataProvider as? ScatterChartView { + // Make the chart header the first element in the accessible elements array + let element = createAccessibleHeader(usingChart: chart, + andData: scatterData, + withDefaultDescription: "Scatter Chart") + accessibleChartElements.append(element) + } + + // TODO: Due to the potential complexity of data presented in Scatter charts, a more usable way + // for VO accessibility would be to use axis based traversal rather than by dataset. + // Hence, accessibleChartElements is not populated below. (Individual renderers guard against dataSource being their respective views) + for i in 0 ..< scatterData.dataSetCount + { + guard let set = scatterData.getDataSetByIndex(i) else { continue } + + if set.isVisible + { + if !(set is IScatterChartDataSet) + { + fatalError("Datasets for ScatterChartRenderer must conform to IScatterChartDataSet") + } + + drawDataSet(context: context, dataSet: set as! IScatterChartDataSet) + } + } + } + + private var _lineSegments = [CGPoint](repeating: CGPoint(), count: 2) + + @objc open func drawDataSet(context: CGContext, dataSet: IScatterChartDataSet) + { + guard let dataProvider = dataProvider else { return } + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + + let phaseY = animator.phaseY + + let entryCount = dataSet.entryCount + + var point = CGPoint() + + let valueToPixelMatrix = trans.valueToPixelMatrix + + if let renderer = dataSet.shapeRenderer + { + context.saveGState() + + for j in 0 ..< Int(min(ceil(Double(entryCount) * animator.phaseX), Double(entryCount))) + { + guard let e = dataSet.entryForIndex(j) else { continue } + + point.x = CGFloat(e.x) + point.y = CGFloat(e.y * phaseY) + point = point.applying(valueToPixelMatrix) + + if !viewPortHandler.isInBoundsRight(point.x) + { + break + } + + if !viewPortHandler.isInBoundsLeft(point.x) || + !viewPortHandler.isInBoundsY(point.y) + { + continue + } + + renderer.renderShape(context: context, dataSet: dataSet, viewPortHandler: viewPortHandler, point: point, color: dataSet.color(atIndex: j)) + } + + context.restoreGState() + } + else + { + print("There's no IShapeRenderer specified for ScatterDataSet", terminator: "\n") + } + } + + open override func drawValues(context: CGContext) + { + guard + let dataProvider = dataProvider, + let scatterData = dataProvider.scatterData + else { return } + + // if values are drawn + if isDrawingValuesAllowed(dataProvider: dataProvider) + { + guard let dataSets = scatterData.dataSets as? [IScatterChartDataSet] else { return } + + let phaseY = animator.phaseY + + var pt = CGPoint() + + for i in 0 ..< scatterData.dataSetCount + { + let dataSet = dataSets[i] + guard let + formatter = dataSet.valueFormatter, + shouldDrawValues(forDataSet: dataSet) + else { continue } + + let valueFont = dataSet.valueFont + + let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) + let valueToPixelMatrix = trans.valueToPixelMatrix + + let iconsOffset = dataSet.iconsOffset + + let shapeSize = dataSet.scatterShapeSize + let lineHeight = valueFont.lineHeight + + _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) + + for j in _xBounds + { + guard let e = dataSet.entryForIndex(j) else { break } + + pt.x = CGFloat(e.x) + pt.y = CGFloat(e.y * phaseY) + pt = pt.applying(valueToPixelMatrix) + + if (!viewPortHandler.isInBoundsRight(pt.x)) + { + break + } + + // make sure the lines don't do shitty things outside bounds + if (!viewPortHandler.isInBoundsLeft(pt.x) + || !viewPortHandler.isInBoundsY(pt.y)) + { + continue + } + + let text = formatter.stringForValue( + e.y, + entry: e, + dataSetIndex: i, + viewPortHandler: viewPortHandler) + + if dataSet.isDrawValuesEnabled + { + ChartUtils.drawText( + context: context, + text: text, + point: CGPoint( + x: pt.x, + y: pt.y - shapeSize - lineHeight), + align: .center, + attributes: [NSAttributedString.Key.font: valueFont, NSAttributedString.Key.foregroundColor: dataSet.valueTextColorAt(j)] + ) + } + + if let icon = e.icon, dataSet.isDrawIconsEnabled + { + ChartUtils.drawImage(context: context, + image: icon, + x: pt.x + iconsOffset.x, + y: pt.y + iconsOffset.y, + size: icon.size) + } + } + } + } + } + + open override func drawExtras(context: CGContext) + { + + } + + open override func drawHighlighted(context: CGContext, indices: [Highlight]) + { + guard + let dataProvider = dataProvider, + let scatterData = dataProvider.scatterData + else { return } + + context.saveGState() + + for high in indices + { + guard + let set = scatterData.getDataSetByIndex(high.dataSetIndex) as? IScatterChartDataSet, + set.isHighlightEnabled + else { continue } + + guard let entry = set.entryForXValue(high.x, closestToY: high.y) else { continue } + + if !isInBoundsX(entry: entry, dataSet: set) { continue } + + context.setStrokeColor(set.highlightColor.cgColor) + context.setLineWidth(set.highlightLineWidth) + if set.highlightLineDashLengths != nil + { + context.setLineDash(phase: set.highlightLineDashPhase, lengths: set.highlightLineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + let x = entry.x // get the x-position + let y = entry.y * Double(animator.phaseY) + + let trans = dataProvider.getTransformer(forAxis: set.axisDependency) + + let pt = trans.pixelForValues(x: x, y: y) + + high.setDraw(pt: pt) + + // draw the lines + drawHighlightLines(context: context, point: pt, set: set) + } + + context.restoreGState() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/XAxisRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/XAxisRenderer.swift new file mode 100644 index 000000000..2cb80bc2d --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/XAxisRenderer.swift @@ -0,0 +1,448 @@ +// +// XAxisRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + +@objc(ChartXAxisRenderer) +open class XAxisRenderer: AxisRendererBase +{ + @objc public init(viewPortHandler: ViewPortHandler, xAxis: XAxis?, transformer: Transformer?) + { + super.init(viewPortHandler: viewPortHandler, transformer: transformer, axis: xAxis) + } + + open override func computeAxis(min: Double, max: Double, inverted: Bool) + { + var min = min, max = max + + if let transformer = self.transformer + { + // calculate the starting and entry point of the y-labels (depending on + // zoom / contentrect bounds) + if viewPortHandler.contentWidth > 10 && !viewPortHandler.isFullyZoomedOutX + { + let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) + + if inverted + { + min = Double(p2.x) + max = Double(p1.x) + } + else + { + min = Double(p1.x) + max = Double(p2.x) + } + } + } + + computeAxisValues(min: min, max: max) + } + + open override func computeAxisValues(min: Double, max: Double) + { + super.computeAxisValues(min: min, max: max) + + computeSize() + } + + @objc open func computeSize() + { + guard let + xAxis = self.axis as? XAxis + else { return } + + let longest = xAxis.getLongestLabel() + + let labelSize = longest.size(withAttributes: [NSAttributedString.Key.font: xAxis.labelFont]) + + let labelWidth = labelSize.width + let labelHeight = labelSize.height + + let labelRotatedSize = labelSize.rotatedBy(degrees: xAxis.labelRotationAngle) + + xAxis.labelWidth = labelWidth + xAxis.labelHeight = labelHeight + xAxis.labelRotatedWidth = labelRotatedSize.width + xAxis.labelRotatedHeight = labelRotatedSize.height + } + + open override func renderAxisLabels(context: CGContext) + { + guard let xAxis = self.axis as? XAxis else { return } + + if !xAxis.isEnabled || !xAxis.isDrawLabelsEnabled + { + return + } + + let yOffset = xAxis.yOffset + + if xAxis.labelPosition == .top + { + drawLabels(context: context, pos: viewPortHandler.contentTop - yOffset, anchor: CGPoint(x: 0.5, y: 1.0)) + } + else if xAxis.labelPosition == .topInside + { + drawLabels(context: context, pos: viewPortHandler.contentTop + yOffset + xAxis.labelRotatedHeight, anchor: CGPoint(x: 0.5, y: 1.0)) + } + else if xAxis.labelPosition == .bottom + { + drawLabels(context: context, pos: viewPortHandler.contentBottom + yOffset, anchor: CGPoint(x: 0.5, y: 0.0)) + } + else if xAxis.labelPosition == .bottomInside + { + drawLabels(context: context, pos: viewPortHandler.contentBottom - yOffset - xAxis.labelRotatedHeight, anchor: CGPoint(x: 0.5, y: 0.0)) + } + else + { // BOTH SIDED + drawLabels(context: context, pos: viewPortHandler.contentTop - yOffset, anchor: CGPoint(x: 0.5, y: 1.0)) + drawLabels(context: context, pos: viewPortHandler.contentBottom + yOffset, anchor: CGPoint(x: 0.5, y: 0.0)) + } + } + + private var _axisLineSegmentsBuffer = [CGPoint](repeating: CGPoint(), count: 2) + + open override func renderAxisLine(context: CGContext) + { + guard let xAxis = self.axis as? XAxis else { return } + + if !xAxis.isEnabled || !xAxis.isDrawAxisLineEnabled + { + return + } + + context.saveGState() + + context.setStrokeColor(xAxis.axisLineColor.cgColor) + context.setLineWidth(xAxis.axisLineWidth) + if xAxis.axisLineDashLengths != nil + { + context.setLineDash(phase: xAxis.axisLineDashPhase, lengths: xAxis.axisLineDashLengths) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + if xAxis.labelPosition == .top + || xAxis.labelPosition == .topInside + || xAxis.labelPosition == .bothSided + { + _axisLineSegmentsBuffer[0].x = viewPortHandler.contentLeft + _axisLineSegmentsBuffer[0].y = viewPortHandler.contentTop + _axisLineSegmentsBuffer[1].x = viewPortHandler.contentRight + _axisLineSegmentsBuffer[1].y = viewPortHandler.contentTop + context.strokeLineSegments(between: _axisLineSegmentsBuffer) + } + + if xAxis.labelPosition == .bottom + || xAxis.labelPosition == .bottomInside + || xAxis.labelPosition == .bothSided + { + _axisLineSegmentsBuffer[0].x = viewPortHandler.contentLeft + _axisLineSegmentsBuffer[0].y = viewPortHandler.contentBottom + _axisLineSegmentsBuffer[1].x = viewPortHandler.contentRight + _axisLineSegmentsBuffer[1].y = viewPortHandler.contentBottom + context.strokeLineSegments(between: _axisLineSegmentsBuffer) + } + + context.restoreGState() + } + + /// draws the x-labels on the specified y-position + @objc open func drawLabels(context: CGContext, pos: CGFloat, anchor: CGPoint) + { + guard + let xAxis = self.axis as? XAxis, + let transformer = self.transformer + else { return } + + let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle + paraStyle.alignment = .center + + let labelAttrs: [NSAttributedString.Key : Any] = [ + .font: xAxis.labelFont, + .foregroundColor: xAxis.labelTextColor, + .paragraphStyle: paraStyle + ] + let labelRotationAngleRadians = xAxis.labelRotationAngle.DEG2RAD + + let centeringEnabled = xAxis.isCenterAxisLabelsEnabled + + let valueToPixelMatrix = transformer.valueToPixelMatrix + + var position = CGPoint(x: 0.0, y: 0.0) + + var labelMaxSize = CGSize() + + if xAxis.isWordWrapEnabled + { + labelMaxSize.width = xAxis.wordWrapWidthPercent * valueToPixelMatrix.a + } + + let entries = xAxis.entries + + for i in stride(from: 0, to: entries.count, by: 1) + { + if centeringEnabled + { + position.x = CGFloat(xAxis.centeredEntries[i]) + } + else + { + position.x = CGFloat(entries[i]) + } + + position.y = 0.0 + position = position.applying(valueToPixelMatrix) + + if viewPortHandler.isInBoundsX(position.x) + { + let label = xAxis.valueFormatter?.stringForValue(xAxis.entries[i], axis: xAxis) ?? "" + + let labelns = label as NSString + + if xAxis.isAvoidFirstLastClippingEnabled + { + // avoid clipping of the last + if i == xAxis.entryCount - 1 && xAxis.entryCount > 1 + { + let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width + + if width > viewPortHandler.offsetRight * 2.0 + && position.x + width > viewPortHandler.chartWidth + { + position.x -= width / 2.0 + } + } + else if i == 0 + { // avoid clipping of the first + let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width + position.x += width / 2.0 + } + } + + drawLabel(context: context, + formattedLabel: label, + x: position.x, + y: pos, + attributes: labelAttrs, + constrainedToSize: labelMaxSize, + anchor: anchor, + angleRadians: labelRotationAngleRadians) + } + } + } + + @objc open func drawLabel( + context: CGContext, + formattedLabel: String, + x: CGFloat, + y: CGFloat, + attributes: [NSAttributedString.Key : Any], + constrainedToSize: CGSize, + anchor: CGPoint, + angleRadians: CGFloat) + { + ChartUtils.drawMultilineText( + context: context, + text: formattedLabel, + point: CGPoint(x: x, y: y), + attributes: attributes, + constrainedToSize: constrainedToSize, + anchor: anchor, + angleRadians: angleRadians) + } + + open override func renderGridLines(context: CGContext) + { + guard + let xAxis = self.axis as? XAxis, + let transformer = self.transformer + else { return } + + if !xAxis.isDrawGridLinesEnabled || !xAxis.isEnabled + { + return + } + + context.saveGState() + defer { context.restoreGState() } + context.clip(to: self.gridClippingRect) + + context.setShouldAntialias(xAxis.gridAntialiasEnabled) + context.setStrokeColor(xAxis.gridColor.cgColor) + context.setLineWidth(xAxis.gridLineWidth) + context.setLineCap(xAxis.gridLineCap) + + if xAxis.gridLineDashLengths != nil + { + context.setLineDash(phase: xAxis.gridLineDashPhase, lengths: xAxis.gridLineDashLengths) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + let valueToPixelMatrix = transformer.valueToPixelMatrix + + var position = CGPoint(x: 0.0, y: 0.0) + + let entries = xAxis.entries + + for i in stride(from: 0, to: entries.count, by: 1) + { + position.x = CGFloat(entries[i]) + position.y = position.x + position = position.applying(valueToPixelMatrix) + + drawGridLine(context: context, x: position.x, y: position.y) + } + } + + @objc open var gridClippingRect: CGRect + { + var contentRect = viewPortHandler.contentRect + let dx = self.axis?.gridLineWidth ?? 0.0 + contentRect.origin.x -= dx / 2.0 + contentRect.size.width += dx + return contentRect + } + + @objc open func drawGridLine(context: CGContext, x: CGFloat, y: CGFloat) + { + if x >= viewPortHandler.offsetLeft + && x <= viewPortHandler.chartWidth + { + context.beginPath() + context.move(to: CGPoint(x: x, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: x, y: viewPortHandler.contentBottom)) + context.strokePath() + } + } + + open override func renderLimitLines(context: CGContext) + { + guard + let xAxis = self.axis as? XAxis, + let transformer = self.transformer, + !xAxis.limitLines.isEmpty + else { return } + + let trans = transformer.valueToPixelMatrix + + var position = CGPoint(x: 0.0, y: 0.0) + + for l in xAxis.limitLines where l.isEnabled + { + context.saveGState() + defer { context.restoreGState() } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.x -= l.lineWidth / 2.0 + clippingRect.size.width += l.lineWidth + context.clip(to: clippingRect) + + position.x = CGFloat(l.limit) + position.y = 0.0 + position = position.applying(trans) + + renderLimitLineLine(context: context, limitLine: l, position: position) + renderLimitLineLabel(context: context, limitLine: l, position: position, yOffset: 2.0 + l.yOffset) + } + } + + @objc open func renderLimitLineLine(context: CGContext, limitLine: ChartLimitLine, position: CGPoint) + { + + context.beginPath() + context.move(to: CGPoint(x: position.x, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: position.x, y: viewPortHandler.contentBottom)) + + context.setStrokeColor(limitLine.lineColor.cgColor) + context.setLineWidth(limitLine.lineWidth) + if limitLine.lineDashLengths != nil + { + context.setLineDash(phase: limitLine.lineDashPhase, lengths: limitLine.lineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.strokePath() + } + + @objc open func renderLimitLineLabel(context: CGContext, limitLine: ChartLimitLine, position: CGPoint, yOffset: CGFloat) + { + + let label = limitLine.label + guard limitLine.drawLabelEnabled, !label.isEmpty else { return } + + let labelLineHeight = limitLine.valueFont.lineHeight + + let xOffset: CGFloat = limitLine.lineWidth + limitLine.xOffset + let attributes: [NSAttributedString.Key : Any] = [ + .font : limitLine.valueFont, + .foregroundColor : limitLine.valueTextColor + ] + + let (point, align): (CGPoint, NSTextAlignment) + switch limitLine.labelPosition { + case .topRight: + point = CGPoint( + x: position.x + xOffset, + y: viewPortHandler.contentTop + yOffset + ) + align = .left + + case .bottomRight: + point = CGPoint( + x: position.x + xOffset, + y: viewPortHandler.contentBottom - labelLineHeight - yOffset + ) + align = .left + + case .topLeft: + point = CGPoint( + x: position.x - xOffset, + y: viewPortHandler.contentTop + yOffset + ) + align = .right + + case .bottomLeft: + point = CGPoint( + x: position.x - xOffset, + y: viewPortHandler.contentBottom - labelLineHeight - yOffset + ) + align = .right + } + + ChartUtils.drawText( + context: context, + text: label, + point: point, + align: align, + attributes: attributes + ) + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift b/dydx/Pods/Charts/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift new file mode 100644 index 000000000..86c569d9d --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift @@ -0,0 +1,356 @@ +// +// XAxisRendererHorizontalBarChart.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class XAxisRendererHorizontalBarChart: XAxisRenderer +{ + internal weak var chart: BarChartView? + + @objc public init(viewPortHandler: ViewPortHandler, xAxis: XAxis?, transformer: Transformer?, chart: BarChartView) + { + super.init(viewPortHandler: viewPortHandler, xAxis: xAxis, transformer: transformer) + + self.chart = chart + } + + open override func computeAxis(min: Double, max: Double, inverted: Bool) + { + var min = min, max = max + + if let transformer = self.transformer + { + // calculate the starting and entry point of the y-labels (depending on + // zoom / contentrect bounds) + if viewPortHandler.contentWidth > 10 && !viewPortHandler.isFullyZoomedOutY + { + let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) + let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + + if inverted + { + min = Double(p2.y) + max = Double(p1.y) + } + else + { + min = Double(p1.y) + max = Double(p2.y) + } + } + } + + computeAxisValues(min: min, max: max) + } + + open override func computeSize() + { + guard let + xAxis = self.axis as? XAxis + else { return } + + let longest = xAxis.getLongestLabel() as NSString + + let labelSize = longest.size(withAttributes: [NSAttributedString.Key.font: xAxis.labelFont]) + + let labelWidth = floor(labelSize.width + xAxis.xOffset * 3.5) + let labelHeight = labelSize.height + let labelRotatedSize = CGSize(width: labelSize.width, height: labelHeight).rotatedBy(degrees: xAxis.labelRotationAngle) + + xAxis.labelWidth = labelWidth + xAxis.labelHeight = labelHeight + xAxis.labelRotatedWidth = round(labelRotatedSize.width + xAxis.xOffset * 3.5) + xAxis.labelRotatedHeight = round(labelRotatedSize.height) + } + + open override func renderAxisLabels(context: CGContext) + { + guard + let xAxis = self.axis as? XAxis + else { return } + + if !xAxis.isEnabled || !xAxis.isDrawLabelsEnabled || chart?.data === nil + { + return + } + + let xoffset = xAxis.xOffset + + if xAxis.labelPosition == .top + { + drawLabels(context: context, pos: viewPortHandler.contentRight + xoffset, anchor: CGPoint(x: 0.0, y: 0.5)) + } + else if xAxis.labelPosition == .topInside + { + drawLabels(context: context, pos: viewPortHandler.contentRight - xoffset, anchor: CGPoint(x: 1.0, y: 0.5)) + } + else if xAxis.labelPosition == .bottom + { + drawLabels(context: context, pos: viewPortHandler.contentLeft - xoffset, anchor: CGPoint(x: 1.0, y: 0.5)) + } + else if xAxis.labelPosition == .bottomInside + { + drawLabels(context: context, pos: viewPortHandler.contentLeft + xoffset, anchor: CGPoint(x: 0.0, y: 0.5)) + } + else + { // BOTH SIDED + drawLabels(context: context, pos: viewPortHandler.contentRight + xoffset, anchor: CGPoint(x: 0.0, y: 0.5)) + drawLabels(context: context, pos: viewPortHandler.contentLeft - xoffset, anchor: CGPoint(x: 1.0, y: 0.5)) + } + } + + /// draws the x-labels on the specified y-position + open override func drawLabels(context: CGContext, pos: CGFloat, anchor: CGPoint) + { + guard + let xAxis = self.axis as? XAxis, + let transformer = self.transformer + else { return } + + let labelFont = xAxis.labelFont + let labelTextColor = xAxis.labelTextColor + let labelRotationAngleRadians = xAxis.labelRotationAngle.DEG2RAD + + let centeringEnabled = xAxis.isCenterAxisLabelsEnabled + + // pre allocate to save performance (dont allocate in loop) + var position = CGPoint(x: 0.0, y: 0.0) + + for i in stride(from: 0, to: xAxis.entryCount, by: 1) + { + // only fill x values + + position.x = 0.0 + + if centeringEnabled + { + position.y = CGFloat(xAxis.centeredEntries[i]) + } + else + { + position.y = CGFloat(xAxis.entries[i]) + } + + transformer.pointValueToPixel(&position) + + if viewPortHandler.isInBoundsY(position.y) + { + if let label = xAxis.valueFormatter?.stringForValue(xAxis.entries[i], axis: xAxis) + { + drawLabel( + context: context, + formattedLabel: label, + x: pos, + y: position.y, + attributes: [NSAttributedString.Key.font: labelFont, NSAttributedString.Key.foregroundColor: labelTextColor], + anchor: anchor, + angleRadians: labelRotationAngleRadians) + } + } + } + } + + @objc open func drawLabel( + context: CGContext, + formattedLabel: String, + x: CGFloat, + y: CGFloat, + attributes: [NSAttributedString.Key : Any], + anchor: CGPoint, + angleRadians: CGFloat) + { + ChartUtils.drawText( + context: context, + text: formattedLabel, + point: CGPoint(x: x, y: y), + attributes: attributes, + anchor: anchor, + angleRadians: angleRadians) + } + + open override var gridClippingRect: CGRect + { + var contentRect = viewPortHandler.contentRect + let dy = self.axis?.gridLineWidth ?? 0.0 + contentRect.origin.y -= dy / 2.0 + contentRect.size.height += dy + return contentRect + } + + private var _gridLineSegmentsBuffer = [CGPoint](repeating: CGPoint(), count: 2) + + open override func drawGridLine(context: CGContext, x: CGFloat, y: CGFloat) + { + if viewPortHandler.isInBoundsY(y) + { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: y)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: y)) + context.strokePath() + } + } + + open override func renderAxisLine(context: CGContext) + { + guard let xAxis = self.axis as? XAxis else { return } + + if !xAxis.isEnabled || !xAxis.isDrawAxisLineEnabled + { + return + } + + context.saveGState() + + context.setStrokeColor(xAxis.axisLineColor.cgColor) + context.setLineWidth(xAxis.axisLineWidth) + if xAxis.axisLineDashLengths != nil + { + context.setLineDash(phase: xAxis.axisLineDashPhase, lengths: xAxis.axisLineDashLengths) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + if xAxis.labelPosition == .top || + xAxis.labelPosition == .topInside || + xAxis.labelPosition == .bothSided + { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentBottom)) + context.strokePath() + } + + if xAxis.labelPosition == .bottom || + xAxis.labelPosition == .bottomInside || + xAxis.labelPosition == .bothSided + { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) + context.strokePath() + } + + context.restoreGState() + } + + open override func renderLimitLines(context: CGContext) + { + guard + let xAxis = self.axis as? XAxis, + let transformer = self.transformer + else { return } + + let limitLines = xAxis.limitLines + + if limitLines.count == 0 + { + return + } + + let trans = transformer.valueToPixelMatrix + + var position = CGPoint(x: 0.0, y: 0.0) + + for i in 0 ..< limitLines.count + { + let l = limitLines[i] + + if !l.isEnabled + { + continue + } + + context.saveGState() + defer { context.restoreGState() } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.y -= l.lineWidth / 2.0 + clippingRect.size.height += l.lineWidth + context.clip(to: clippingRect) + + position.x = 0.0 + position.y = CGFloat(l.limit) + position = position.applying(trans) + + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: position.y)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: position.y)) + + context.setStrokeColor(l.lineColor.cgColor) + context.setLineWidth(l.lineWidth) + if l.lineDashLengths != nil + { + context.setLineDash(phase: l.lineDashPhase, lengths: l.lineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.strokePath() + + let label = l.label + + // if drawing the limit-value label is enabled + if l.drawLabelEnabled && label.count > 0 + { + let labelLineHeight = l.valueFont.lineHeight + + let xOffset: CGFloat = 4.0 + l.xOffset + let yOffset: CGFloat = l.lineWidth + labelLineHeight + l.yOffset + + if l.labelPosition == .topRight + { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentRight - xOffset, + y: position.y - yOffset), + align: .right, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + else if l.labelPosition == .bottomRight + { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentRight - xOffset, + y: position.y + yOffset - labelLineHeight), + align: .right, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + else if l.labelPosition == .topLeft + { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentLeft + xOffset, + y: position.y - yOffset), + align: .left, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + else + { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentLeft + xOffset, + y: position.y + yOffset - labelLineHeight), + align: .left, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + } + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/XAxisRendererRadarChart.swift b/dydx/Pods/Charts/Source/Charts/Renderers/XAxisRendererRadarChart.swift new file mode 100644 index 000000000..c5fcedf54 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/XAxisRendererRadarChart.swift @@ -0,0 +1,91 @@ +// +// XAxisRendererRadarChart.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class XAxisRendererRadarChart: XAxisRenderer +{ + @objc open weak var chart: RadarChartView? + + @objc public init(viewPortHandler: ViewPortHandler, xAxis: XAxis?, chart: RadarChartView) + { + super.init(viewPortHandler: viewPortHandler, xAxis: xAxis, transformer: nil) + + self.chart = chart + } + + open override func renderAxisLabels(context: CGContext) + { + guard let + xAxis = axis as? XAxis, + let chart = chart + else { return } + + if !xAxis.isEnabled || !xAxis.isDrawLabelsEnabled + { + return + } + + let labelFont = xAxis.labelFont + let labelTextColor = xAxis.labelTextColor + let labelRotationAngleRadians = xAxis.labelRotationAngle.RAD2DEG + let drawLabelAnchor = CGPoint(x: 0.5, y: 0.25) + + let sliceangle = chart.sliceAngle + + // calculate the factor that is needed for transforming the value to pixels + let factor = chart.factor + + let center = chart.centerOffsets + + for i in stride(from: 0, to: chart.data?.maxEntryCountSet?.entryCount ?? 0, by: 1) + { + + let label = xAxis.valueFormatter?.stringForValue(Double(i), axis: xAxis) ?? "" + + let angle = (sliceangle * CGFloat(i) + chart.rotationAngle).truncatingRemainder(dividingBy: 360.0) + + let p = center.moving(distance: CGFloat(chart.yRange) * factor + xAxis.labelRotatedWidth / 2.0, atAngle: angle) + + drawLabel(context: context, + formattedLabel: label, + x: p.x, + y: p.y - xAxis.labelRotatedHeight / 2.0, + attributes: [NSAttributedString.Key.font: labelFont, NSAttributedString.Key.foregroundColor: labelTextColor], + anchor: drawLabelAnchor, + angleRadians: labelRotationAngleRadians) + } + } + + @objc open func drawLabel( + context: CGContext, + formattedLabel: String, + x: CGFloat, + y: CGFloat, + attributes: [NSAttributedString.Key : Any], + anchor: CGPoint, + angleRadians: CGFloat) + { + ChartUtils.drawText( + context: context, + text: formattedLabel, + point: CGPoint(x: x, y: y), + attributes: attributes, + anchor: anchor, + angleRadians: angleRadians) + } + + open override func renderLimitLines(context: CGContext) + { + /// XAxis LimitLines on RadarChart not yet supported. + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/YAxisRenderer.swift b/dydx/Pods/Charts/Source/Charts/Renderers/YAxisRenderer.swift new file mode 100644 index 000000000..9d0017f49 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/YAxisRenderer.swift @@ -0,0 +1,396 @@ +// +// YAxisRenderer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + +@objc(ChartYAxisRenderer) +open class YAxisRenderer: AxisRendererBase +{ + @objc public init(viewPortHandler: ViewPortHandler, yAxis: YAxis?, transformer: Transformer?) + { + super.init(viewPortHandler: viewPortHandler, transformer: transformer, axis: yAxis) + } + + /// draws the y-axis labels to the screen + open override func renderAxisLabels(context: CGContext) + { + guard let yAxis = self.axis as? YAxis else { return } + + if !yAxis.isEnabled || !yAxis.isDrawLabelsEnabled + { + return + } + + let xoffset = yAxis.xOffset + let yoffset = yAxis.labelFont.lineHeight / 2.5 + yAxis.yOffset + + let dependency = yAxis.axisDependency + let labelPosition = yAxis.labelPosition + + var xPos = CGFloat(0.0) + + var textAlign: NSTextAlignment + + if dependency == .left + { + if labelPosition == .outsideChart + { + textAlign = .right + xPos = viewPortHandler.offsetLeft - xoffset + } + else + { + textAlign = .left + xPos = viewPortHandler.offsetLeft + xoffset + } + + } + else + { + if labelPosition == .outsideChart + { + textAlign = .left + xPos = viewPortHandler.contentRight + xoffset + } + else + { + textAlign = .right + xPos = viewPortHandler.contentRight - xoffset + } + } + + drawYLabels( + context: context, + fixedPosition: xPos, + positions: transformedPositions(), + offset: yoffset - yAxis.labelFont.lineHeight, + textAlign: textAlign) + } + + open override func renderAxisLine(context: CGContext) + { + guard let yAxis = self.axis as? YAxis else { return } + + if !yAxis.isEnabled || !yAxis.drawAxisLineEnabled + { + return + } + + context.saveGState() + + context.setStrokeColor(yAxis.axisLineColor.cgColor) + context.setLineWidth(yAxis.axisLineWidth) + if yAxis.axisLineDashLengths != nil + { + context.setLineDash(phase: yAxis.axisLineDashPhase, lengths: yAxis.axisLineDashLengths) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + if yAxis.axisDependency == .left + { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) + context.strokePath() + } + else + { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentBottom)) + context.strokePath() + } + + context.restoreGState() + } + + /// draws the y-labels on the specified x-position + open func drawYLabels( + context: CGContext, + fixedPosition: CGFloat, + positions: [CGPoint], + offset: CGFloat, + textAlign: NSTextAlignment) + { + guard + let yAxis = self.axis as? YAxis + else { return } + + let labelFont = yAxis.labelFont + let labelTextColor = yAxis.labelTextColor + + let from = yAxis.isDrawBottomYLabelEntryEnabled ? 0 : 1 + let to = yAxis.isDrawTopYLabelEntryEnabled ? yAxis.entryCount : (yAxis.entryCount - 1) + + let xOffset = yAxis.labelXOffset + + for i in stride(from: from, to: to, by: 1) + { + let text = yAxis.getFormattedLabel(i) + + ChartUtils.drawText( + context: context, + text: text, + point: CGPoint(x: fixedPosition + xOffset, y: positions[i].y + offset), + align: textAlign, + attributes: [.font: labelFont, .foregroundColor: labelTextColor] + ) + } + } + + open override func renderGridLines(context: CGContext) + { + guard let + yAxis = self.axis as? YAxis + else { return } + + if !yAxis.isEnabled + { + return + } + + if yAxis.drawGridLinesEnabled + { + let positions = transformedPositions() + + context.saveGState() + defer { context.restoreGState() } + context.clip(to: self.gridClippingRect) + + context.setShouldAntialias(yAxis.gridAntialiasEnabled) + context.setStrokeColor(yAxis.gridColor.cgColor) + context.setLineWidth(yAxis.gridLineWidth) + context.setLineCap(yAxis.gridLineCap) + + if yAxis.gridLineDashLengths != nil + { + context.setLineDash(phase: yAxis.gridLineDashPhase, lengths: yAxis.gridLineDashLengths) + + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + // draw the grid + positions.forEach { drawGridLine(context: context, position: $0) } + } + + if yAxis.drawZeroLineEnabled + { + // draw zero line + drawZeroLine(context: context) + } + } + + @objc open var gridClippingRect: CGRect + { + var contentRect = viewPortHandler.contentRect + let dy = self.axis?.gridLineWidth ?? 0.0 + contentRect.origin.y -= dy / 2.0 + contentRect.size.height += dy + return contentRect + } + + @objc open func drawGridLine( + context: CGContext, + position: CGPoint) + { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: position.y)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: position.y)) + context.strokePath() + } + + @objc open func transformedPositions() -> [CGPoint] + { + guard + let yAxis = self.axis as? YAxis, + let transformer = self.transformer + else { return [CGPoint]() } + + var positions = [CGPoint]() + positions.reserveCapacity(yAxis.entryCount) + + let entries = yAxis.entries + + for i in stride(from: 0, to: yAxis.entryCount, by: 1) + { + positions.append(CGPoint(x: 0.0, y: entries[i])) + } + + transformer.pointValuesToPixel(&positions) + + return positions + } + + /// Draws the zero line at the specified position. + @objc open func drawZeroLine(context: CGContext) + { + guard + let yAxis = self.axis as? YAxis, + let transformer = self.transformer, + let zeroLineColor = yAxis.zeroLineColor + else { return } + + context.saveGState() + defer { context.restoreGState() } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.y -= yAxis.zeroLineWidth / 2.0 + clippingRect.size.height += yAxis.zeroLineWidth + context.clip(to: clippingRect) + + context.setStrokeColor(zeroLineColor.cgColor) + context.setLineWidth(yAxis.zeroLineWidth) + + let pos = transformer.pixelForValues(x: 0.0, y: 0.0) + + if yAxis.zeroLineDashLengths != nil + { + context.setLineDash(phase: yAxis.zeroLineDashPhase, lengths: yAxis.zeroLineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: pos.y)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: pos.y)) + context.drawPath(using: CGPathDrawingMode.stroke) + } + + open override func renderLimitLines(context: CGContext) + { + guard + let yAxis = self.axis as? YAxis, + let transformer = self.transformer + else { return } + + let limitLines = yAxis.limitLines + + if limitLines.count == 0 + { + return + } + + context.saveGState() + + let trans = transformer.valueToPixelMatrix + + var position = CGPoint(x: 0.0, y: 0.0) + + for i in 0 ..< limitLines.count + { + let l = limitLines[i] + + if !l.isEnabled + { + continue + } + + context.saveGState() + defer { context.restoreGState() } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.y -= l.lineWidth / 2.0 + clippingRect.size.height += l.lineWidth + context.clip(to: clippingRect) + + position.x = 0.0 + position.y = CGFloat(l.limit) + position = position.applying(trans) + + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: position.y)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: position.y)) + + context.setStrokeColor(l.lineColor.cgColor) + context.setLineWidth(l.lineWidth) + if l.lineDashLengths != nil + { + context.setLineDash(phase: l.lineDashPhase, lengths: l.lineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.strokePath() + + let label = l.label + + // if drawing the limit-value label is enabled + if l.drawLabelEnabled && label.count > 0 + { + let labelLineHeight = l.valueFont.lineHeight + + let xOffset: CGFloat = 4.0 + l.xOffset + let yOffset: CGFloat = l.lineWidth + labelLineHeight + l.yOffset + + if l.labelPosition == .topRight + { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentRight - xOffset, + y: position.y - yOffset), + align: .right, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + else if l.labelPosition == .bottomRight + { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentRight - xOffset, + y: position.y + yOffset - labelLineHeight), + align: .right, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + else if l.labelPosition == .topLeft + { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentLeft + xOffset, + y: position.y - yOffset), + align: .left, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + else + { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: viewPortHandler.contentLeft + xOffset, + y: position.y + yOffset - labelLineHeight), + align: .left, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + } + } + + context.restoreGState() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift b/dydx/Pods/Charts/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift new file mode 100644 index 000000000..de4f65e52 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift @@ -0,0 +1,368 @@ +// +// YAxisRendererHorizontalBarChart.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class YAxisRendererHorizontalBarChart: YAxisRenderer +{ + public override init(viewPortHandler: ViewPortHandler, yAxis: YAxis?, transformer: Transformer?) + { + super.init(viewPortHandler: viewPortHandler, yAxis: yAxis, transformer: transformer) + } + + /// Computes the axis values. + open override func computeAxis(min: Double, max: Double, inverted: Bool) + { + guard let transformer = self.transformer else { return } + + var min = min, max = max + + // calculate the starting and entry point of the y-labels (depending on zoom / contentrect bounds) + if viewPortHandler.contentHeight > 10.0 && !viewPortHandler.isFullyZoomedOutX + { + let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) + + if !inverted + { + min = Double(p1.x) + max = Double(p2.x) + } + else + { + min = Double(p2.x) + max = Double(p1.x) + } + } + + computeAxisValues(min: min, max: max) + } + + /// draws the y-axis labels to the screen + open override func renderAxisLabels(context: CGContext) + { + guard let yAxis = axis as? YAxis else { return } + + if !yAxis.isEnabled || !yAxis.isDrawLabelsEnabled + { + return + } + + let lineHeight = yAxis.labelFont.lineHeight + let baseYOffset: CGFloat = 2.5 + + let dependency = yAxis.axisDependency + let labelPosition = yAxis.labelPosition + + var yPos: CGFloat = 0.0 + + if dependency == .left + { + if labelPosition == .outsideChart + { + yPos = viewPortHandler.contentTop - baseYOffset + } + else + { + yPos = viewPortHandler.contentTop - baseYOffset + } + } + else + { + if labelPosition == .outsideChart + { + yPos = viewPortHandler.contentBottom + lineHeight + baseYOffset + } + else + { + yPos = viewPortHandler.contentBottom + lineHeight + baseYOffset + } + } + + // For compatibility with Android code, we keep above calculation the same, + // And here we pull the line back up + yPos -= lineHeight + + drawYLabels( + context: context, + fixedPosition: yPos, + positions: transformedPositions(), + offset: yAxis.yOffset) + } + + open override func renderAxisLine(context: CGContext) + { + guard let yAxis = axis as? YAxis else { return } + + if !yAxis.isEnabled || !yAxis.drawAxisLineEnabled + { + return + } + + context.saveGState() + + context.setStrokeColor(yAxis.axisLineColor.cgColor) + context.setLineWidth(yAxis.axisLineWidth) + if yAxis.axisLineDashLengths != nil + { + context.setLineDash(phase: yAxis.axisLineDashPhase, lengths: yAxis.axisLineDashLengths) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + if yAxis.axisDependency == .left + { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) + context.strokePath() + } + else + { + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentBottom)) + context.strokePath() } + + context.restoreGState() + } + + /// draws the y-labels on the specified x-position + @objc open func drawYLabels( + context: CGContext, + fixedPosition: CGFloat, + positions: [CGPoint], + offset: CGFloat) + { + guard let + yAxis = axis as? YAxis + else { return } + + let labelFont = yAxis.labelFont + let labelTextColor = yAxis.labelTextColor + + let from = yAxis.isDrawBottomYLabelEntryEnabled ? 0 : 1 + let to = yAxis.isDrawTopYLabelEntryEnabled ? yAxis.entryCount : (yAxis.entryCount - 1) + + let xOffset = yAxis.labelXOffset + + for i in stride(from: from, to: to, by: 1) + { + let text = yAxis.getFormattedLabel(i) + + ChartUtils.drawText( + context: context, + text: text, + point: CGPoint( + x: positions[i].x, y: + fixedPosition - offset + xOffset + ), + align: .center, + attributes: [NSAttributedString.Key.font: labelFont, NSAttributedString.Key.foregroundColor: labelTextColor]) + } + } + + open override var gridClippingRect: CGRect + { + var contentRect = viewPortHandler.contentRect + let dx = self.axis?.gridLineWidth ?? 0.0 + contentRect.origin.x -= dx / 2.0 + contentRect.size.width += dx + return contentRect + } + + open override func drawGridLine( + context: CGContext, + position: CGPoint) + { + context.beginPath() + context.move(to: CGPoint(x: position.x, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: position.x, y: viewPortHandler.contentBottom)) + context.strokePath() + } + + open override func transformedPositions() -> [CGPoint] + { + guard + let yAxis = self.axis as? YAxis, + let transformer = self.transformer + else { return [CGPoint]() } + + var positions = [CGPoint]() + positions.reserveCapacity(yAxis.entryCount) + + let entries = yAxis.entries + + for i in stride(from: 0, to: yAxis.entryCount, by: 1) + { + positions.append(CGPoint(x: entries[i], y: 0.0)) + } + + transformer.pointValuesToPixel(&positions) + + return positions + } + + /// Draws the zero line at the specified position. + open override func drawZeroLine(context: CGContext) + { + guard + let yAxis = self.axis as? YAxis, + let transformer = self.transformer, + let zeroLineColor = yAxis.zeroLineColor + else { return } + + context.saveGState() + defer { context.restoreGState() } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.x -= yAxis.zeroLineWidth / 2.0 + clippingRect.size.width += yAxis.zeroLineWidth + context.clip(to: clippingRect) + + context.setStrokeColor(zeroLineColor.cgColor) + context.setLineWidth(yAxis.zeroLineWidth) + + let pos = transformer.pixelForValues(x: 0.0, y: 0.0) + + if yAxis.zeroLineDashLengths != nil + { + context.setLineDash(phase: yAxis.zeroLineDashPhase, lengths: yAxis.zeroLineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.move(to: CGPoint(x: pos.x - 1.0, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: pos.x - 1.0, y: viewPortHandler.contentBottom)) + context.drawPath(using: CGPathDrawingMode.stroke) + } + + private var _limitLineSegmentsBuffer = [CGPoint](repeating: CGPoint(), count: 2) + + open override func renderLimitLines(context: CGContext) + { + guard + let yAxis = axis as? YAxis, + let transformer = self.transformer + else { return } + + let limitLines = yAxis.limitLines + + if limitLines.count <= 0 + { + return + } + + context.saveGState() + + let trans = transformer.valueToPixelMatrix + + var position = CGPoint(x: 0.0, y: 0.0) + + for i in 0 ..< limitLines.count + { + let l = limitLines[i] + + if !l.isEnabled + { + continue + } + + context.saveGState() + defer { context.restoreGState() } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.x -= l.lineWidth / 2.0 + clippingRect.size.width += l.lineWidth + context.clip(to: clippingRect) + + position.x = CGFloat(l.limit) + position.y = 0.0 + position = position.applying(trans) + + context.beginPath() + context.move(to: CGPoint(x: position.x, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: position.x, y: viewPortHandler.contentBottom)) + + context.setStrokeColor(l.lineColor.cgColor) + context.setLineWidth(l.lineWidth) + if l.lineDashLengths != nil + { + context.setLineDash(phase: l.lineDashPhase, lengths: l.lineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.strokePath() + + let label = l.label + + // if drawing the limit-value label is enabled + if l.drawLabelEnabled && label.count > 0 + { + let labelLineHeight = l.valueFont.lineHeight + + let xOffset: CGFloat = l.lineWidth + l.xOffset + let yOffset: CGFloat = 2.0 + l.yOffset + + if l.labelPosition == .topRight + { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: position.x + xOffset, + y: viewPortHandler.contentTop + yOffset), + align: .left, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + else if l.labelPosition == .bottomRight + { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: position.x + xOffset, + y: viewPortHandler.contentBottom - labelLineHeight - yOffset), + align: .left, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + else if l.labelPosition == .topLeft + { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: position.x - xOffset, + y: viewPortHandler.contentTop + yOffset), + align: .right, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + else + { + ChartUtils.drawText(context: context, + text: label, + point: CGPoint( + x: position.x - xOffset, + y: viewPortHandler.contentBottom - labelLineHeight - yOffset), + align: .right, + attributes: [NSAttributedString.Key.font: l.valueFont, NSAttributedString.Key.foregroundColor: l.valueTextColor]) + } + } + } + + context.restoreGState() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Renderers/YAxisRendererRadarChart.swift b/dydx/Pods/Charts/Source/Charts/Renderers/YAxisRendererRadarChart.swift new file mode 100644 index 000000000..a5e44daa6 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Renderers/YAxisRendererRadarChart.swift @@ -0,0 +1,281 @@ +// +// YAxisRendererRadarChart.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + +open class YAxisRendererRadarChart: YAxisRenderer +{ + private weak var chart: RadarChartView? + + @objc public init(viewPortHandler: ViewPortHandler, yAxis: YAxis?, chart: RadarChartView) + { + super.init(viewPortHandler: viewPortHandler, yAxis: yAxis, transformer: nil) + + self.chart = chart + } + + open override func computeAxisValues(min yMin: Double, max yMax: Double) + { + guard let + axis = axis as? YAxis + else { return } + + let labelCount = axis.labelCount + let range = abs(yMax - yMin) + + if labelCount == 0 || range <= 0 || range.isInfinite + { + axis.entries = [Double]() + axis.centeredEntries = [Double]() + return + } + + // Find out how much spacing (in yValue space) between axis values + let rawInterval = range / Double(labelCount) + var interval = rawInterval.roundedToNextSignficant() + + // If granularity is enabled, then do not allow the interval to go below specified granularity. + // This is used to avoid repeated values when rounding values for display. + if axis.isGranularityEnabled + { + interval = interval < axis.granularity ? axis.granularity : interval + } + + // Normalize interval + let intervalMagnitude = pow(10.0, floor(log10(interval))).roundedToNextSignficant() + let intervalSigDigit = Int(interval / intervalMagnitude) + + if intervalSigDigit > 5 + { + // Use one order of magnitude higher, to avoid intervals like 0.9 or 90 + // if it's 0.0 after floor(), we use the old value + interval = floor(10.0 * intervalMagnitude) == 0.0 ? interval : floor(10.0 * intervalMagnitude) + } + + let centeringEnabled = axis.isCenterAxisLabelsEnabled + var n = centeringEnabled ? 1 : 0 + + // force label count + if axis.isForceLabelsEnabled + { + let step = Double(range) / Double(labelCount - 1) + + // Ensure stops contains at least n elements. + axis.entries.removeAll(keepingCapacity: true) + axis.entries.reserveCapacity(labelCount) + + var v = yMin + + for _ in 0 ..< labelCount + { + axis.entries.append(v) + v += step + } + + n = labelCount + } + else + { + // no forced count + + var first = interval == 0.0 ? 0.0 : ceil(yMin / interval) * interval + + if centeringEnabled + { + first -= interval + } + + let last = interval == 0.0 ? 0.0 : (floor(yMax / interval) * interval).nextUp + + if interval != 0.0 + { + for _ in stride(from: first, through: last, by: interval) + { + n += 1 + } + } + + n += 1 + + // Ensure stops contains at least n elements. + axis.entries.removeAll(keepingCapacity: true) + axis.entries.reserveCapacity(labelCount) + + var f = first + var i = 0 + while i < n + { + if f == 0.0 + { + // Fix for IEEE negative zero case (Where value == -0.0, and 0.0 == -0.0) + f = 0.0 + } + + axis.entries.append(Double(f)) + + f += interval + i += 1 + } + } + + // set decimals + if interval < 1 + { + axis.decimals = Int(ceil(-log10(interval))) + } + else + { + axis.decimals = 0 + } + + if centeringEnabled + { + axis.centeredEntries.reserveCapacity(n) + axis.centeredEntries.removeAll() + + let offset = (axis.entries[1] - axis.entries[0]) / 2.0 + + for i in 0 ..< n + { + axis.centeredEntries.append(axis.entries[i] + offset) + } + } + + axis._axisMinimum = axis.entries[0] + axis._axisMaximum = axis.entries[n-1] + axis.axisRange = abs(axis._axisMaximum - axis._axisMinimum) + } + + open override func renderAxisLabels(context: CGContext) + { + guard let + yAxis = axis as? YAxis, + let chart = chart + else { return } + + if !yAxis.isEnabled || !yAxis.isDrawLabelsEnabled + { + return + } + + let labelFont = yAxis.labelFont + let labelTextColor = yAxis.labelTextColor + + let center = chart.centerOffsets + let factor = chart.factor + + let labelLineHeight = yAxis.labelFont.lineHeight + + let from = yAxis.isDrawBottomYLabelEntryEnabled ? 0 : 1 + let to = yAxis.isDrawTopYLabelEntryEnabled ? yAxis.entryCount : (yAxis.entryCount - 1) + + let alignment: NSTextAlignment = yAxis.labelAlignment + let xOffset = yAxis.labelXOffset + + for j in stride(from: from, to: to, by: 1) + { + let r = CGFloat(yAxis.entries[j] - yAxis._axisMinimum) * factor + + let p = center.moving(distance: r, atAngle: chart.rotationAngle) + + let label = yAxis.getFormattedLabel(j) + + ChartUtils.drawText( + context: context, + text: label, + point: CGPoint(x: p.x + xOffset, y: p.y - labelLineHeight), + align: alignment, + attributes: [ + NSAttributedString.Key.font: labelFont, + NSAttributedString.Key.foregroundColor: labelTextColor + ]) + } + } + + open override func renderLimitLines(context: CGContext) + { + guard + let yAxis = axis as? YAxis, + let chart = chart, + let data = chart.data + else { return } + + let limitLines = yAxis.limitLines + + if limitLines.count == 0 + { + return + } + + context.saveGState() + + let sliceangle = chart.sliceAngle + + // calculate the factor that is needed for transforming the value to pixels + let factor = chart.factor + + let center = chart.centerOffsets + + for i in 0 ..< limitLines.count + { + let l = limitLines[i] + + if !l.isEnabled + { + continue + } + + context.setStrokeColor(l.lineColor.cgColor) + context.setLineWidth(l.lineWidth) + if l.lineDashLengths != nil + { + context.setLineDash(phase: l.lineDashPhase, lengths: l.lineDashLengths!) + } + else + { + context.setLineDash(phase: 0.0, lengths: []) + } + + let r = CGFloat(l.limit - chart.chartYMin) * factor + + context.beginPath() + + for j in 0 ..< (data.maxEntryCountSet?.entryCount ?? 0) + { + let p = center.moving(distance: r, atAngle: sliceangle * CGFloat(j) + chart.rotationAngle) + + if j == 0 + { + context.move(to: CGPoint(x: p.x, y: p.y)) + } + else + { + context.addLine(to: CGPoint(x: p.x, y: p.y)) + } + } + + context.closePath() + + context.strokePath() + } + + context.restoreGState() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Utils/ChartColorTemplates.swift b/dydx/Pods/Charts/Source/Charts/Utils/ChartColorTemplates.swift new file mode 100644 index 000000000..534f9cb88 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Utils/ChartColorTemplates.swift @@ -0,0 +1,198 @@ +// +// ChartColorTemplates.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +open class ChartColorTemplates: NSObject +{ + @objc open class func liberty () -> [NSUIColor] + { + return [ + NSUIColor(red: 207/255.0, green: 248/255.0, blue: 246/255.0, alpha: 1.0), + NSUIColor(red: 148/255.0, green: 212/255.0, blue: 212/255.0, alpha: 1.0), + NSUIColor(red: 136/255.0, green: 180/255.0, blue: 187/255.0, alpha: 1.0), + NSUIColor(red: 118/255.0, green: 174/255.0, blue: 175/255.0, alpha: 1.0), + NSUIColor(red: 42/255.0, green: 109/255.0, blue: 130/255.0, alpha: 1.0) + ] + } + + @objc open class func joyful () -> [NSUIColor] + { + return [ + NSUIColor(red: 217/255.0, green: 80/255.0, blue: 138/255.0, alpha: 1.0), + NSUIColor(red: 254/255.0, green: 149/255.0, blue: 7/255.0, alpha: 1.0), + NSUIColor(red: 254/255.0, green: 247/255.0, blue: 120/255.0, alpha: 1.0), + NSUIColor(red: 106/255.0, green: 167/255.0, blue: 134/255.0, alpha: 1.0), + NSUIColor(red: 53/255.0, green: 194/255.0, blue: 209/255.0, alpha: 1.0) + ] + } + + @objc open class func pastel () -> [NSUIColor] + { + return [ + NSUIColor(red: 64/255.0, green: 89/255.0, blue: 128/255.0, alpha: 1.0), + NSUIColor(red: 149/255.0, green: 165/255.0, blue: 124/255.0, alpha: 1.0), + NSUIColor(red: 217/255.0, green: 184/255.0, blue: 162/255.0, alpha: 1.0), + NSUIColor(red: 191/255.0, green: 134/255.0, blue: 134/255.0, alpha: 1.0), + NSUIColor(red: 179/255.0, green: 48/255.0, blue: 80/255.0, alpha: 1.0) + ] + } + + @objc open class func colorful () -> [NSUIColor] + { + return [ + NSUIColor(red: 193/255.0, green: 37/255.0, blue: 82/255.0, alpha: 1.0), + NSUIColor(red: 255/255.0, green: 102/255.0, blue: 0/255.0, alpha: 1.0), + NSUIColor(red: 245/255.0, green: 199/255.0, blue: 0/255.0, alpha: 1.0), + NSUIColor(red: 106/255.0, green: 150/255.0, blue: 31/255.0, alpha: 1.0), + NSUIColor(red: 179/255.0, green: 100/255.0, blue: 53/255.0, alpha: 1.0) + ] + } + + @objc open class func vordiplom () -> [NSUIColor] + { + return [ + NSUIColor(red: 192/255.0, green: 255/255.0, blue: 140/255.0, alpha: 1.0), + NSUIColor(red: 255/255.0, green: 247/255.0, blue: 140/255.0, alpha: 1.0), + NSUIColor(red: 255/255.0, green: 208/255.0, blue: 140/255.0, alpha: 1.0), + NSUIColor(red: 140/255.0, green: 234/255.0, blue: 255/255.0, alpha: 1.0), + NSUIColor(red: 255/255.0, green: 140/255.0, blue: 157/255.0, alpha: 1.0) + ] + } + + @objc open class func material () -> [NSUIColor] + { + return [ + NSUIColor(red: 46/255.0, green: 204/255.0, blue: 113/255.0, alpha: 1.0), + NSUIColor(red: 241/255.0, green: 196/255.0, blue: 15/255.0, alpha: 1.0), + NSUIColor(red: 231/255.0, green: 76/255.0, blue: 60/255.0, alpha: 1.0), + NSUIColor(red: 52/255.0, green: 152/255.0, blue: 219/255.0, alpha: 1.0) + ] + } + + @objc open class func colorFromString(_ colorString: String) -> NSUIColor + { + let leftParenCharset: CharacterSet = CharacterSet(charactersIn: "( ") + let commaCharset: CharacterSet = CharacterSet(charactersIn: ", ") + + let colorString = colorString.lowercased() + + if colorString.hasPrefix("#") + { + var argb: [UInt] = [255, 0, 0, 0] + let colorString = colorString.unicodeScalars + var length = colorString.count + var index = colorString.startIndex + let endIndex = colorString.endIndex + + index = colorString.index(after: index) + length = length - 1 + + if length == 3 || length == 6 || length == 8 + { + var i = length == 8 ? 0 : 1 + while index < endIndex + { + var c = colorString[index] + index = colorString.index(after: index) + + var val = (c.value >= 0x61 && c.value <= 0x66) ? (c.value - 0x61 + 10) : c.value - 0x30 + argb[i] = UInt(val) * 16 + if length == 3 + { + argb[i] = argb[i] + UInt(val) + } + else + { + c = colorString[index] + index = colorString.index(after: index) + + val = (c.value >= 0x61 && c.value <= 0x66) ? (c.value - 0x61 + 10) : c.value - 0x30 + argb[i] = argb[i] + UInt(val) + } + + i += 1 + } + } + + return NSUIColor(red: CGFloat(argb[1]) / 255.0, green: CGFloat(argb[2]) / 255.0, blue: CGFloat(argb[3]) / 255.0, alpha: CGFloat(argb[0]) / 255.0) + } + else if colorString.hasPrefix("rgba") + { + var a: Float = 1.0 + var r: Int32 = 0 + var g: Int32 = 0 + var b: Int32 = 0 + let scanner: Scanner = Scanner(string: colorString) + scanner.scanString("rgba", into: nil) + scanner.scanCharacters(from: leftParenCharset, into: nil) + scanner.scanInt32(&r) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&g) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&b) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanFloat(&a) + return NSUIColor( + red: CGFloat(r) / 255.0, + green: CGFloat(g) / 255.0, + blue: CGFloat(b) / 255.0, + alpha: CGFloat(a) + ) + } + else if colorString.hasPrefix("argb") + { + var a: Float = 1.0 + var r: Int32 = 0 + var g: Int32 = 0 + var b: Int32 = 0 + let scanner: Scanner = Scanner(string: colorString) + scanner.scanString("argb", into: nil) + scanner.scanCharacters(from: leftParenCharset, into: nil) + scanner.scanFloat(&a) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&r) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&g) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&b) + return NSUIColor( + red: CGFloat(r) / 255.0, + green: CGFloat(g) / 255.0, + blue: CGFloat(b) / 255.0, + alpha: CGFloat(a) + ) + } + else if colorString.hasPrefix("rgb") + { + var r: Int32 = 0 + var g: Int32 = 0 + var b: Int32 = 0 + let scanner: Scanner = Scanner(string: colorString) + scanner.scanString("rgb", into: nil) + scanner.scanCharacters(from: leftParenCharset, into: nil) + scanner.scanInt32(&r) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&g) + scanner.scanCharacters(from: commaCharset, into: nil) + scanner.scanInt32(&b) + return NSUIColor( + red: CGFloat(r) / 255.0, + green: CGFloat(g) / 255.0, + blue: CGFloat(b) / 255.0, + alpha: 1.0 + ) + } + + return NSUIColor.clear + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Utils/ChartUtils.swift b/dydx/Pods/Charts/Source/Charts/Utils/ChartUtils.swift new file mode 100644 index 000000000..5e90873b5 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Utils/ChartUtils.swift @@ -0,0 +1,311 @@ +// +// Utils.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +#if canImport(UIKit) + import UIKit +#endif + +#if canImport(Cocoa) +import Cocoa +#endif + +extension Comparable +{ + func clamped(to range: ClosedRange) -> Self + { + if self > range.upperBound + { + return range.upperBound + } + else if self < range.lowerBound + { + return range.lowerBound + } + else + { + return self + } + } +} + +extension FloatingPoint +{ + var DEG2RAD: Self + { + return self * .pi / 180 + } + + var RAD2DEG: Self + { + return self * 180 / .pi + } + + /// - Note: Value must be in degrees + /// - Returns: An angle between 0.0 < 360.0 (not less than zero, less than 360) + var normalizedAngle: Self + { + let angle = truncatingRemainder(dividingBy: 360) + return (sign == .minus) ? angle + 360 : angle + } +} + +extension CGSize +{ + func rotatedBy(degrees: CGFloat) -> CGSize + { + let radians = degrees.DEG2RAD + return rotatedBy(radians: radians) + } + + func rotatedBy(radians: CGFloat) -> CGSize + { + return CGSize( + width: abs(width * cos(radians)) + abs(height * sin(radians)), + height: abs(width * sin(radians)) + abs(height * cos(radians)) + ) + } +} + +extension Double +{ + /// Rounds the number to the nearest multiple of it's order of magnitude, rounding away from zero if halfway. + func roundedToNextSignficant() -> Double + { + guard + !isInfinite, + !isNaN, + self != 0 + else { return self } + + let d = ceil(log10(self < 0 ? -self : self)) + let pw = 1 - Int(d) + let magnitude = pow(10.0, Double(pw)) + let shifted = (self * magnitude).rounded() + return shifted / magnitude + } + + var decimalPlaces: Int + { + guard + !isNaN, + !isInfinite, + self != 0.0 + else { return 0 } + + let i = self.roundedToNextSignficant() + + guard + !i.isInfinite, + !i.isNaN + else { return 0 } + + return Int(ceil(-log10(i))) + 2 + } +} + +extension CGPoint +{ + /// Calculates the position around a center point, depending on the distance from the center, and the angle of the position around the center. + func moving(distance: CGFloat, atAngle angle: CGFloat) -> CGPoint + { + return CGPoint(x: x + distance * cos(angle.DEG2RAD), + y: y + distance * sin(angle.DEG2RAD)) + } +} + +open class ChartUtils +{ + private static var _defaultValueFormatter: IValueFormatter = ChartUtils.generateDefaultValueFormatter() + + open class func drawImage( + context: CGContext, + image: NSUIImage, + x: CGFloat, + y: CGFloat, + size: CGSize) + { + var drawOffset = CGPoint() + drawOffset.x = x - (size.width / 2) + drawOffset.y = y - (size.height / 2) + + NSUIGraphicsPushContext(context) + + if image.size.width != size.width && image.size.height != size.height + { + let key = "resized_\(size.width)_\(size.height)" + + // Try to take scaled image from cache of this image + var scaledImage = objc_getAssociatedObject(image, key) as? NSUIImage + if scaledImage == nil + { + // Scale the image + NSUIGraphicsBeginImageContextWithOptions(size, false, 0.0) + + image.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: size)) + + scaledImage = NSUIGraphicsGetImageFromCurrentImageContext() + NSUIGraphicsEndImageContext() + + // Put the scaled image in a cache owned by the original image + objc_setAssociatedObject(image, key, scaledImage, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + + scaledImage?.draw(in: CGRect(origin: drawOffset, size: size)) + } + else + { + image.draw(in: CGRect(origin: drawOffset, size: size)) + } + + NSUIGraphicsPopContext() + } + + open class func drawText(context: CGContext, text: String, point: CGPoint, align: NSTextAlignment, attributes: [NSAttributedString.Key : Any]?) + { + var point = point + + if align == .center + { + point.x -= text.size(withAttributes: attributes).width / 2.0 + } + else if align == .right + { + point.x -= text.size(withAttributes: attributes).width + } + + NSUIGraphicsPushContext(context) + + (text as NSString).draw(at: point, withAttributes: attributes) + + NSUIGraphicsPopContext() + } + + open class func drawText(context: CGContext, text: String, point: CGPoint, attributes: [NSAttributedString.Key : Any]?, anchor: CGPoint, angleRadians: CGFloat) + { + var drawOffset = CGPoint() + + NSUIGraphicsPushContext(context) + + if angleRadians != 0.0 + { + let size = text.size(withAttributes: attributes) + + // Move the text drawing rect in a way that it always rotates around its center + drawOffset.x = -size.width * 0.5 + drawOffset.y = -size.height * 0.5 + + var translate = point + + // Move the "outer" rect relative to the anchor, assuming its centered + if anchor.x != 0.5 || anchor.y != 0.5 + { + let rotatedSize = size.rotatedBy(radians: angleRadians) + + translate.x -= rotatedSize.width * (anchor.x - 0.5) + translate.y -= rotatedSize.height * (anchor.y - 0.5) + } + + context.saveGState() + context.translateBy(x: translate.x, y: translate.y) + context.rotate(by: angleRadians) + + (text as NSString).draw(at: drawOffset, withAttributes: attributes) + + context.restoreGState() + } + else + { + if anchor.x != 0.0 || anchor.y != 0.0 + { + let size = text.size(withAttributes: attributes) + + drawOffset.x = -size.width * anchor.x + drawOffset.y = -size.height * anchor.y + } + + drawOffset.x += point.x + drawOffset.y += point.y + + (text as NSString).draw(at: drawOffset, withAttributes: attributes) + } + + NSUIGraphicsPopContext() + } + + internal class func drawMultilineText(context: CGContext, text: String, knownTextSize: CGSize, point: CGPoint, attributes: [NSAttributedString.Key : Any]?, constrainedToSize: CGSize, anchor: CGPoint, angleRadians: CGFloat) + { + var rect = CGRect(origin: CGPoint(), size: knownTextSize) + + NSUIGraphicsPushContext(context) + + if angleRadians != 0.0 + { + // Move the text drawing rect in a way that it always rotates around its center + rect.origin.x = -knownTextSize.width * 0.5 + rect.origin.y = -knownTextSize.height * 0.5 + + var translate = point + + // Move the "outer" rect relative to the anchor, assuming its centered + if anchor.x != 0.5 || anchor.y != 0.5 + { + let rotatedSize = knownTextSize.rotatedBy(radians: angleRadians) + + translate.x -= rotatedSize.width * (anchor.x - 0.5) + translate.y -= rotatedSize.height * (anchor.y - 0.5) + } + + context.saveGState() + context.translateBy(x: translate.x, y: translate.y) + context.rotate(by: angleRadians) + + (text as NSString).draw(with: rect, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) + + context.restoreGState() + } + else + { + if anchor.x != 0.0 || anchor.y != 0.0 + { + rect.origin.x = -knownTextSize.width * anchor.x + rect.origin.y = -knownTextSize.height * anchor.y + } + + rect.origin.x += point.x + rect.origin.y += point.y + + (text as NSString).draw(with: rect, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) + } + + NSUIGraphicsPopContext() + } + + internal class func drawMultilineText(context: CGContext, text: String, point: CGPoint, attributes: [NSAttributedString.Key : Any]?, constrainedToSize: CGSize, anchor: CGPoint, angleRadians: CGFloat) + { + let rect = text.boundingRect(with: constrainedToSize, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) + drawMultilineText(context: context, text: text, knownTextSize: rect.size, point: point, attributes: attributes, constrainedToSize: constrainedToSize, anchor: anchor, angleRadians: angleRadians) + } + + private class func generateDefaultValueFormatter() -> IValueFormatter + { + let formatter = DefaultValueFormatter(decimals: 1) + return formatter + } + + /// - Returns: The default value formatter used for all chart components that needs a default + open class func defaultValueFormatter() -> IValueFormatter + { + return _defaultValueFormatter + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Utils/Fill.swift b/dydx/Pods/Charts/Source/Charts/Utils/Fill.swift new file mode 100644 index 000000000..1294b3efc --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Utils/Fill.swift @@ -0,0 +1,323 @@ +// +// Fill.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(ChartFillType) +public enum FillType: Int +{ + case empty + case color + case linearGradient + case radialGradient + case image + case tiledImage + case layer +} + +@objc(ChartFill) +open class Fill: NSObject +{ + private var _type: FillType = FillType.empty + private var _color: CGColor? + private var _gradient: CGGradient? + private var _gradientAngle: CGFloat = 0.0 + private var _gradientStartOffsetPercent: CGPoint = CGPoint() + private var _gradientStartRadiusPercent: CGFloat = 0.0 + private var _gradientEndOffsetPercent: CGPoint = CGPoint() + private var _gradientEndRadiusPercent: CGFloat = 0.0 + private var _image: CGImage? + private var _layer: CGLayer? + + // MARK: Properties + + @objc open var type: FillType + { + return _type + } + + @objc open var color: CGColor? + { + return _color + } + + @objc open var gradient: CGGradient? + { + return _gradient + } + + @objc open var gradientAngle: CGFloat + { + return _gradientAngle + } + + @objc open var gradientStartOffsetPercent: CGPoint + { + return _gradientStartOffsetPercent + } + + @objc open var gradientStartRadiusPercent: CGFloat + { + return _gradientStartRadiusPercent + } + + @objc open var gradientEndOffsetPercent: CGPoint + { + return _gradientEndOffsetPercent + } + + @objc open var gradientEndRadiusPercent: CGFloat + { + return _gradientEndRadiusPercent + } + + @objc open var image: CGImage? + { + return _image + } + + @objc open var layer: CGLayer? + { + return _layer + } + + // MARK: Constructors + + public override init() + { + } + + @objc public init(CGColor: CGColor) + { + _type = .color + _color = CGColor + } + + @objc public convenience init(color: NSUIColor) + { + self.init(CGColor: color.cgColor) + } + + @objc public init(linearGradient: CGGradient, angle: CGFloat) + { + _type = .linearGradient + _gradient = linearGradient + _gradientAngle = angle + } + + @objc public init( + radialGradient: CGGradient, + startOffsetPercent: CGPoint, + startRadiusPercent: CGFloat, + endOffsetPercent: CGPoint, + endRadiusPercent: CGFloat + ) + { + _type = .radialGradient + _gradient = radialGradient + _gradientStartOffsetPercent = startOffsetPercent + _gradientStartRadiusPercent = startRadiusPercent + _gradientEndOffsetPercent = endOffsetPercent + _gradientEndRadiusPercent = endRadiusPercent + } + + @objc public convenience init(radialGradient: CGGradient) + { + self.init( + radialGradient: radialGradient, + startOffsetPercent: CGPoint(x: 0.0, y: 0.0), + startRadiusPercent: 0.0, + endOffsetPercent: CGPoint(x: 0.0, y: 0.0), + endRadiusPercent: 1.0 + ) + } + + @objc public init(CGImage: CGImage, tiled: Bool) + { + _type = tiled ? .tiledImage : .image + _image = CGImage + } + + @objc public convenience init(image: NSUIImage, tiled: Bool) + { + self.init(CGImage: image.cgImage!, tiled: tiled) + } + + @objc public convenience init(CGImage: CGImage) + { + self.init(CGImage: CGImage, tiled: false) + } + + @objc public convenience init(image: NSUIImage) + { + self.init(image: image, tiled: false) + } + + @objc public init(CGLayer: CGLayer) + { + _type = .layer + _layer = CGLayer + } + + // MARK: Constructors + + @objc open class func fillWithCGColor(_ CGColor: CGColor) -> Fill + { + return Fill(CGColor: CGColor) + } + + @objc open class func fillWithColor(_ color: NSUIColor) -> Fill + { + return Fill(color: color) + } + + @objc open class func fillWithLinearGradient( + _ linearGradient: CGGradient, + angle: CGFloat) -> Fill + { + return Fill(linearGradient: linearGradient, angle: angle) + } + + @objc open class func fillWithRadialGradient( + _ radialGradient: CGGradient, + startOffsetPercent: CGPoint, + startRadiusPercent: CGFloat, + endOffsetPercent: CGPoint, + endRadiusPercent: CGFloat + ) -> Fill + { + return Fill( + radialGradient: radialGradient, + startOffsetPercent: startOffsetPercent, + startRadiusPercent: startRadiusPercent, + endOffsetPercent: endOffsetPercent, + endRadiusPercent: endRadiusPercent + ) + } + + @objc open class func fillWithRadialGradient(_ radialGradient: CGGradient) -> Fill + { + return Fill(radialGradient: radialGradient) + } + + @objc open class func fillWithCGImage(_ CGImage: CGImage, tiled: Bool) -> Fill + { + return Fill(CGImage: CGImage, tiled: tiled) + } + + @objc open class func fillWithImage(_ image: NSUIImage, tiled: Bool) -> Fill + { + return Fill(image: image, tiled: tiled) + } + + @objc open class func fillWithCGImage(_ CGImage: CGImage) -> Fill + { + return Fill(CGImage: CGImage) + } + + @objc open class func fillWithImage(_ image: NSUIImage) -> Fill + { + return Fill(image: image) + } + + @objc open class func fillWithCGLayer(_ CGLayer: CGLayer) -> Fill + { + return Fill(CGLayer: CGLayer) + } + + // MARK: Drawing code + + /// Draws the provided path in filled mode with the provided area + @objc open func fillPath( + context: CGContext, + rect: CGRect) + { + let fillType = _type + if fillType == .empty + { + return + } + + context.saveGState() + + switch fillType + { + case .color: + + context.setFillColor(_color!) + context.fillPath() + + case .image: + + context.clip() + context.draw(_image!, in: rect) + + case .tiledImage: + + context.clip() + context.draw(_image!, in: rect, byTiling: true) + + case .layer: + + context.clip() + context.draw(_layer!, in: rect) + + case .linearGradient: + + let radians = (360.0 - _gradientAngle).DEG2RAD + let centerPoint = CGPoint(x: rect.midX, y: rect.midY) + let xAngleDelta = cos(radians) * rect.width / 2.0 + let yAngleDelta = sin(radians) * rect.height / 2.0 + let startPoint = CGPoint( + x: centerPoint.x - xAngleDelta, + y: centerPoint.y - yAngleDelta + ) + let endPoint = CGPoint( + x: centerPoint.x + xAngleDelta, + y: centerPoint.y + yAngleDelta + ) + + context.clip() + context.drawLinearGradient(_gradient!, + start: startPoint, + end: endPoint, + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation] + ) + + case .radialGradient: + + let centerPoint = CGPoint(x: rect.midX, y: rect.midY) + let radius = max(rect.width, rect.height) / 2.0 + + context.clip() + context.drawRadialGradient(_gradient!, + startCenter: CGPoint( + x: centerPoint.x + rect.width * _gradientStartOffsetPercent.x, + y: centerPoint.y + rect.height * _gradientStartOffsetPercent.y + ), + startRadius: radius * _gradientStartRadiusPercent, + endCenter: CGPoint( + x: centerPoint.x + rect.width * _gradientEndOffsetPercent.x, + y: centerPoint.y + rect.height * _gradientEndOffsetPercent.y + ), + endRadius: radius * _gradientEndRadiusPercent, + options: [.drawsAfterEndLocation, .drawsBeforeStartLocation] + ) + + case .empty: + break + } + + context.restoreGState() + } + +} diff --git a/dydx/Pods/Charts/Source/Charts/Utils/Platform+Accessibility.swift b/dydx/Pods/Charts/Source/Charts/Utils/Platform+Accessibility.swift new file mode 100644 index 000000000..35668eaba --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Utils/Platform+Accessibility.swift @@ -0,0 +1,215 @@ +// +// Platform+Accessibility.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation + +#if os(iOS) || os(tvOS) +#if canImport(UIKit) + import UIKit +#endif + +internal func accessibilityPostLayoutChangedNotification(withElement element: Any? = nil) +{ + UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: element) +} + +internal func accessibilityPostScreenChangedNotification(withElement element: Any? = nil) +{ + UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: element) +} + +/// A simple abstraction over UIAccessibilityElement and NSAccessibilityElement. +open class NSUIAccessibilityElement: UIAccessibilityElement +{ + private weak var containerView: UIView? + + final var isHeader: Bool = false + { + didSet + { + accessibilityTraits = isHeader ? UIAccessibilityTraits.header : UIAccessibilityTraits.none + } + } + + final var isSelected: Bool = false + { + didSet + { + accessibilityTraits = isSelected ? UIAccessibilityTraits.selected : UIAccessibilityTraits.none + } + } + + override public init(accessibilityContainer container: Any) + { + // We can force unwrap since all chart views are subclasses of UIView + containerView = (container as! UIView) + super.init(accessibilityContainer: container) + } + + override open var accessibilityFrame: CGRect + { + get + { + return super.accessibilityFrame + } + + set + { + guard let containerView = containerView else { return } + super.accessibilityFrame = containerView.convert(newValue, to: UIScreen.main.coordinateSpace) + } + } +} + +extension NSUIView +{ + /// An array of accessibilityElements that is used to implement UIAccessibilityContainer internally. + /// Subclasses **MUST** override this with an array of such elements. + @objc open func accessibilityChildren() -> [Any]? + { + return nil + } + + public final override var isAccessibilityElement: Bool + { + get { return false } // Return false here, so we can make individual elements accessible + set { } + } + + open override func accessibilityElementCount() -> Int + { + return accessibilityChildren()?.count ?? 0 + } + + open override func accessibilityElement(at index: Int) -> Any? + { + return accessibilityChildren()?[index] + } + + open override func index(ofAccessibilityElement element: Any) -> Int + { + guard let axElement = element as? NSUIAccessibilityElement else { return NSNotFound } + return (accessibilityChildren() as? [NSUIAccessibilityElement])? + .firstIndex(of: axElement) ?? NSNotFound + } +} + +#endif + +#if os(OSX) + +#if canImport(AppKit) +import AppKit +#endif + +internal func accessibilityPostLayoutChangedNotification(withElement element: Any? = nil) +{ + guard let validElement = element else { return } + NSAccessibility.post(element: validElement, notification: .layoutChanged) +} + +internal func accessibilityPostScreenChangedNotification(withElement element: Any? = nil) +{ + // Placeholder +} + +/// A simple abstraction over UIAccessibilityElement and NSAccessibilityElement. +open class NSUIAccessibilityElement: NSAccessibilityElement +{ + private weak var containerView: NSView? + + final var isHeader: Bool = false + { + didSet + { + setAccessibilityRole(isHeader ? .staticText : .none) + } + } + + final var isSelected: Bool = false + { + didSet + { + setAccessibilitySelected(isSelected) + } + } + + open var accessibilityLabel: String + { + get + { + return accessibilityLabel() ?? "" + } + + set + { + setAccessibilityLabel(newValue) + } + } + + open var accessibilityFrame: NSRect + { + get + { + return accessibilityFrame() + } + + set + { + guard let containerView = containerView else { return } + + let bounds = NSAccessibility.screenRect(fromView: containerView, rect: newValue) + + // This works, but won't auto update if the window is resized or moved. + // setAccessibilityFrame(bounds) + + // using FrameInParentSpace allows for automatic updating of frame when windows are moved and resized. + // However, there seems to be a bug right now where using it causes an offset in the frame. + // This is a slightly hacky workaround that calculates the offset and removes it from frame calculation. + setAccessibilityFrameInParentSpace(bounds) + let axFrame = accessibilityFrame() + let widthOffset = abs(axFrame.origin.x - bounds.origin.x) + let heightOffset = abs(axFrame.origin.y - bounds.origin.y) + let rect = NSRect(x: bounds.origin.x - widthOffset, + y: bounds.origin.y - heightOffset, + width: bounds.width, + height: bounds.height) + setAccessibilityFrameInParentSpace(rect) + } + } + + public init(accessibilityContainer container: Any) + { + // We can force unwrap since all chart views are subclasses of NSView + containerView = (container as! NSView) + + super.init() + + setAccessibilityParent(containerView) + setAccessibilityRole(.row) + } +} + +/// - Note: setAccessibilityRole(.list) is called at init. See Platform.swift. +extension NSUIView: NSAccessibilityGroup +{ + open override func accessibilityLabel() -> String? + { + return "Chart View" + } + + open override func accessibilityRows() -> [Any]? + { + return accessibilityChildren() + } +} + +#endif diff --git a/dydx/Pods/Charts/Source/Charts/Utils/Platform+Color.swift b/dydx/Pods/Charts/Source/Charts/Utils/Platform+Color.swift new file mode 100644 index 000000000..02bff08ed --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Utils/Platform+Color.swift @@ -0,0 +1,57 @@ +// +// Platform+Color.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +#if canImport(UIKit) +import UIKit + +public typealias NSUIColor = UIColor +private func fetchLabelColor() -> UIColor +{ + if #available(iOS 13, tvOS 13, *) + { + return .label + } + else + { + return .black + } +} +private let labelColor: UIColor = fetchLabelColor() + +extension UIColor +{ + static var labelOrBlack: UIColor { labelColor } +} +#endif + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) + +import AppKit + +public typealias NSUIColor = NSColor +private func fetchLabelColor() -> NSColor +{ + if #available(macOS 10.14, *) + { + return .labelColor + } + else + { + return .black + } +} +private let labelColor: NSColor = fetchLabelColor() + +extension NSColor +{ + static var labelOrBlack: NSColor { labelColor } +} +#endif diff --git a/dydx/Pods/Charts/Source/Charts/Utils/Platform+Gestures.swift b/dydx/Pods/Charts/Source/Charts/Utils/Platform+Gestures.swift new file mode 100644 index 000000000..e22e16887 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Utils/Platform+Gestures.swift @@ -0,0 +1,172 @@ +// +// Platform+Gestures.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +// MARK: - UIKit +#if canImport(UIKit) +import UIKit + +public typealias NSUIGestureRecognizer = UIGestureRecognizer +public typealias NSUIGestureRecognizerState = UIGestureRecognizer.State +public typealias NSUIGestureRecognizerDelegate = UIGestureRecognizerDelegate +public typealias NSUITapGestureRecognizer = UITapGestureRecognizer +public typealias NSUIPanGestureRecognizer = UIPanGestureRecognizer + +extension NSUITapGestureRecognizer +{ + @objc final func nsuiNumberOfTouches() -> Int + { + return numberOfTouches + } + + @objc final var nsuiNumberOfTapsRequired: Int + { + get + { + return self.numberOfTapsRequired + } + set + { + self.numberOfTapsRequired = newValue + } + } +} + +extension NSUIPanGestureRecognizer +{ + @objc final func nsuiNumberOfTouches() -> Int + { + return numberOfTouches + } + + @objc final func nsuiLocationOfTouch(_ touch: Int, inView: UIView?) -> CGPoint + { + return super.location(ofTouch: touch, in: inView) + } +} + +#if !os(tvOS) +public typealias NSUIPinchGestureRecognizer = UIPinchGestureRecognizer +public typealias NSUIRotationGestureRecognizer = UIRotationGestureRecognizer + +extension NSUIRotationGestureRecognizer +{ + @objc final var nsuiRotation: CGFloat + { + get { return rotation } + set { rotation = newValue } + } +} + +extension NSUIPinchGestureRecognizer +{ + @objc final var nsuiScale: CGFloat + { + get + { + return scale + } + set + { + scale = newValue + } + } + + @objc final func nsuiLocationOfTouch(_ touch: Int, inView: UIView?) -> CGPoint + { + return super.location(ofTouch: touch, in: inView) + } +} +#endif +#endif + +// MARK: - AppKit +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit + +public typealias NSUIGestureRecognizer = NSGestureRecognizer +public typealias NSUIGestureRecognizerState = NSGestureRecognizer.State +public typealias NSUIGestureRecognizerDelegate = NSGestureRecognizerDelegate +public typealias NSUITapGestureRecognizer = NSClickGestureRecognizer +public typealias NSUIPanGestureRecognizer = NSPanGestureRecognizer +public typealias NSUIPinchGestureRecognizer = NSMagnificationGestureRecognizer +public typealias NSUIRotationGestureRecognizer = NSRotationGestureRecognizer + +/** The 'tap' gesture is mapped to clicks. */ +extension NSUITapGestureRecognizer +{ + final func nsuiNumberOfTouches() -> Int + { + return 1 + } + + final var nsuiNumberOfTapsRequired: Int + { + get + { + return self.numberOfClicksRequired + } + set + { + self.numberOfClicksRequired = newValue + } + } +} + +extension NSUIPanGestureRecognizer +{ + final func nsuiNumberOfTouches() -> Int + { + return 1 + } + + /// FIXME: Currently there are no more than 1 touch in OSX gestures, and not way to create custom touch gestures. + final func nsuiLocationOfTouch(_ touch: Int, inView: NSView?) -> NSPoint + { + return super.location(in: inView) + } +} + +extension NSUIRotationGestureRecognizer +{ + /// FIXME: Currently there are no velocities in OSX gestures, and not way to create custom touch gestures. + final var velocity: CGFloat + { + return 0.1 + } + + final var nsuiRotation: CGFloat + { + get { return -rotation } + set { rotation = -newValue } + } +} + +extension NSUIPinchGestureRecognizer +{ + final var nsuiScale: CGFloat + { + get + { + return magnification + 1.0 + } + set + { + magnification = newValue - 1.0 + } + } + + /// FIXME: Currently there are no more than 1 touch in OSX gestures, and not way to create custom touch gestures. + final func nsuiLocationOfTouch(_ touch: Int, inView view: NSView?) -> NSPoint + { + return super.location(in: view) + } +} +#endif diff --git a/dydx/Pods/Charts/Source/Charts/Utils/Platform+Graphics.swift b/dydx/Pods/Charts/Source/Charts/Utils/Platform+Graphics.swift new file mode 100644 index 000000000..a8427a4fb --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Utils/Platform+Graphics.swift @@ -0,0 +1,163 @@ +// +// Platform+Graphics.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +enum Orientation +{ + case portrait, landscape +} + +extension CGSize +{ + var orientation: Orientation { return width > height ? .landscape : .portrait } +} + +extension CGRect +{ + var orientation: Orientation { size.orientation } +} + +// MARK: - UIKit +#if canImport(UIKit) +import UIKit + +func NSUIGraphicsGetCurrentContext() -> CGContext? +{ + return UIGraphicsGetCurrentContext() +} + +func NSUIGraphicsGetImageFromCurrentImageContext() -> NSUIImage! +{ + return UIGraphicsGetImageFromCurrentImageContext() +} + +func NSUIGraphicsPushContext(_ context: CGContext) +{ + UIGraphicsPushContext(context) +} + +func NSUIGraphicsPopContext() +{ + UIGraphicsPopContext() +} + +func NSUIGraphicsEndImageContext() +{ + UIGraphicsEndImageContext() +} + +func NSUIImagePNGRepresentation(_ image: NSUIImage) -> Data? +{ + return image.pngData() +} + +func NSUIImageJPEGRepresentation(_ image: NSUIImage, _ quality: CGFloat = 0.8) -> Data? +{ + return image.jpegData(compressionQuality: quality) +} + +func NSUIGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat) +{ + UIGraphicsBeginImageContextWithOptions(size, opaque, scale) +} +#endif + +// MARK: - AppKit +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit + +func NSUIGraphicsGetCurrentContext() -> CGContext? +{ + return NSGraphicsContext.current?.cgContext +} + +func NSUIGraphicsPushContext(_ context: CGContext) +{ + let cx = NSGraphicsContext(cgContext: context, flipped: true) + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = cx +} + +func NSUIGraphicsPopContext() +{ + NSGraphicsContext.restoreGraphicsState() +} + +func NSUIImagePNGRepresentation(_ image: NSUIImage) -> Data? +{ + image.lockFocus() + let rep = NSBitmapImageRep(focusedViewRect: NSMakeRect(0, 0, image.size.width, image.size.height)) + image.unlockFocus() + return rep?.representation(using: .png, properties: [:]) +} + +func NSUIImageJPEGRepresentation(_ image: NSUIImage, _ quality: CGFloat = 0.9) -> Data? +{ + image.lockFocus() + let rep = NSBitmapImageRep(focusedViewRect: NSMakeRect(0, 0, image.size.width, image.size.height)) + image.unlockFocus() + return rep?.representation(using: .jpeg, properties: [NSBitmapImageRep.PropertyKey.compressionFactor: quality]) +} + +private var imageContextStack: [CGFloat] = [] + +func NSUIGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat) +{ + var scale = scale + if scale == 0.0 + { + scale = NSScreen.main?.backingScaleFactor ?? 1.0 + } + + let width = Int(size.width * scale) + let height = Int(size.height * scale) + + if width > 0 && height > 0 + { + imageContextStack.append(scale) + + let colorSpace = CGColorSpaceCreateDeviceRGB() + + guard let ctx = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: 4*width, space: colorSpace, bitmapInfo: (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) + else { return } + + ctx.concatenate(CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(height))) + ctx.scaleBy(x: scale, y: scale) + NSUIGraphicsPushContext(ctx) + } +} + +func NSUIGraphicsGetImageFromCurrentImageContext() -> NSUIImage? +{ + if !imageContextStack.isEmpty + { + guard let ctx = NSUIGraphicsGetCurrentContext() + else { return nil } + + let scale = imageContextStack.last! + if let theCGImage = ctx.makeImage() + { + let size = CGSize(width: CGFloat(ctx.width) / scale, height: CGFloat(ctx.height) / scale) + let image = NSImage(cgImage: theCGImage, size: size) + return image + } + } + return nil +} + +func NSUIGraphicsEndImageContext() +{ + if imageContextStack.last != nil + { + imageContextStack.removeLast() + NSUIGraphicsPopContext() + } +} +#endif diff --git a/dydx/Pods/Charts/Source/Charts/Utils/Platform+Touch Handling.swift b/dydx/Pods/Charts/Source/Charts/Utils/Platform+Touch Handling.swift new file mode 100644 index 000000000..acb402ecb --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Utils/Platform+Touch Handling.swift @@ -0,0 +1,134 @@ +// +// Platform+Touch Handling.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +#if canImport(UIKit) +import UIKit + +public typealias NSUIEvent = UIEvent +public typealias NSUITouch = UITouch + +@objc +extension NSUIView { + public final override func touchesBegan(_ touches: Set, with event: NSUIEvent?) + { + self.nsuiTouchesBegan(touches, withEvent: event) + } + + public final override func touchesMoved(_ touches: Set, with event: NSUIEvent?) + { + self.nsuiTouchesMoved(touches, withEvent: event) + } + + public final override func touchesEnded(_ touches: Set, with event: NSUIEvent?) + { + self.nsuiTouchesEnded(touches, withEvent: event) + } + + public final override func touchesCancelled(_ touches: Set, with event: NSUIEvent?) + { + self.nsuiTouchesCancelled(touches, withEvent: event) + } + + open func nsuiTouchesBegan(_ touches: Set, withEvent event: NSUIEvent?) + { + super.touchesBegan(touches, with: event!) + } + + open func nsuiTouchesMoved(_ touches: Set, withEvent event: NSUIEvent?) + { + super.touchesMoved(touches, with: event!) + } + + open func nsuiTouchesEnded(_ touches: Set, withEvent event: NSUIEvent?) + { + super.touchesEnded(touches, with: event!) + } + + open func nsuiTouchesCancelled(_ touches: Set?, withEvent event: NSUIEvent?) + { + super.touchesCancelled(touches!, with: event!) + } +} + +extension UIView +{ + @objc final var nsuiGestureRecognizers: [NSUIGestureRecognizer]? + { + return self.gestureRecognizers + } +} +#endif + + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit + +public typealias NSUIEvent = NSEvent +public typealias NSUITouch = NSTouch + +@objc +extension NSUIView +{ + public final override func touchesBegan(with event: NSEvent) + { + self.nsuiTouchesBegan(event.touches(matching: .any, in: self), withEvent: event) + } + + public final override func touchesEnded(with event: NSEvent) + { + self.nsuiTouchesEnded(event.touches(matching: .any, in: self), withEvent: event) + } + + public final override func touchesMoved(with event: NSEvent) + { + self.nsuiTouchesMoved(event.touches(matching: .any, in: self), withEvent: event) + } + + open override func touchesCancelled(with event: NSEvent) + { + self.nsuiTouchesCancelled(event.touches(matching: .any, in: self), withEvent: event) + } + + open func nsuiTouchesBegan(_ touches: Set, withEvent event: NSUIEvent?) + { + super.touchesBegan(with: event!) + } + + open func nsuiTouchesMoved(_ touches: Set, withEvent event: NSUIEvent?) + { + super.touchesMoved(with: event!) + } + + open func nsuiTouchesEnded(_ touches: Set, withEvent event: NSUIEvent?) + { + super.touchesEnded(with: event!) + } + + open func nsuiTouchesCancelled(_ touches: Set?, withEvent event: NSUIEvent?) + { + super.touchesCancelled(with: event!) + } +} + +extension NSTouch +{ + /** Touch locations on OS X are relative to the trackpad, whereas on iOS they are actually *on* the view. */ + func locationInView(view: NSView) -> NSPoint + { + let n = self.normalizedPosition + let b = view.bounds + return NSPoint( + x: b.origin.x + b.size.width * n.x, + y: b.origin.y + b.size.height * n.y + ) + } +} +#endif diff --git a/dydx/Pods/Charts/Source/Charts/Utils/Platform.swift b/dydx/Pods/Charts/Source/Charts/Utils/Platform.swift new file mode 100644 index 000000000..3722647d3 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Utils/Platform.swift @@ -0,0 +1,240 @@ +import Foundation + +/** This file provides a thin abstraction layer atop of UIKit (iOS, tvOS) and Cocoa (OS X). The two APIs are very much + alike, and for the chart library's usage of the APIs it is often sufficient to typealias one to the other. The NSUI* + types are aliased to either their UI* implementation (on iOS) or their NS* implementation (on OS X). */ +#if os(iOS) || os(tvOS) +#if canImport(UIKit) + import UIKit +#endif + +public typealias NSUIFont = UIFont +public typealias NSUIImage = UIImage +public typealias NSUIScrollView = UIScrollView +public typealias NSUIScreen = UIScreen +public typealias NSUIDisplayLink = CADisplayLink + +open class NSUIView: UIView +{ + @objc var nsuiLayer: CALayer? + { + return self.layer + } +} + +extension UIScrollView +{ + @objc var nsuiIsScrollEnabled: Bool + { + get { return isScrollEnabled } + set { isScrollEnabled = newValue } + } +} + +extension UIScreen +{ + @objc final var nsuiScale: CGFloat + { + return self.scale + } +} + +#endif + +#if os(OSX) +import Cocoa +import Quartz + +public typealias NSUIFont = NSFont +public typealias NSUIImage = NSImage +public typealias NSUIScrollView = NSScrollView +public typealias NSUIScreen = NSScreen + +/** On OS X there is no CADisplayLink. Use a 60 fps timer to render the animations. */ +public class NSUIDisplayLink +{ + private var timer: Timer? + private var displayLink: CVDisplayLink? + private var _timestamp: CFTimeInterval = 0.0 + + private weak var _target: AnyObject? + private var _selector: Selector + + public var timestamp: CFTimeInterval + { + return _timestamp + } + + init(target: Any, selector: Selector) + { + _target = target as AnyObject + _selector = selector + + if CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) == kCVReturnSuccess + { + + CVDisplayLinkSetOutputCallback(displayLink!, { (displayLink, inNow, inOutputTime, flagsIn, flagsOut, userData) -> CVReturn in + + let _self = unsafeBitCast(userData, to: NSUIDisplayLink.self) + + _self._timestamp = CFAbsoluteTimeGetCurrent() + _self._target?.performSelector(onMainThread: _self._selector, with: _self, waitUntilDone: false) + + return kCVReturnSuccess + }, Unmanaged.passUnretained(self).toOpaque()) + } + else + { + timer = Timer(timeInterval: 1.0 / 60.0, target: target, selector: selector, userInfo: nil, repeats: true) + } + } + + deinit + { + stop() + } + + open func add(to runloop: RunLoop, forMode mode: RunLoop.Mode) + { + if displayLink != nil + { + CVDisplayLinkStart(displayLink!) + } + else if timer != nil + { + runloop.add(timer!, forMode: mode) + } + } + + open func remove(from: RunLoop, forMode: RunLoop.Mode) + { + stop() + } + + private func stop() + { + if displayLink != nil + { + CVDisplayLinkStop(displayLink!) + } + if timer != nil + { + timer?.invalidate() + } + } +} + +extension NSView +{ + final var nsuiGestureRecognizers: [NSGestureRecognizer]? + { + return self.gestureRecognizers + } +} + +extension NSScrollView +{ + var nsuiIsScrollEnabled: Bool + { + get { return scrollEnabled } + set { scrollEnabled = newValue } + } +} + +open class NSUIView: NSView +{ + /// A private constant to set the accessibility role during initialization. + /// It ensures parity with the iOS element ordering as well as numbered counts of chart components. + /// (See Platform+Accessibility for details) + private let role: NSAccessibility.Role = .list + + public override init(frame frameRect: NSRect) + { + super.init(frame: frameRect) + setAccessibilityRole(role) + } + + required public init?(coder decoder: NSCoder) + { + super.init(coder: decoder) + setAccessibilityRole(role) + } + + public final override var isFlipped: Bool + { + return true + } + + func setNeedsDisplay() + { + self.setNeedsDisplay(self.bounds) + } + + + open var backgroundColor: NSUIColor? + { + get + { + return self.layer?.backgroundColor == nil + ? nil + : NSColor(cgColor: self.layer!.backgroundColor!) + } + set + { + self.wantsLayer = true + self.layer?.backgroundColor = newValue == nil ? nil : newValue!.cgColor + } + } + + final var nsuiLayer: CALayer? + { + return self.layer + } +} + +extension NSFont +{ + var lineHeight: CGFloat + { + // Not sure if this is right, but it looks okay + return self.boundingRectForFont.size.height + } +} + +extension NSScreen +{ + final var nsuiScale: CGFloat + { + return self.backingScaleFactor + } +} + +extension NSImage +{ + var cgImage: CGImage? + { + return self.cgImage(forProposedRect: nil, context: nil, hints: nil) + } +} + +extension NSScrollView +{ + /// NOTE: Unable to disable scrolling in macOS + var scrollEnabled: Bool + { + get + { + return true + } + set + { + } + } +} + +#endif + +extension NSUIScreen +{ + class var nsuiMain: NSUIScreen? { .main } +} diff --git a/dydx/Pods/Charts/Source/Charts/Utils/Transformer.swift b/dydx/Pods/Charts/Source/Charts/Utils/Transformer.swift new file mode 100644 index 000000000..b50ea823c --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Utils/Transformer.swift @@ -0,0 +1,170 @@ +// +// Transformer.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +/// Transformer class that contains all matrices and is responsible for transforming values into pixels on the screen and backwards. +@objc(ChartTransformer) +open class Transformer: NSObject +{ + /// matrix to map the values to the screen pixels + internal var _matrixValueToPx = CGAffineTransform.identity + + /// matrix for handling the different offsets of the chart + internal var _matrixOffset = CGAffineTransform.identity + + internal var _viewPortHandler: ViewPortHandler + + @objc public init(viewPortHandler: ViewPortHandler) + { + _viewPortHandler = viewPortHandler + } + + /// Prepares the matrix that transforms values to pixels. Calculates the scale factors from the charts size and offsets. + @objc open func prepareMatrixValuePx(chartXMin: Double, deltaX: CGFloat, deltaY: CGFloat, chartYMin: Double) + { + var scaleX = (_viewPortHandler.contentWidth / deltaX) + var scaleY = (_viewPortHandler.contentHeight / deltaY) + + if CGFloat.infinity == scaleX + { + scaleX = 0.0 + } + if CGFloat.infinity == scaleY + { + scaleY = 0.0 + } + + // setup all matrices + _matrixValueToPx = CGAffineTransform.identity + _matrixValueToPx = _matrixValueToPx.scaledBy(x: scaleX, y: -scaleY) + _matrixValueToPx = _matrixValueToPx.translatedBy(x: CGFloat(-chartXMin), y: CGFloat(-chartYMin)) + } + + /// Prepares the matrix that contains all offsets. + @objc open func prepareMatrixOffset(inverted: Bool) + { + if !inverted + { + _matrixOffset = CGAffineTransform(translationX: _viewPortHandler.offsetLeft, y: _viewPortHandler.chartHeight - _viewPortHandler.offsetBottom) + } + else + { + _matrixOffset = CGAffineTransform(scaleX: 1.0, y: -1.0) + _matrixOffset = _matrixOffset.translatedBy(x: _viewPortHandler.offsetLeft, y: -_viewPortHandler.offsetTop) + } + } + + /// Transform an array of points with all matrices. + // VERY IMPORTANT: Keep matrix order "value-touch-offset" when transforming. + open func pointValuesToPixel(_ points: inout [CGPoint]) + { + let trans = valueToPixelMatrix + points = points.map { $0.applying(trans) } + } + + open func pointValueToPixel(_ point: inout CGPoint) + { + point = point.applying(valueToPixelMatrix) + } + + @objc open func pixelForValues(x: Double, y: Double) -> CGPoint + { + return CGPoint(x: x, y: y).applying(valueToPixelMatrix) + } + + /// Transform a rectangle with all matrices. + open func rectValueToPixel(_ r: inout CGRect) + { + r = r.applying(valueToPixelMatrix) + } + + /// Transform a rectangle with all matrices with potential animation phases. + open func rectValueToPixel(_ r: inout CGRect, phaseY: Double) + { + // multiply the height of the rect with the phase + var bottom = r.origin.y + r.size.height + bottom *= CGFloat(phaseY) + let top = r.origin.y * CGFloat(phaseY) + r.size.height = bottom - top + r.origin.y = top + + r = r.applying(valueToPixelMatrix) + } + + /// Transform a rectangle with all matrices. + open func rectValueToPixelHorizontal(_ r: inout CGRect) + { + r = r.applying(valueToPixelMatrix) + } + + /// Transform a rectangle with all matrices with potential animation phases. + open func rectValueToPixelHorizontal(_ r: inout CGRect, phaseY: Double) + { + // multiply the height of the rect with the phase + let left = r.origin.x * CGFloat(phaseY) + let right = (r.origin.x + r.size.width) * CGFloat(phaseY) + r.size.width = right - left + r.origin.x = left + + r = r.applying(valueToPixelMatrix) + } + + /// transforms multiple rects with all matrices + open func rectValuesToPixel(_ rects: inout [CGRect]) + { + let trans = valueToPixelMatrix + rects = rects.map { $0.applying(trans) } + } + + /// Transforms the given array of touch points (pixels) into values on the chart. + open func pixelsToValues(_ pixels: inout [CGPoint]) + { + let trans = pixelToValueMatrix + pixels = pixels.map { $0.applying(trans) } + } + + /// Transforms the given touch point (pixels) into a value on the chart. + open func pixelToValues(_ pixel: inout CGPoint) + { + pixel = pixel.applying(pixelToValueMatrix) + } + + /// - Returns: The x and y values in the chart at the given touch point + /// (encapsulated in a CGPoint). This method transforms pixel coordinates to + /// coordinates / values in the chart. + @objc open func valueForTouchPoint(_ point: CGPoint) -> CGPoint + { + return point.applying(pixelToValueMatrix) + } + + /// - Returns: The x and y values in the chart at the given touch point + /// (x/y). This method transforms pixel coordinates to + /// coordinates / values in the chart. + @objc open func valueForTouchPoint(x: CGFloat, y: CGFloat) -> CGPoint + { + return CGPoint(x: x, y: y).applying(pixelToValueMatrix) + } + + @objc open var valueToPixelMatrix: CGAffineTransform + { + return + _matrixValueToPx.concatenating(_viewPortHandler.touchMatrix + ).concatenating(_matrixOffset + ) + } + + @objc open var pixelToValueMatrix: CGAffineTransform + { + return valueToPixelMatrix.inverted() + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Utils/TransformerHorizontalBarChart.swift b/dydx/Pods/Charts/Source/Charts/Utils/TransformerHorizontalBarChart.swift new file mode 100644 index 000000000..d7e657bd2 --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Utils/TransformerHorizontalBarChart.swift @@ -0,0 +1,32 @@ +// +// TransformerHorizontalBarChart.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +@objc(ChartTransformerHorizontalBarChart) +open class TransformerHorizontalBarChart: Transformer +{ + /// Prepares the matrix that contains all offsets. + open override func prepareMatrixOffset(inverted: Bool) + { + if !inverted + { + _matrixOffset = CGAffineTransform(translationX: _viewPortHandler.offsetLeft, y: _viewPortHandler.chartHeight - _viewPortHandler.offsetBottom) + } + else + { + _matrixOffset = CGAffineTransform(scaleX: -1.0, y: 1.0) + _matrixOffset = _matrixOffset.translatedBy(x: -(_viewPortHandler.chartWidth - _viewPortHandler.offsetRight), + y: _viewPortHandler.chartHeight - _viewPortHandler.offsetBottom) + } + } +} diff --git a/dydx/Pods/Charts/Source/Charts/Utils/ViewPortHandler.swift b/dydx/Pods/Charts/Source/Charts/Utils/ViewPortHandler.swift new file mode 100755 index 000000000..8916d068a --- /dev/null +++ b/dydx/Pods/Charts/Source/Charts/Utils/ViewPortHandler.swift @@ -0,0 +1,610 @@ +// +// ViewPortHandler.swift +// Charts +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/Charts +// + +import Foundation +import CoreGraphics + +/// Class that contains information about the charts current viewport settings, including offsets, scale & translation levels, ... +@objc(ChartViewPortHandler) +open class ViewPortHandler: NSObject +{ + /// matrix used for touch events + private var _touchMatrix = CGAffineTransform.identity + + /// this rectangle defines the area in which graph values can be drawn + private var _contentRect = CGRect() + + private var _chartWidth = CGFloat(0.0) + private var _chartHeight = CGFloat(0.0) + + /// minimum scale value on the y-axis + private var _minScaleY = CGFloat(1.0) + + /// maximum scale value on the y-axis + private var _maxScaleY = CGFloat.greatestFiniteMagnitude + + /// minimum scale value on the x-axis + private var _minScaleX = CGFloat(1.0) + + /// maximum scale value on the x-axis + private var _maxScaleX = CGFloat.greatestFiniteMagnitude + + /// contains the current scale factor of the x-axis + private var _scaleX = CGFloat(1.0) + + /// contains the current scale factor of the y-axis + private var _scaleY = CGFloat(1.0) + + /// current translation (drag distance) on the x-axis + private var _transX = CGFloat(0.0) + + /// current translation (drag distance) on the y-axis + private var _transY = CGFloat(0.0) + + /// offset that allows the chart to be dragged over its bounds on the x-axis + private var _transOffsetX = CGFloat(0.0) + + /// offset that allows the chart to be dragged over its bounds on the x-axis + private var _transOffsetY = CGFloat(0.0) + + /// Constructor - don't forget calling setChartDimens(...) + @objc public init(width: CGFloat, height: CGFloat) + { + super.init() + + setChartDimens(width: width, height: height) + } + + @objc open func setChartDimens(width: CGFloat, height: CGFloat) + { + let offsetLeft = self.offsetLeft + let offsetTop = self.offsetTop + let offsetRight = self.offsetRight + let offsetBottom = self.offsetBottom + + _chartHeight = height + _chartWidth = width + + restrainViewPort(offsetLeft: offsetLeft, offsetTop: offsetTop, offsetRight: offsetRight, offsetBottom: offsetBottom) + } + + @objc open var hasChartDimens: Bool + { + if _chartHeight > 0.0 && _chartWidth > 0.0 + { + return true + } + else + { + return false + } + } + + @objc open func restrainViewPort(offsetLeft: CGFloat, offsetTop: CGFloat, offsetRight: CGFloat, offsetBottom: CGFloat) + { + _contentRect.origin.x = offsetLeft + _contentRect.origin.y = offsetTop + _contentRect.size.width = _chartWidth - offsetLeft - offsetRight + _contentRect.size.height = _chartHeight - offsetBottom - offsetTop + } + + @objc open var offsetLeft: CGFloat + { + return _contentRect.origin.x + } + + @objc open var offsetRight: CGFloat + { + return _chartWidth - _contentRect.size.width - _contentRect.origin.x + } + + @objc open var offsetTop: CGFloat + { + return _contentRect.origin.y + } + + @objc open var offsetBottom: CGFloat + { + return _chartHeight - _contentRect.size.height - _contentRect.origin.y + } + + @objc open var contentTop: CGFloat + { + return _contentRect.origin.y + } + + @objc open var contentLeft: CGFloat + { + return _contentRect.origin.x + } + + @objc open var contentRight: CGFloat + { + return _contentRect.origin.x + _contentRect.size.width + } + + @objc open var contentBottom: CGFloat + { + return _contentRect.origin.y + _contentRect.size.height + } + + @objc open var contentWidth: CGFloat + { + return _contentRect.size.width + } + + @objc open var contentHeight: CGFloat + { + return _contentRect.size.height + } + + @objc open var contentRect: CGRect + { + return _contentRect + } + + @objc open var contentCenter: CGPoint + { + return CGPoint(x: _contentRect.origin.x + _contentRect.size.width / 2.0, y: _contentRect.origin.y + _contentRect.size.height / 2.0) + } + + @objc open var chartHeight: CGFloat + { + return _chartHeight + } + + @objc open var chartWidth: CGFloat + { + return _chartWidth + } + + // MARK: - Scaling/Panning etc. + + /// Zooms by the specified zoom factors. + @objc open func zoom(scaleX: CGFloat, scaleY: CGFloat) -> CGAffineTransform + { + return _touchMatrix.scaledBy(x: scaleX, y: scaleY) + } + + /// Zooms around the specified center + @objc open func zoom(scaleX: CGFloat, scaleY: CGFloat, x: CGFloat, y: CGFloat) -> CGAffineTransform + { + var matrix = _touchMatrix.translatedBy(x: x, y: y) + matrix = matrix.scaledBy(x: scaleX, y: scaleY) + matrix = matrix.translatedBy(x: -x, y: -y) + return matrix + } + + /// Zooms in by 1.4, x and y are the coordinates (in pixels) of the zoom center. + @objc open func zoomIn(x: CGFloat, y: CGFloat) -> CGAffineTransform + { + return zoom(scaleX: 1.4, scaleY: 1.4, x: x, y: y) + } + + /// Zooms out by 0.7, x and y are the coordinates (in pixels) of the zoom center. + @objc open func zoomOut(x: CGFloat, y: CGFloat) -> CGAffineTransform + { + return zoom(scaleX: 0.7, scaleY: 0.7, x: x, y: y) + } + + /// Zooms out to original size. + @objc open func resetZoom() -> CGAffineTransform + { + return zoom(scaleX: 1.0, scaleY: 1.0, x: 0.0, y: 0.0) + } + + /// Sets the scale factor to the specified values. + @objc open func setZoom(scaleX: CGFloat, scaleY: CGFloat) -> CGAffineTransform + { + var matrix = _touchMatrix + matrix.a = scaleX + matrix.d = scaleY + return matrix + } + + /// Sets the scale factor to the specified values. x and y is pivot. + @objc open func setZoom(scaleX: CGFloat, scaleY: CGFloat, x: CGFloat, y: CGFloat) -> CGAffineTransform + { + var matrix = _touchMatrix + matrix.a = 1.0 + matrix.d = 1.0 + matrix = matrix.translatedBy(x: x, y: y) + matrix = matrix.scaledBy(x: scaleX, y: scaleY) + matrix = matrix.translatedBy(x: -x, y: -y) + return matrix + } + + /// Resets all zooming and dragging and makes the chart fit exactly it's bounds. + @objc open func fitScreen() -> CGAffineTransform + { + _minScaleX = 1.0 + _minScaleY = 1.0 + + return CGAffineTransform.identity + } + + /// Translates to the specified point. + @objc open func translate(pt: CGPoint) -> CGAffineTransform + { + let translateX = pt.x - offsetLeft + let translateY = pt.y - offsetTop + + let matrix = _touchMatrix.concatenating(CGAffineTransform(translationX: -translateX, y: -translateY)) + + return matrix + } + + /// Centers the viewport around the specified position (x-index and y-value) in the chart. + /// Centering the viewport outside the bounds of the chart is not possible. + /// Makes most sense in combination with the setScaleMinima(...) method. + @objc open func centerViewPort(pt: CGPoint, chart: ChartViewBase) + { + let translateX = pt.x - offsetLeft + let translateY = pt.y - offsetTop + + let matrix = _touchMatrix.concatenating(CGAffineTransform(translationX: -translateX, y: -translateY)) + refresh(newMatrix: matrix, chart: chart, invalidate: true) + } + + /// call this method to refresh the graph with a given matrix + @objc @discardableResult open func refresh(newMatrix: CGAffineTransform, chart: ChartViewBase, invalidate: Bool) -> CGAffineTransform + { + _touchMatrix = newMatrix + + // make sure scale and translation are within their bounds + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + + chart.setNeedsDisplay() + + return _touchMatrix + } + + /// limits the maximum scale and X translation of the given matrix + private func limitTransAndScale(matrix: inout CGAffineTransform, content: CGRect?) + { + // min scale-x is 1 + _scaleX = min(max(_minScaleX, matrix.a), _maxScaleX) + + // min scale-y is 1 + _scaleY = min(max(_minScaleY, matrix.d), _maxScaleY) + + + var width: CGFloat = 0.0 + var height: CGFloat = 0.0 + + if content != nil + { + width = content!.width + height = content!.height + } + + let maxTransX = -width * (_scaleX - 1.0) + _transX = min(max(matrix.tx, maxTransX - _transOffsetX), _transOffsetX) + + let maxTransY = height * (_scaleY - 1.0) + _transY = max(min(matrix.ty, maxTransY + _transOffsetY), -_transOffsetY) + + matrix.tx = _transX + matrix.a = _scaleX + matrix.ty = _transY + matrix.d = _scaleY + } + + /// Sets the minimum scale factor for the x-axis + @objc open func setMinimumScaleX(_ xScale: CGFloat) + { + var newValue = xScale + + if newValue < 1.0 + { + newValue = 1.0 + } + + _minScaleX = newValue + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + } + + /// Sets the maximum scale factor for the x-axis + @objc open func setMaximumScaleX(_ xScale: CGFloat) + { + var newValue = xScale + + if newValue == 0.0 + { + newValue = CGFloat.greatestFiniteMagnitude + } + + _maxScaleX = newValue + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + } + + /// Sets the minimum and maximum scale factors for the x-axis + @objc open func setMinMaxScaleX(minScaleX: CGFloat, maxScaleX: CGFloat) + { + var newMin = minScaleX + var newMax = maxScaleX + + if newMin < 1.0 + { + newMin = 1.0 + } + if newMax == 0.0 + { + newMax = CGFloat.greatestFiniteMagnitude + } + + _minScaleX = newMin + _maxScaleX = maxScaleX + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + } + + /// Sets the minimum scale factor for the y-axis + @objc open func setMinimumScaleY(_ yScale: CGFloat) + { + var newValue = yScale + + if newValue < 1.0 + { + newValue = 1.0 + } + + _minScaleY = newValue + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + } + + /// Sets the maximum scale factor for the y-axis + @objc open func setMaximumScaleY(_ yScale: CGFloat) + { + var newValue = yScale + + if newValue == 0.0 + { + newValue = CGFloat.greatestFiniteMagnitude + } + + _maxScaleY = newValue + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + } + + @objc open func setMinMaxScaleY(minScaleY: CGFloat, maxScaleY: CGFloat) + { + var minScaleY = minScaleY, maxScaleY = maxScaleY + + if minScaleY < 1.0 + { + minScaleY = 1.0 + } + + if maxScaleY == 0.0 + { + maxScaleY = CGFloat.greatestFiniteMagnitude + } + + _minScaleY = minScaleY + _maxScaleY = maxScaleY + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect) + } + + @objc open var touchMatrix: CGAffineTransform + { + return _touchMatrix + } + + // MARK: - Boundaries Check + + @objc open func isInBoundsX(_ x: CGFloat) -> Bool + { + return isInBoundsLeft(x) && isInBoundsRight(x) + } + + @objc open func isInBoundsY(_ y: CGFloat) -> Bool + { + return isInBoundsTop(y) && isInBoundsBottom(y) + } + + /** + A method to check whether coordinate lies within the viewport. + + - Parameters: + - point: a coordinate. + */ + @objc open func isInBounds(point: CGPoint) -> Bool + { + return isInBounds(x: point.x, y: point.y) + } + + @objc open func isInBounds(x: CGFloat, y: CGFloat) -> Bool + { + return isInBoundsX(x) && isInBoundsY(y) + } + + @objc open func isInBoundsLeft(_ x: CGFloat) -> Bool + { + return _contentRect.origin.x <= x + 1.0 + } + + @objc open func isInBoundsRight(_ x: CGFloat) -> Bool + { + let x = floor(x * 100.0) / 100.0 + return (_contentRect.origin.x + _contentRect.size.width) >= x - 1.0 + } + + @objc open func isInBoundsTop(_ y: CGFloat) -> Bool + { + return _contentRect.origin.y <= y + } + + @objc open func isInBoundsBottom(_ y: CGFloat) -> Bool + { + let normalizedY = floor(y * 100.0) / 100.0 + return (_contentRect.origin.y + _contentRect.size.height) >= normalizedY + } + + /** + A method to check whether a line between two coordinates intersects with the view port by using a linear function. + + Linear function (calculus): `y = ax + b` + + Note: this method will not check for collision with the right edge of the view port, as we assume lines run from left + to right (e.g. `startPoint < endPoint`). + + - Parameters: + - startPoint: the start coordinate of the line. + - endPoint: the end coordinate of the line. + */ + @objc open func isIntersectingLine(from startPoint: CGPoint, to endPoint: CGPoint) -> Bool + { + // If start- and/or endpoint fall within the viewport, bail out early. + if isInBounds(point: startPoint) || isInBounds(point: endPoint) { return true } + // check if x in bound when it's a vertical line + if startPoint.x == endPoint.x { return isInBoundsX(startPoint.x) } + + // Calculate the slope (`a`) of the line (e.g. `a = (y2 - y1) / (x2 - x1)`). + let a = (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x) + // Calculate the y-correction (`b`) of the line (e.g. `b = y1 - (a * x1)`). + let b = startPoint.y - (a * startPoint.x) + + // Check for colission with the left edge of the view port (e.g. `y = (a * minX) + b`). + // if a is 0, it's a horizontal line; checking b here is still valid, as b is `point.y` all the time + if isInBoundsY((a * contentRect.minX) + b) { return true } + + // Skip unnecessary check for collision with the right edge of the view port + // (e.g. `y = (a * maxX) + b`), as such a line will either begin inside the view port, + // or intersect the left, top or bottom edges of the view port. Leaving this logic here for clarity's sake: + // if isInBoundsY((a * contentRect.maxX) + b) { return true } + + // While slope `a` can theoretically never be `0`, we should protect against division by zero. + guard a != 0 else { return false } + + // Check for collision with the bottom edge of the view port (e.g. `x = (maxY - b) / a`). + if isInBoundsX((contentRect.maxY - b) / a) { return true } + + // Check for collision with the top edge of the view port (e.g. `x = (minY - b) / a`). + if isInBoundsX((contentRect.minY - b) / a) { return true } + + // This line does not intersect the view port. + return false + } + + /// The current x-scale factor + @objc open var scaleX: CGFloat + { + return _scaleX + } + + /// The current y-scale factor + @objc open var scaleY: CGFloat + { + return _scaleY + } + + /// The minimum x-scale factor + @objc open var minScaleX: CGFloat + { + return _minScaleX + } + + /// The minimum y-scale factor + @objc open var minScaleY: CGFloat + { + return _minScaleY + } + + /// The minimum x-scale factor + @objc open var maxScaleX: CGFloat + { + return _maxScaleX + } + + /// The minimum y-scale factor + @objc open var maxScaleY: CGFloat + { + return _maxScaleY + } + + /// The translation (drag / pan) distance on the x-axis + @objc open var transX: CGFloat + { + return _transX + } + + /// The translation (drag / pan) distance on the y-axis + @objc open var transY: CGFloat + { + return _transY + } + + /// if the chart is fully zoomed out, return true + @objc open var isFullyZoomedOut: Bool + { + return isFullyZoomedOutX && isFullyZoomedOutY + } + + /// `true` if the chart is fully zoomed out on it's y-axis (vertical). + @objc open var isFullyZoomedOutY: Bool + { + return !(_scaleY > _minScaleY || _minScaleY > 1.0) + } + + /// `true` if the chart is fully zoomed out on it's x-axis (horizontal). + @objc open var isFullyZoomedOutX: Bool + { + return !(_scaleX > _minScaleX || _minScaleX > 1.0) + } + + /// Set an offset in pixels that allows the user to drag the chart over it's bounds on the x-axis. + @objc open func setDragOffsetX(_ offset: CGFloat) + { + _transOffsetX = offset + } + + /// Set an offset in pixels that allows the user to drag the chart over it's bounds on the y-axis. + @objc open func setDragOffsetY(_ offset: CGFloat) + { + _transOffsetY = offset + } + + /// `true` if both drag offsets (x and y) are zero or smaller. + @objc open var hasNoDragOffset: Bool + { + return _transOffsetX <= 0.0 && _transOffsetY <= 0.0 + } + + /// `true` if the chart is not yet fully zoomed out on the x-axis + @objc open var canZoomOutMoreX: Bool + { + return _scaleX > _minScaleX + } + + /// `true` if the chart is not yet fully zoomed in on the x-axis + @objc open var canZoomInMoreX: Bool + { + return _scaleX < _maxScaleX + } + + /// `true` if the chart is not yet fully zoomed out on the y-axis + @objc open var canZoomOutMoreY: Bool + { + return _scaleY > _minScaleY + } + + /// `true` if the chart is not yet fully zoomed in on the y-axis + @objc open var canZoomInMoreY: Bool + { + return _scaleY < _maxScaleY + } +} diff --git a/dydx/Pods/CocoaLumberjack/CHANGELOG.md b/dydx/Pods/CocoaLumberjack/CHANGELOG.md new file mode 100644 index 000000000..7a519a3fa --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/CHANGELOG.md @@ -0,0 +1,638 @@ +# [3.8.5 - Xcode 15.3 on Mar 8th, 2024](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tags/3.8.5) + +### Public + +- Fix build failure due to privacy manifest when using static linking with CocoaPods (#1408) +- Allow custom mapping of `DDLogFlag` to `os_log_type_t`, fix default mapping for `DDLogFlagWarn` (#1410) + + +# [3.8.4 - Xcode 15.2 on Feb 8th, 2024](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tags/3.8.4) + +### Public + +- Extend privacy manifest to fulfill validation criteria (#1403) +- Fix crash when fetching registered classes (#1406) + + +# [3.8.3 - Xcode 15.2 on Feb 5th, 2024](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tags/3.8.3) + +### Public + +- Add privacy manifest (#1403) +- Update doc from DDLogLevelWarn to DDLogLevelWarning to match library (#1383) +- Only cleanup files on configuration change if the manager is used by a file logger (#1398) +- Fix #1386 again by adding a missing return and adjusting the preprocessor conditionals +- Fix some C++ warnings + +### Internal + +- Update copyright for 2024 (#1400) +- Improve asserts (#1385) + + +# [3.8.2 - Xcode 15.0 on Oct 31st, 2023](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.8.2) + +### Public + +- App background-mode not correctly detected in app extensions (#1359) +- Fix DDFileLogger rollingFrequency and maximumFileSize not being honored (#1361) +- Fix potential crashes when using the new `DDLogMessageFormat` with messages that contain '%' +- Fix simulator issues when using dynamic registered logging on iOS 17 (#1386) +- Allow `DDFileLogger` to write in different file formats (#1380) + +### Internal + +- Generate Podspec (#1360) + + +# [3.8.1 - Xcode 14.3 on Aug 21st, 2023](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.8.1) + +### Public + +- Silence double conversion warnings for 32-bit watchOS (#1320) +- Enable ALLOW_TARGET_PLATFORM_SPECIALIZATION (#1321) +- Update to swift-log 1.5.2, implement metadata providers (#1329) +- Preserve `messageFormat` in `DDLogMessage` (#1347) + +### Internal + +- Update copyright for 2023 + + +## [3.8.0 - Xcode 14.1 on Nov 2nd, 2022](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.8.0) + +### Public + +- Add support for Xcode 14 / Swift 5.7 - drop support for Swift < 5.5, iOS/tvOS < 11, macOS < 10.13, watchOS < 4 (#1316) +- Update README about swift-log usage (#1275) +- Use dispatch_walltime for scheduling log file rolling timer (#1309) + +### Internal + +- Add consistent newline to file endings (#1272) +- Fix error checking in DDFileLogger (#1274) +- Avoid using NSString format (#1280) +- Prevent logging to symlink files (#1314) + + +## [3.7.4 - Xcode 13.2 on Dec 16, 2021](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.7.4) + +### Public + +- Fix swift-tools-version in Package@swift-5.3.swift + + +## [3.7.3 - Xcode 13.2 on Dec 16, 2021](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.7.3) + +### Public + +- Fix "DDFileLogger: Failed to get offset" when setting maximumFileSize (#1234) +- Follow-up to add annotations to DDOSLogger (#1248) +- Fixed nullability conflict in DDDispatchQueueLogFormatter.h (#1252) +- Add Swift 5.5 support, fix archive build on Xcode 13 (#1253) +- Fix file access issue in Catalyst apps (#1257) +- Fix excluded archs in debug build when not mac catalyst (#1260) +- Bump Xcode last upgraded version to 13.2 (#1265) +- Don't log warnings for CLI apps in DDTTYLogger (#1269) + + +## [3.7.2 - Xcode 12.4 on Apr 9th, 2021](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.7.2) + +### Public + +- Re-introduce (and deprecate) `_tag` field to fix breakage in 3.7.1 (#1224) + + +## [3.7.1 - Xcode 12.4 on Apr 7th, 2021](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.7.1) + +### Public + +- Deprecate `tag` property of `DDLogMessage`, use `representedObject` instead. (#1177, #532) +- Add per-message synchronous logging control for messages logged via SwiftLog using `DDLogHandler` (#1209) +- Add TargetConditionals import for Xcode 12.5 (#1210) +- Prevent logging an error when archiving an already deleted file (#1212) +- Use inclusive words - denylist / allowlist (#1218) +- Add `DDAssertionFailure` macro for Objective-C (#1220) + +### Internal + +- Use setter to replace kvo for `NSFileLogger` (#1180) +- Use new API for `NSFileHandle` on supported platforms (#1181) +- Remove unnecessary checks in `DDFileLogger` (#1182) +- Add an assertion to avoid potential deadlock issues for `flushLog` (#1183) + + +## [3.7.0 - Xcode 12 on Oct 2nd, 2020](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.7.0) + +### Public + +- **Breaking change**: Dropped support for iOS 8 (#1153) +- Update SPM tools-version to 5.3 to enable Swift 5.3 support (#1148) +- Add backend for swift-log (#1164) +- Specify CocoaPods version to ensure `swift_version` attribute works (#1167) +- Simplify `DDLogFileManager` callbacks for archived log files (#1166) + + +## [3.6.2 - Xcode 11.6 on July 31st, 2020](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.6.2) + +### Public +- Fix warnings when building with SPM bundled with Swift 5.2 / Xcode 11.4 (#1132) +- Added Swift name for DDQualityOfServiceName constants. +- Don't localize timestamps in `DDefaultFileLogFormatter` (#1151) +- Allow logging arbitrary objects via Swift log functions (#1146) + +### Repository +- Switch from Travis to GitHub Actions (#1135, #1140, #1150, #1152) + + +## [3.6.1 - Xcode 11.3.1 on Jan 25th, 2020](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.6.1) + +### Public +- Improve error handling during log file creation in DDFileLogger & DDLogFileManager (#1103 / #1111) +- Improve nullability annotations in public headers (#1111 / #1112 / #1119) +- Added support for thread QOS in DDLogMessage class (#1124) + +### Internal +- Fix rolling timer being rescheduled rapidly due to leeway (#1106 / #1107) +- Fix -didArchiveLogFile: returning the file name instead of the file path (#1078) +- Fix setxattr() function usage (#1118) +- Fix NSDateFormatter thread safety (#1121) +- Fix -lt_dataForMessage: duplicated code (#1122) + + +## [3.6.0 - Xcode 11 on October 2nd, 2019](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.6.0) + +### Public +- Swift Package Manager Support (#1083) +- New `willLogMessage:` and `didLogMessage:` methods on `DDFileLogger` which provide access to the current log file info. + +### Internal +- Fix issue with log archiving in the simulator (#1098) +- Limit assertion to non-simulator build (#1100) + + +## [3.5.3 - Xcode 10.2 on Apr 24th, 2019](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.5.3) + +### Public +- Additional compatibility with Swift 5 (backwards compatible with Swift 4) (#1043) +- Fix warning building with Xcode 10.2 (#1059) +- Set Xcode 10.2 and Swift 5.0 as a default (#1064) +- Fix format string crash (#1066) + +### Internal +- Fix warning about syntax (#1054) (#1065) +- Remove banned APIs (#1056) (#1057) +- Add CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER & fix warnings (#1059) +- Use LLONG_MAX instead of LONG_LONG_MAX (#1062) + + +## [3.5.2 - Xcode 10.1 on Mar 15th, 2019](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.5.2) + +### Public +- Fix reusing of log files after rolling (#1042) +- Fix creation of too many log files (#1049) +- Preliminary compatibility with Swift 5 (backwards compatible with Swift 4) (#1044) +- core: loggers os logger variations have been added (#1039) + +### Internal +- Sync internal queues to prevent cleaning up log files too soon in tests (#1053) +- DDLog checks for NULL values and for global queue dispatching has been added (#1045) + + +## [3.5.1 - Xcode 10 on Feb 4th, 2019](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.5.1) + +### Public +- Fix high CPU usage because of empty fileAttributes and / or too high rollingFrequceny (#1028) + + +## [3.5.0 - Xcode 10 on Jan 25th, 2019](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.5.0) + +### Public +- Added `logFileHeader` property to `DDLogFileManagerDefault`. Override to set header for each created file. #998 +- `DDFileLogger` now accepts a `dispatch_queue_t` which it uses to run callbacks. If not provided, the default global queue is used. #1003 +- Added opt-in buffering to `DDFileLogger`. Call `wrapWithBuffer` to create a file logger which buffers. #1001, #1012 +- Add `DDAssert` and `DDAssertionFailure` functions for Swift #934 +- Add `DD_LOG_LEVEL` define (which can be set in `GCC_PREPROCESSOR_DEFINITIONS`) for Swift to set default log level (enables stripping for strings that are not logged). #952 +- Add `asyncLoggingEnabled` global variable to control asynchronous logging. #1019 + +### Internal +- Prevent memory access errors caused by a failed fetch #944 +- Fix common warnings emitted by `-Wall`, `-Wconversion`, `-Wextra`, etc #943, #931 +- Fixes issue that could cause log messages to become interleaved when there are multiple `DDFileLogger`s #985 +- `DispatchQueueFormatter` knows about `com.apple.root.default-qos.overcommit` now #932 +- Fix thread safety issues in `DDFileLogger`. Makes it a little harder to deadlock in some cases. #986, #1003, #946 +- Fix availability checks and memory leak #996 + +### Repository +- Reduce podspec to two subspecs and remove customized modulemap #976 +- Add danger support for PR checks #962 - fixes #956 +- Merged framework targets + using `xcconfig` + deployment target `iOS 8` and `Mac OS 10.10` #959 e97da34 +- Documentation update #955 e7414ae 0239196 #933 +- Full links to Docs and other resources so they are resolved on external pages (i.e. https://cocoapods.org/pods/CocoaLumberjack) e9d6971 +- Replace `OSAtomic` with `stdatomic` in `DDDispatchQueueLogFormatter` #957 #958 +- Add Stale Bot + configuration #953 +- Update to Xcode 10 and Swift 4.2 compiler #950 +- Xcode 10 scheme changes #949 +- Update incomplete BSD 3-Clause License #942 +- Updated to CocoaPods 1.5.3 2d0590f +- Use Xcode 9.4 image for tests #939 +- Xcode (schemes) version bumps #938 +- Update demo and documentation about CustomLogLevels #1023 + + +## [3.4.2 - Xcode 9.3 on Apr 17th, 2018](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.4.2) +- Update README.md #912 +- Fixed typo in pull request template #913 +- Fix `-Wimplicit-retain-self` warnings #915 +- Update memory management in dynamic logging #916 +- Xcode 9.3 support #921 #923 #926 #927 +- Add extern "C" for Objective-C++ #922 +- Add `flush` method to the `DDFileLogger` #928 + + +## [3.4.1 - Xcode 9.1 on Jan 26th, 2018](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.4.1) +- Fix `DDLogFileManagerDefault` `-isLogFile` #909 +- Fix locking the main thread #911 + + +## [3.4.0 - Xcode 9.1 on Jan 3rd, 2018](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.4.0) +- Fix Travis CI #895 +- Fix typos #896 +- Fix schemes and link errors #897 #899 #903 #907 + + +## [3.3.0 - Xcode 9 on Oct 3rd, 2017](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.3.0) +- Xcode 9 support and Swift 4 support #890 #893 +- Replace `OSSpinLock` with `pthread_mutex` #889 + + +## [3.2.1 - Xcode 9 beta on Aug 21st, 2017](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.2.1) +- Xcode 9 beta support #874 #873 #884 #883 #882 +- Fixed some issues around deleting log files #868 #879 +- update 'Use Log Level per Logger' doc #888 +- Remove empty asset catalogs so that they don't show up in Open Quickly #877 +- Fixed typo in pull request template #880 + + +## [3.2.0 - Swift 3.0.0, Xcode 8.3 on May 3rd, 2017](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.2.0) +- Xcode 8.3 support #860 #853 +- added a very basic `os_log` (unified logging) logger #850 #856 +- Use `NSFileProtectionType` instead of `NSString` #866 +- Optimized timestamp calculation in `DDTTYLogger` #851 +- Updated docs #864 +- Fix Travis #863 +- Fixed nullability of `DDLogMessage.function` #849 `DDExtractFileNameWithoutExtension` #845 +- Ignore `Carthage/Build` directory #862 +- Updated schemes #859 #857 + + +## [3.1.0 - Swift 3.0.1, Xcode 8.1 on Feb 22nd, 2017](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.1.0) +- Swift 3.0.1 and Xcode 8.1 support via #816 +- Fix Carthage build and updated the podspec structure #819 #818 #784 #790 #782 #778 #815 +- Fix CLIColor.h not included in umbrella header #781 #796 #813 #783 +- Fix crash in `[DDLog log:level:flag:context:file:function:line:tag:format:]` #831 #830 +- Code improvements: + - using class properties #779 + - nullability #803 #809 #776 + - fix static analyzer issues #822 #828 + - optimized `USE_DISPATCH_CURRENT_QUEUE_LABEL` and `USE_DISPATCH_GET_CURRENT_QUEUE` macros #829 + - fixed dispatch_source_set_timer() usage #834 + - fixed misuse of non null parameter in `DDFileLogger fileAttributes` #835 + - store calendar in logger queue specifics for multi-thread safety #837 + - reenable default `init` method for `DDLogMessage` class #838 +- Added option to not copy messages #832 +- Added new hooks when adding loggers and formatters #836 +- Ability to create new log files every day #736 +- Skip messages in ASL logger which are filtered out by the formatter #786 #742 +- Fixed #823 by adding a `hash` implementation for `DDFileLogger` - same as `isEqual`, it only considers the `filePath` 7ceed08 +- Fix Travis CI build #807 +- Updated docs #798 #808 #811 #810 #820 + + +## [3.0.0 - Swift 3.0, Xcode 8 on Sep 21st, 2016](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.0.0) +- Swift 3.0 and Xcode 8 support via #769, fixes #771 and #772. Many thanks to @ffried @max-potapov @chrisdoc @BarakRL @devxoul and the others who contributed + + +## [2.4.0 - Swift 2.3 on Sep 19th, 2016](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.4.0) +- Swift 2.3 explicit so that the project compiles on Xcode 8 - #747 #773 fix #762 #763 #766 +- CocoaPods 1.0.0 fully adopted - 0f5a793 637dfc1 70439fe #729 +- Fix CLIColor.h not found for non-AppKit binaries w/o clang modules #745 +- Retrieve the `DDLogLevel` of each logger associated to `DDLog` #753 +- updated doc: #727 a9f54c9 #741, diagrams in 8bd128d +- Added CONTRIBUTING, ISSUE and PULL_REQUEST TEMPLATE and added a small Communication section to the Readme +- Fixed an issue with one demo #760 + + +## [2.3.0 - Swift 2.2, Xcode7.3 on May 2nd, 2016](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.3.0) +- Updated to Swift 2.2 - #704 +- replaced deprecated `__FUNCTION__`, `__FILE__`, `__LINE__` with newly added to Swift 2.2: `#function`, `#file`, `#line` +- Xcode 7.3 update - #692 #662 +- simplify usage and integration of the static library target - #657 +- DDLog usable via instances - #679 +- Swift cleanup - #649 +- Enable Application extension API only for tvOS - #701 +- Added `appletvos` and `appletvsimulator` to `SUPPORTED_PLATFORMS` and set `TVOS_DEPLOYMENT_TARGET` - #707 +- fixed `OSSpinLock` init issue - #653 +- Added check to prevent duplicate loggers - #682 +- fixed typo in import - #693 +- updated the docs - #646 #650 #656 #655 #661 #664 #667 #684 #724 + + +## [2.2.0 - TVOS, Xcode7.1 on Oct 28th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.2.0) +- added `tvOS` support (thanks [@sinoru](https://github.com/sinoru)) - [#634](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/634) [#640](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/640) [#630](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/630) [#628](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/628) [#618](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/618) [#611](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/611) +- Remove `(escaping)` from the Swift `@autoclosure` parameters - [#642](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/642) + + +## [2.1.0 - Swift 2.0, WatchOS, Xcode7 on Oct 23rd, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.1.0) +- Fixed the version for the Carthage builds - see [#633](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/633) +- Improved documentation + + +## [2.1.0 RC - Swift 2.0, WatchOS, Xcode7 on Oct 22nd, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.1.0-rc) +- Refactored the `NSDateFormatter` related code to fix a bunch of issues: [#621](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/621) +- Fix Issue [#488](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/488): Support `DDLog` without `AppKit Dependency` (`#define DD_CLI`): [#627](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/627) +- Re-add `NS_DESIGNATED_INITIALIZER` [#619](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/619) + + +## [2.1.0 Beta - Swift 2.0, WatchOS, Xcode7 on Oct 12th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.1.0-beta) +- Updated the library to use Swift 2.0 and Xcode 7 [#617](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/617) [#545](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/545) [#534](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/534) +- WatchOS support (2.0) [#583](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/583) [#581](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/581) [#579](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/579) + + +## [2.0.3 Patch for 2.0.0 on Oct 13th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.3) + +- Compatibility with Xcode 6 that was broken by the 2.0.2 patch - [f042fd3](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/f042fd3) + + +## [2.0.2 Patch for 2.0.0 on Oct 12th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.2) + +- Swift 1.2 fixes [#546](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/546) [#578](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/578) plus and update to Swift 2.0 [5627dff](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/5627dff) imported from our swift_2.0 branch +- Make build work on `tvOS` [#597](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/597) +- Make `CocoaLumberjackSwift-iOS` target depends on `CocoaLumberjack-iOS` [#575](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/575) +- `APPLICATION_EXTENSION_API_ONLY` to `YES` for Extensions [#576](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/576) +- Remove unnecessary `NS_DESIGNATED_INITIALIZER`s [#593](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/593) fixes [#592](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/592) +- Add ignore warning mark for `DDMakeColor` [#553](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/553) +- Kill unused function warnings from `DDTTYLogger.h` [#613](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/613) +- Flag unused parameters as being unused to silence strict warnings [#566](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/566) +- Extend ignore unused warning pragma to cover all platforms [#559](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/559) +- Removed images.xcassets from Mobile project [#580](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/580) +- Silence the Xcode 7 upgrade check - [#595](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/595) +- Fix import for when CL framework files are manually imported into project [#560](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/560) +- Don't override defines in case they're already set at project level [#551](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/551) +- log full filepath when failing to set attribute [#550](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/550) +- Fix issue in standalone build with `DDLegacyMacros.h` [#552](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/552) +- Update `CustomFormatters.md` with proper thread-safe blurb [#555](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/555) +- typo in parameter's variable name fixed [#568](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/568) +- Typo: minor fix [#571](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/571) +- Surely we should be adding 1, not 0 for `OSAtomicAdd32` ? [#587](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/587) +- `rollLogFileWithCompletionBlock` calls back on background queue instead of main queue [#589](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/589) +- Removing extraneous `\` on line 55 [#600](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/600) +- Updated `GettingStarted.md` to include `ddLogLevel` [#602](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/602) +- Remove redundant check for `processorCount` availability [#604](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/604) + + +## [2.0.1 Patch for 2.0.0 on Jun 25th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.1) + +- **Carthage support** [#521](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/521) [#526](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/526) +- fixed crash on `DDASLLogCapture` when `TIME` or `TIME_NSEC` is `NULL` [#484](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/484) +- **Swift** fixes and improvements: [#483](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/483) [#509](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/509) [#518](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/518) [#522](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/522) [5eafceb](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/5eafceb) +- Unit tests: [#500](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/500) [#498](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/498) [#499](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/499) +- Fix [#478](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/478) by reverting [#473](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/473) +- Add `armv7s` to static library [#538](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/538) +- Fix `NSLog` `threadid` mismatch with iOS 8+/OSX 10.10+ [#514](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/514) +- Fixed the `LogV` macros so that avalist is no longer undefined [#511](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/511) +- Using type safe `DDColor` alias instead of #define directive [#506](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/506) +- Several fixes/tweaks to `DDASLLogCapture` [#512](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/512) +- Prevent duplicate log entries when both `DDASLLogCapture` and `DDASLLogger` are used [#515](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/515) +- Fix memory leaks in `DDTTYLogger`, add self annotations to blocks [#536](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/536) +- Update older syntax to modern subscripting for array access [#482](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/482) +- Remove execute permission on non-executable files [#517](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/517) +- Change code samples to use `DDLogFlagWarning` [#520](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/520) +- Fix seemingly obvious typo in the `toLogLevel` function [#508](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/508) + + +## [CocoaLumberjack 2.0.0 on Mar 13th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0) + +The library was strongly refactored, with a few goals in mind: +- Swift support - that we will release in a separate milestone, since CocoaPods 0.36.0 just got out +- Unit tests support +- reorganised things (on disk) +- better coding style + +See [Migration from 1.x to 2.x](https://github.com/CocoaLumberjack/CocoaLumberjack#migrating-to-2x) + + +## [2.0.0-rc2 on Feb 20th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-rc2) + +- Bucket of Swift improvements - [#434](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/434) [#437](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/437) [#449](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/449) [#440](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/440) +- Fixed [#433](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/433) (build issue due to dispatch_queue properties) - [#455](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/455) +- Enable codesign for iOS device framework builds - [#444](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/444) +- Declare `automaticallyAppendNewlineForCustomFormatters` properties as `nonatomic` - [#443](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/443) +- Warning fixes & type standardization - [#419](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/419) +- Legacy checks updated - [#424](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/424) +- Documentation updates + + +## [2.0.0-rc on Dec 11th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-rc) + +- Fix `dispatch_queue_t` properties. +- Fix `registeredClasses` crashes at launch. + + +## [2.0.0-beta4 on Nov 7th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-beta4) + +- Major refactoring and clean up. +- Remove superfluous `log` from property names and use underscore for direct variable access. +- Preliminary Swift support through `CocoaLumberjack.swift`. +- Automatic 1.9.x legacy support when `DDLog.h` is imported instead of the new `CocoaLumberjack.h`. + + +## [2.0.0-beta3 on Oct 21st, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-beta3) + +- Modernize flag variables to be `NS_OPTIONS`/`NS_ENUM`. +- Change the log flags and levels to `NSUInteger`. +- Fix warning when compiled with assertions blocked. +- Crash fixes. + + +## [2.0.0-beta2 on Sep 30th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-beta2) + +- Cleanup code. +- Match `NSLog` read UID functionality in `DDASLLogger`. +- Update framework and static libraries. + + +## [2.0.0 Beta on Aug 12th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-beta) + +See [Migrate from 1.x to 2.x](https://github.com/CocoaLumberjack/CocoaLumberjack#migrating-to-2x) + + +## [1.9.2 Updated patch release for 1.9.0 on Aug 11th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.9.2) + +- Fixed `NSCalendar components:fromDate:` crash - [#140](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/140) [#307](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/307) [#216](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/216) +- New `DDAssert` macros - [#306](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/306) +- Limit log growth by disk space only, not the number of files - [#195](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/195) [#303](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/303) +- Change the mechanism for adding new line character (i.e. '\n\) to log messages in some logger - [#308](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/308) [#310](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/310) +- Fixed deprecations - [#320](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/320) [#312](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/312) [#317](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/317) +- `aslmsg` not freed and causing memory leak - [#314](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/314) +- Fixed `CompressingLogFileManager` compression bug - [#315](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/315) +- Remove unnecessary `NULL` check before `free()` - [#316](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/316) + + +## [1.9.1 Patch release for 1.9.0 on Jun 30th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.9.1) + +- Fixed issues in rolling frequency - [#243](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/243) [#295](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/295) [@georgekola](https://github.com/georgekola) +- Fixed critical issue, `addLogger` method should use a full bit mask instead of `LOG_LEVEL_VERBOSE`, otherwise extended logs or extra flags are ignored [fe6824c](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/fe6824c) [@robbiehanson](https://github.com/robbiehanson) +- Performance optimisation: use compiler macros to skip iOS version checks - [4656d3b](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/4656d3b) [#298](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/298) [#291](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/291) [@robbiehanson](https://github.com/robbiehanson) [@liviur](https://github.com/liviur) +- Changed the `Build Active Architecture Only` to `NO` [#294](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/294) [#293](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/293) +- Optimisation by reusing `NSDateFormatter` instances [#296](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/296) [#301](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/301) + + +## [1.9.0 New ASL capture module, several File logger fixes on May 23rd, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.9.0) + +- New ASL capture module [#242](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/242) [#263](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/263) +- Override default `NSFileProtection` handling [#285](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/285) +- Replaced warnings when ARC was not enabled with errors [#284](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/284) +- Fix for issue [#278](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/278) where really large log files can keep growing [#280](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/280) +- Fixed Xcode warnings [#279](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/279) +- Update `calendarUnitFlags` with new iOS SDK values [#277](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/277) +- Fix possible crash in `[NSCalendar components:fromDate:]` [#277](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/277) +- Fix [#262](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/262) inverted ifs when renaming log [#264](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/264) +- Proper way of doing singletons (via `dispatch_once`) [#259](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/259) +- Explicitly declare `DDFileLogger` and `DDDispatchQueueLogFormatter ` properties as atomic to avoid Xcode warnings [#258](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/258) +- Set `NSFileProtectionKey` on the temporary file created during compression [#256](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/256) +- Fix a rare crash in `CompressingLogFileManager` caused by an unchecked result from read [#255](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/255) +- Add explicit casts for integer conversion [#253](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/253) +- Replace use of `NSThread.detachNewThreadSelector` [#251](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/251) +- Add a constructor override for `initWithLogsDirectory:` [#252](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/252) +- Check and log the streamError whenever we fail to write during compression and log any failures when removing the original file or cleaning up the temporary file after compression failed [#250](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/250) +- Following Apple's guidelines for iOS Static Libraries [#249](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/249) +- Some extra warnings for the mobile framework xcode project [a2e5666](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/a2e5666) +- Update `FineGrainedLoggingAppDelegate.m` [#244](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/244) +- New `[DDLog log:message:]` primitive [7f8af2e](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/7f8af2e) +- Fixed issue [#181](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/181) when logging messages in iOS7 devices aren't properly retrieved by `asl_search` [#240](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/240) +- Allow prevention of log file reuse [#238](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/238) +- `DDTTYLogger`: Favour XcodeColors environment variable [#237](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/237) +- `DDLog`: calling `atexit_b` in CLI applications, that use Foundation framework [#234](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/234) + + +## [1.8.1 AllLoggers and bugfixes on Feb 14th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.8.1) + +- read access to all loggers - [#217](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/217) [#219](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/219) +- fixed bug with archived logs not being handled correctly on iOS simulator - [#218](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/218) +- log the `strerror(errno)` value when `setxattr()` fails - [#211](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/211) +- Add a check for an archived log before overwriting - [#214](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/214) +- improved safety by using assertions instead of comments (`DDLog` in the core) - [#221](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/221) +- added Lumberjack logo :) + + +## [1.8.0 Better CL support, custom logfile name format, bugfixes on Jan 21st, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.8.0) + +- `DDFileLogger` custom logfile (name) format - [#208](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/208) +- Security static analysis fix - [#202](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/202) +- `DDFileLogger`: using `CFBundleIdentifier` as a log filename prefix on OSX and iOS - [#206](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/206) +- Allow disabling of specific levels per-logger - [#204](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/204) +- Improve support for OS X command line tools - [#194](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/194) +- `DDFileLogger`: fixed crash that occurred in case if application name == nil - [#198](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/198) +- `DDFileLogger`: fixed comment - [#199](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/199) +- Fix Travis - [#205](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/205) + + +## [1.7.0 New log file naming convention and CocoaLumberjack organisation on Dec 10th, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.7.0) + +- new log file naming convention - [#191](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/191) +- completed transition to **CocoaLumberjack** organisation - [#188](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/188) + + +## [1.6.5.1 Patch release for Xcode 4.4+ compatibility on Dec 4th, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.5.1) + +- fixed compatibility with Xcode 4.4+ [#187](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/187) + + +## [1.6.5 File Logger refactoring, Multi Formatter, preffixed extension classes on Dec 3rd, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.5) + +`DDFileLogger` refactoring and fixes (thanks [@dvor](https://github.com/dvor) and [@an0](https://github.com/an0)): +- Fixed [#63](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/63) Loggers don't flush in Command Line Tool [#184](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/184) +- Fixed [#52](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/52) Force log rotation [#183](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/183) +- Fixed [#55](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/55) After deleting log file or log dir they aren't created again without relaunching the app [#183](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/183) +- Fixed [#129](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/129) [iOS] `DDFileLogger` causes crash when logging from background app [#183](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/183) +- Fixed [#153](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/153) Log file on iPhone only contains a single line [#177](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/177) +- Fixed [#155](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/155) How do I combine all my log levels into one file? [#177](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/177) +- Fixed [#175](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/175) `DFileLogger` `creationDate` bug on 64-bit iOS system [#177](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/177) +- Allow customizing the naming convention for log files to use timestamps [#174](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/174) + +Other: +- Implemented multiple formatter (`DDMultiFormatter` - alows chaining of formatters) [#178](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/178) +- Added DD prefix to extension classes (`ContextFilterLogFormatter` and `DispatchQueueLogFormatter`) [#178](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/178) +- Updated code indentation: Tabs changed to spaces [#180](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/180) +- Included `DDLog+LOGV.h` in Cocoapods sources [d253bd7](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/d253bd7) +- other fixes/improvements + + +## [1.6.4 Fix compatibility with 3rd party frameworks on Nov 21st, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.4) + +* "Fix" conflicts with 3rd party libraries using `CocoaLumberjack` [#172](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/172) +* Ignore deprecated warning for `dispatch_get_current_queue` [#167](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/167) +* Add new `DEBUG` log level support to included loggers [#166](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/166) +* Method declarations that make it easier to extend/modify `DispatchQueueLogFormatter` [#164](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/164) + + +## [1.6.3 New macros, updated podspec and bug fixes on Apr 2nd, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.3) + +* Add `LOGV`-style macros [#161](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/161) +* Fix getting queue's label [#159](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/159) +* New log level `DEBUG` [#145](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/145) +* Use `DISPATCH_CURRENT_QUEUE_LABEL` if available [#159](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/159) +* Different `logLevel` per each logger [#151](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/151) +* Created 2 subspecs, `Core` and `Extensions` [#152](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/152) +* Updated observer for keypath using `NSStringFromSelector` + `@selector` [38e5da3](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/38e5da3) +* Replaced `id` return type with `instancetype` [ebee454](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/ebee454) +* Remove implicit conversion warnings [#149](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/149) +* `DDTTYLogger`: Allow to set default color profiles for all contexts at once [#146](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/146) [#158](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/158) +* `DDTTYLogger`: By default apply `setForegroundColor:backgroundColor:forFlag:` to `LOG_CONTEXT_ALL` [#154](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/154) +* `DispatchQueueLogFormatter`: Use modern Objective-C [#142](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/142) +* `DispatchQueueLogFormatter`: Make sure to always use a `NSGregorianCalendar` for date formatter [#142](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/142) +* Replaced explicit reference to class name in `logFileWithPath` factory method [#131](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/131) +* Catch exceptions in `logMessage:` [#130](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/130) +* Fix enum type conversion warnings [#124](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/124) +* Add deployment target condition for workaround [#121](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/121) +* Fix static analyzer warnings about `nil` values in dictionary [#122](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/122) +* Fix `dispatch_get_current_queue` crash [#121](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/121) +* Fixing colors in greyscale color-space not working [d019cfd](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/d019cfd) +* Guard around `dispatch_resume()` being called with null pointer [#107](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/107) +* `NULL` safety checks [#107](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/107) + + +## [1.6.2 on Apr 2nd, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.2) + +## [1.6.1 on Apr 2nd, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.1) + +## [1.6 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6) + +## [1.5.1 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.5.1) + +## [1.5 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.5) + +## [1.4.1 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.4.1) + +## [1.4 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.4) + +## [1.3.3 on Mar 30th, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.3.3) + +## [1.3.2 on Dec 23rd, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.3.2) + +## [1.3.1 on Dec 9th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.3.1) + +## [1.3 on Dec 9th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.3) + +## [1.2.3 on Dec 9th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.2.3) + +## [1.2.2 on Dec 9th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.2.2) + +## [1.2.1 on Oct 13th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.2.1) + +## [1.2 on Oct 13th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.2) + +## [1.1 on Oct 13th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.1) + +## [1.0 on Oct 13th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.0) diff --git a/dydx/Pods/CocoaLumberjack/LICENSE b/dydx/Pods/CocoaLumberjack/LICENSE new file mode 100644 index 000000000..27f20f721 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/LICENSE @@ -0,0 +1,14 @@ +BSD 3-Clause License + +Copyright (c) 2010-2024, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/dydx/Pods/CocoaLumberjack/README.md b/dydx/Pods/CocoaLumberjack/README.md new file mode 100644 index 000000000..e9a2fc173 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/README.md @@ -0,0 +1,301 @@ +

+ +

+ +CocoaLumberjack +=============== +[![Version](https://img.shields.io/github/release/CocoaLumberjack/CocoaLumberjack.svg?display_name=tag&style=flat)](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/latest) +[![License](https://img.shields.io/github/license/CocoaLumberjack/CocoaLumberjack.svg?style=flat)](https://opensource.org/licenses/BSD-3-Clause) +[![Pod Platform](https://img.shields.io/cocoapods/p/CocoaLumberjack.svg?style=flat)](https://cocoadocs.org/docsets/CocoaLumberjack/) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) +![Unit Tests](https://github.com/CocoaLumberjack/CocoaLumberjack/workflows/Unit%20Tests/badge.svg) +[![codecov](https://codecov.io/gh/CocoaLumberjack/CocoaLumberjack/branch/master/graph/badge.svg)](https://codecov.io/gh/CocoaLumberjack/CocoaLumberjack) +[![codebeat badge](https://codebeat.co/badges/840b714a-c8f3-4936-ada4-363473cd4e6b)](https://codebeat.co/projects/github-com-cocoalumberjack-cocoalumberjack-master) + + +**CocoaLumberjack** is a fast & simple, yet powerful & flexible logging framework for macOS, iOS, tvOS and watchOS. + +## How to get started + +First, install CocoaLumberjack via [CocoaPods](https://cocoapods.org), [Carthage](https://github.com/Carthage/Carthage), [Swift Package Manager](https://swift.org/package-manager/) or manually. +Then use `DDOSLogger` for iOS 10 and later, or `DDTTYLogger` and `DDASLLogger` for earlier versions to begin logging messages. + +### CocoaPods + +```ruby +platform :ios, '11.0' + +target 'SampleTarget' do + use_frameworks! + pod 'CocoaLumberjack/Swift' +end +``` +Note: `Swift` is a subspec which will include all the Obj-C code plus the Swift one, so this is sufficient. +For more details about how to use Swift with Lumberjack, see [this conversation](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/405). + +For Objective-C use the following: +```ruby +platform :ios, '11.0' + +target 'SampleTarget' do + pod 'CocoaLumberjack' +end +``` + +### Carthage + +Carthage is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods. + +To install with Carthage, follow the instruction on [Carthage](https://github.com/Carthage/Carthage) + +Cartfile +``` +github "CocoaLumberjack/CocoaLumberjack" +``` + + +### Swift Package Manager + +As of CocoaLumberjack 3.6.0, you can use the Swift Package Manager as integration method. +If you want to use the Swift Package Manager as integration method, either use Xcode to add the package dependency or add the following dependency to your Package.swift: + +```swift +.package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack.git", from: "3.8.0"), +``` + +Note that you may need to add both products, `CocoaLumberjack` and `CocoaLumberjackSwift` to your target since SPM sometimes fails to detect that `CocoaLumerjackSwift` depends on `CocoaLumberjack`. + +### Install manually + +If you want to install CocoaLumberjack manually, read the [manual installation](Documentation/GettingStarted.md#manual-installation) guide for more information. + +### Swift Usage + +Usually, you can simply `import CocoaLumberjackSwift`. If you installed CocoaLumberjack using CocoaPods, you need to use `import CocoaLumberjack` instead. + +```swift +DDLog.add(DDOSLogger.sharedInstance) // Uses os_log + +let fileLogger: DDFileLogger = DDFileLogger() // File Logger +fileLogger.rollingFrequency = 60 * 60 * 24 // 24 hours +fileLogger.logFileManager.maximumNumberOfLogFiles = 7 +DDLog.add(fileLogger) + +... + +DDLogVerbose("Verbose") +DDLogDebug("Debug") +DDLogInfo("Info") +DDLogWarn("Warn") +DDLogError("Error") +``` + +### Obj-C usage + +If you're using Lumberjack as a framework, you can `@import CocoaLumberjack;`. +Otherwise, `#import ` + +```objc +[DDLog addLogger:[DDOSLogger sharedInstance]]; // Uses os_log + +DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger +fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling +fileLogger.logFileManager.maximumNumberOfLogFiles = 7; +[DDLog addLogger:fileLogger]; + +... + +DDLogVerbose(@"Verbose"); +DDLogDebug(@"Debug"); +DDLogInfo(@"Info"); +DDLogWarn(@"Warn"); +DDLogError(@"Error"); +``` +### Objective-C ARC Semantic Issue + +When integrating Lumberjack into an existing Objective-C it is possible to run into `Multiple methods named 'tag' found with mismatched result, parameter type or attributes` build error. + +Add `#define DD_LEGACY_MESSAGE_TAG 0` before importing CocoaLumberjack or add `#define DD_LEGACY_MESSAGE_TAG 0` or add `-DDD_LEGACY_MESSAGE_TAG=0` to *Other C Flags*/*OTHER_CFLAGS* in your Xcode project. + +## [swift-log](https://github.com/apple/swift-log) backend + +CocoaLumberjack also ships with a backend implementation for [swift-log](https://github.com/apple/swift-log). +Simply add CocoaLumberjack as dependency to your SPM target (see above) and also add the `CocoaLumberjackSwiftLogBackend` product as dependency to your target. + +You can then use `DDLogHandler` as backend for swift-log, which will forward all messages to CocoaLumberjack's `DDLog`. You will still configure the loggers and log formatters you want via `DDLog`, but writing log messages will be done using `Logger` from swift-log. + +In your own log formatters, you can make use of the `swiftLogInfo` property on `DDLogMessage` to retrieve the details of a message that is logged via swift-log. + +To use swift-log with CocoaLumberjack, take a look the following code snippet to see how to get started. + +```swift +import CocoaLumberjack +import CocoaLumberjackSwiftLogBackend +import Logging + +// In your application's entry point (e.g. AppDelegate): +DDLog.add(DDOSLogger.sharedInstance) // Configure loggers +LoggingSystem.bootstrapWithCocoaLumberjack() // Use CocoaLumberjack as swift-log backend +``` + + +## More information + +- read the [Getting started](Documentation/GettingStarted.md) guide, check out the [FAQ](Documentation/FAQ.md) section or the other [docs](Documentation/) +- if you find issues or want to suggest improvements, create an issue or a pull request +- for all kinds of questions involving CocoaLumberjack, use the [Google group](https://groups.google.com/group/cocoalumberjack) or StackOverflow (use [#lumberjack](https://stackoverflow.com/questions/tagged/lumberjack)). + + +## CocoaLumberjack 3 + +### Migrating to 3.x + +* To be determined + +## Features + +### Lumberjack is Fast & Simple, yet Powerful & Flexible. + +It is similar in concept to other popular logging frameworks such as log4j, yet is designed specifically for Objective-C, and takes advantage of features such as multi-threading, grand central dispatch (if available), lockless atomic operations, and the dynamic nature of the Objective-C runtime. + +### Lumberjack is Fast + +In most cases it is an order of magnitude faster than NSLog. + +### Lumberjack is Simple + +It takes as little as a single line of code to configure lumberjack when your application launches. Then simply replace your NSLog statements with DDLog statements and that's about it. (And the DDLog macros have the exact same format and syntax as NSLog, so it's super easy.) + +### Lumberjack is Powerful: + +One log statement can be sent to multiple loggers, meaning you can log to a file and the console simultaneously. Want more? Create your own loggers (it's easy) and send your log statements over the network. Or to a database or distributed file system. The sky is the limit. + +### Lumberjack is Flexible: + +Configure your logging however you want. Change log levels per file (perfect for debugging). Change log levels per logger (verbose console, but concise log file). Change log levels per xcode configuration (verbose debug, but concise release). Have your log statements compiled out of the release build. Customize the number of log levels for your application. Add your own fine-grained logging. Dynamically change log levels during runtime. Choose how & when you want your log files to be rolled. Upload your log files to a central server. Compress archived log files to save disk space... + +## This framework is for you if: + +- You're looking for a way to track down that impossible-to-reproduce bug that keeps popping up in the field. +- You're frustrated with the super short console log on the iPhone. +- You're looking to take your application to the next level in terms of support and stability. +- You're looking for an enterprise level logging solution for your application (Mac or iPhone). + +## Documentation + +- **[Get started using Lumberjack](Documentation/GettingStarted.md)**
+- [Different log levels for Debug and Release builds](Documentation/XcodeTricks.md)
+- [Different log levels for each logger](Documentation/PerLoggerLogLevels.md)
+- [Use colors in the Xcode debugging console](Documentation/XcodeColors.md)
+- [Write your own custom formatters](Documentation/CustomFormatters.md)
+- [FAQ](Documentation/FAQ.md)
+- [Analysis of performance with benchmarks](Documentation/Performance.md)
+- [Common issues you may encounter and their solutions](Documentation/ProblemSolution.md)
+- [AppCode support](Documentation/AppCode-support.md) +- **[Full Lumberjack documentation](Documentation/)**
+ +## Requirements +The current version of Lumberjack requires: +- Xcode 14.1 or later +- Swift 5.5 or later +- macOS 10.13 or later +- iOS 11 or later +- tvOS 11 or later +- watchOS 4 or later + +### Backwards compatibility +- for iOS/tvOS up to 10, watchOS up to 3, macOS up to 10.12, Xcode up to 13 and Swift up to 5.4, use the 3.7.4 version +- for Xcode 11 and Swift up to 5.2, use the 3.6.2 version +- for Xcode 10 and Swift 4.2, use the 3.5.2 version +- for iOS 8, use the 3.6.1 version +- for iOS 6, iOS 7, OS X 10.8, OS X 10.9 and Xcode 9, use the 3.4.2 version +- for iOS 5 and OS X 10.7, use the 3.3 version +- for Xcode 8 and Swift 3, use the 3.2 version +- for Xcode 7.3 and Swift 2.3, use the 2.4.0 version +- for Xcode 7.3 and Swift 2.2, use the 2.3.0 version +- for Xcode 7.2 and 7.1, use the 2.2.0 version +- for Xcode 7.0 or earlier, use the 2.1.0 version +- for Xcode 6 or earlier, use the 2.0.x version +- for OS X < 10.7 support, use the 1.6.0 version + +## Communication + +- If you **need help**, use [Stack Overflow](https://stackoverflow.com/questions/tagged/lumberjack). (Tag 'lumberjack') +- If you'd like to **ask a general question**, use [Stack Overflow](https://stackoverflow.com/questions/tagged/lumberjack). +- If you **found a bug**, open an issue. +- If you **have a feature request**, open an issue. +- If you **want to contribute**, submit a pull request. + +## Data Collection Practices + +Per [App privacy details on the App Store](https://developer.apple.com/app-store/app-privacy-details/), Apple is requesting app developers to provide info about their data collection, us SDK maintainers must provide them with the same data. + +### Data collection by the framework + +**By default, CocoaLumberjack does NOT collect any data on its own.** + +[See our Data Collection Practices list.](https://cocoalumberjack.github.io/DataCollection/index.html) + +### Indirect data collection through the framework + +CocoaLumberjack is a logging framework which makes it easy to send those logs to different platforms. + +This is why collecting data might happen quite easily, if app developers include any sensitive data into their log messages. + +**Important note: app developers are fully responsible for any sensitive data collected through our logging system!** + +In consequence, you must comply to the Apple's privacy details policy (mentioned above) and document the ways in which user data is being collected. +Since the number of scenarios where data might be indirectly collected through CocoaLumberjack is quite large, it's up to you, as app developers, to properly review your app's code and identify those cases. +What we can do to help is raise awareness about potential data collection through our framework. + +Private data includes but isn't limited to: + +- user info (name, email, address, ...) +- location info +- contacts +- identifiers (user id, device id, ...) +- app usage data +- performance data +- health and fitness info +- financial info +- sensitive info +- user content +- history (browsing, search, ...) +- purchases +- diagnostics +- ... + +_Example_: `DDLogInfo("User: \(myUser)")` will add the `myUser` info to the logs, so if those are forwarded to a 3rd party or sent via email, that may qualify as data collection. + +## Author + +- [Robbie Hanson](https://github.com/robbiehanson) +- Love the project? Wanna buy me a coffee? (or a beer :D) [![donation](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UZRA26JPJB3DA) + +## Collaborators +- [Ernesto Rivera](https://github.com/rivera-ernesto) +- [Dmitry Vorobyov](https://github.com/dvor) +- [Bogdan Poplauschi](https://github.com/bpoplauschi) +- [C.W. Betts](https://github.com/MaddTheSane) +- [Koichi Yokota (sushichop)](https://github.com/sushichop) +- [Nick Brook](https://github.com/nrbrook) +- [Florian Friedrich](https://github.com/ffried) +- [Stephan Diederich](https://github.com/diederich) +- [Kent Sutherland](https://github.com/ksuther) +- [Dmitry Lobanov](https://github.com/lolgear) +- [Hakon Hanesand](https://github.com/hhanesand) + +## License +- CocoaLumberjack is available under the BSD 3 license. See the [LICENSE file](LICENSE). + +## Extensions +- [Birch-Lumberjack](https://github.com/gruffins/birch-lumberjack) A remote logger for CocoaLumberjack +- [BugfenderSDK-CocoaLumberjack](https://github.com/bugfender/BugfenderSDK-CocoaLumberjack) A Bugfender logger for CocoaLumberjack +- [LogIO-CocoaLumberjack](https://github.com/s4nchez/LogIO-CocoaLumberjack) A log.io logger for CocoaLumberjack +- [XCDLumberjackNSLogger](https://github.com/0xced/XCDLumberjackNSLogger) CocoaLumberjack logger which sends logs to NSLogger + +## Architecture + +

+ +

diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/CLI/CLIColor.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/CLI/CLIColor.m new file mode 100644 index 000000000..08c63301d --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/CLI/CLIColor.m @@ -0,0 +1,57 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +#if TARGET_OS_OSX + +#import + +@interface CLIColor () { + CGFloat _red, _green, _blue, _alpha; +} + +@end + + +@implementation CLIColor + ++ (instancetype)colorWithCalibratedRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha { + __auto_type color = [CLIColor new]; + color->_red = red; + color->_green = green; + color->_blue = blue; + color->_alpha = alpha; + return color; +} + +- (void)getRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha { + if (red) { + *red = _red; + } + if (green) { + *green = _green; + } + if (blue) { + *blue = _blue; + } + if (alpha) { + *alpha = _alpha; + } +} + +@end + +#endif diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogCapture.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogCapture.m new file mode 100644 index 000000000..db7254dc0 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogCapture.m @@ -0,0 +1,205 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +#if !TARGET_OS_WATCH + +#include +#include +#include +#include + +#import + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +static __auto_type _cancel = YES; +static __auto_type _captureLevel = DDLogLevelVerbose; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" +@implementation DDASLLogCapture +#pragma clang diagnostic pop + ++ (void)start { + // Ignore subsequent calls + if (!_cancel) { + return; + } + + _cancel = NO; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { + [self captureAslLogs]; + }); +} + ++ (void)stop { + _cancel = YES; +} + ++ (DDLogLevel)captureLevel { + return _captureLevel; +} + ++ (void)setCaptureLevel:(DDLogLevel)level { + _captureLevel = level; +} + +#pragma mark - Private methods + ++ (void)configureAslQuery:(aslmsg)query { + const char param[] = "7"; // ASL_LEVEL_DEBUG, which is everything. We'll rely on regular DDlog log level to filter + + asl_set_query(query, ASL_KEY_LEVEL, param, ASL_QUERY_OP_LESS_EQUAL | ASL_QUERY_OP_NUMERIC); + + // Don't retrieve logs from our own DDASLLogger + asl_set_query(query, kDDASLKeyDDLog, kDDASLDDLogValue, ASL_QUERY_OP_NOT_EQUAL); + +#if !TARGET_OS_IPHONE || (defined(TARGET_SIMULATOR) && TARGET_SIMULATOR) + __auto_type processId = [[NSProcessInfo processInfo] processIdentifier]; + char pid[16]; + snprintf(pid, sizeof(pid), "%d", processId); + asl_set_query(query, ASL_KEY_PID, pid, ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_NUMERIC); +#endif +} + ++ (void)aslMessageReceived:(aslmsg)msg { + __auto_type messageCString = asl_get(msg, ASL_KEY_MSG); + if (messageCString == NULL) + return; + + DDLogFlag flag; + BOOL async; + + __auto_type levelCString = asl_get(msg, ASL_KEY_LEVEL); + switch (levelCString? atoi(levelCString) : 0) { + // By default all NSLog's with a ASL_LEVEL_WARNING level + case ASL_LEVEL_EMERG : + case ASL_LEVEL_ALERT : + case ASL_LEVEL_CRIT : flag = DDLogFlagError; async = NO; break; + case ASL_LEVEL_ERR : flag = DDLogFlagWarning; async = YES; break; + case ASL_LEVEL_WARNING : flag = DDLogFlagInfo; async = YES; break; + case ASL_LEVEL_NOTICE : flag = DDLogFlagDebug; async = YES; break; + case ASL_LEVEL_INFO : + case ASL_LEVEL_DEBUG : + default : flag = DDLogFlagVerbose; async = YES; break; + } + + if (!(_captureLevel & flag)) { + return; + } + + // NSString * sender = [NSString stringWithCString:asl_get(msg, ASL_KEY_SENDER) encoding:NSUTF8StringEncoding]; + NSString *message = @(messageCString); + + __auto_type secondsCString = asl_get(msg, ASL_KEY_TIME); + __auto_type nanoCString = asl_get(msg, ASL_KEY_TIME_NSEC); + __auto_type seconds = secondsCString ? strtod(secondsCString, NULL) : [NSDate timeIntervalSinceReferenceDate] - NSTimeIntervalSince1970; + __auto_type nanoSeconds = nanoCString? strtod(nanoCString, NULL) : 0; + __auto_type totalSeconds = seconds + (nanoSeconds / 1e9); + + __auto_type timeStamp = [NSDate dateWithTimeIntervalSince1970:totalSeconds]; + + __auto_type logMessage = [[DDLogMessage alloc] initWithMessage:message + level:_captureLevel + flag:flag + context:0 + file:@"DDASLLogCapture" + function:nil + line:0 + tag:nil + options:DDLogMessageDontCopyMessage + timestamp:timeStamp]; + + [DDLog log:async message:logMessage]; +} + ++ (void)captureAslLogs { + @autoreleasepool + { + /* + We use ASL_KEY_MSG_ID to see each message once, but there's no + obvious way to get the "next" ID. To bootstrap the process, we'll + search by timestamp until we've seen a message. + */ + + struct timeval timeval = { + .tv_sec = 0 + }; + gettimeofday(&timeval, NULL); + __auto_type startTime = (unsigned long long)timeval.tv_sec; + __block unsigned long long lastSeenID = 0; + + /* + syslogd posts kNotifyASLDBUpdate (com.apple.system.logger.message) + through the notify API when it saves messages to the ASL database. + There is some coalescing - currently it is sent at most twice per + second - but there is no documented guarantee about this. In any + case, there may be multiple messages per notification. + + Notify notifications don't carry any payload, so we need to search + for the messages. + */ + int notifyToken = 0; // Can be used to unregister with notify_cancel(). + notify_register_dispatch(kNotifyASLDBUpdate, ¬ifyToken, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(int token) + { + // At least one message has been posted; build a search query. + @autoreleasepool + { + __auto_type query = asl_new(ASL_TYPE_QUERY); + char stringValue[64]; + + if (lastSeenID > 0) { + snprintf(stringValue, sizeof stringValue, "%llu", lastSeenID); + asl_set_query(query, ASL_KEY_MSG_ID, stringValue, ASL_QUERY_OP_GREATER | ASL_QUERY_OP_NUMERIC); + } else { + snprintf(stringValue, sizeof stringValue, "%llu", startTime); + asl_set_query(query, ASL_KEY_TIME, stringValue, ASL_QUERY_OP_GREATER_EQUAL | ASL_QUERY_OP_NUMERIC); + } + + [self configureAslQuery:query]; + + // Iterate over new messages. + aslmsg msg; + __auto_type response = asl_search(NULL, query); + + while ((msg = asl_next(response))) + { + [self aslMessageReceived:msg]; + + // Keep track of which messages we've seen. + lastSeenID = (unsigned long long)atoll(asl_get(msg, ASL_KEY_MSG_ID)); + } + asl_release(response); + asl_free(query); + + if (_cancel) { + notify_cancel(token); + return; + } + + } + }); + } +} + +@end + +#endif diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogger.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogger.m new file mode 100644 index 000000000..e9b8c6a3a --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogger.m @@ -0,0 +1,133 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +#if !TARGET_OS_WATCH + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import + +#import + +const char* const kDDASLKeyDDLog = "DDLog"; +const char* const kDDASLDDLogValue = "1"; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" +static DDASLLogger *sharedInstance; +#pragma clang diagnostic pop + +@interface DDASLLogger () { + aslclient _client; +} + +@end + + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" +@implementation DDASLLogger +#pragma clang diagnostic pop + ++ (instancetype)sharedInstance { + static dispatch_once_t DDASLLoggerOnceToken; + + dispatch_once(&DDASLLoggerOnceToken, ^{ + sharedInstance = [[[self class] alloc] init]; + }); + + return sharedInstance; +} + +- (instancetype)init { + if (sharedInstance != nil) { + return nil; + } + + if ((self = [super init])) { + // A default asl client is provided for the main thread, + // but background threads need to create their own client. + + _client = asl_open(NULL, "com.apple.console", 0); + } + + return self; +} + +- (DDLoggerName)loggerName { + return DDLoggerNameASL; +} + +- (void)logMessage:(DDLogMessage *)logMessage { + // Skip captured log messages + if ([logMessage->_fileName isEqualToString:@"DDASLLogCapture"]) { + return; + } + + __auto_type message = _logFormatter ? [_logFormatter formatLogMessage:logMessage] : logMessage->_message; + + if (message) { + __auto_type msg = [message UTF8String]; + + size_t aslLogLevel; + switch (logMessage->_flag) { + // Note: By default ASL will filter anything above level 5 (Notice). + // So our mappings shouldn't go above that level. + case DDLogFlagError : aslLogLevel = ASL_LEVEL_CRIT; break; + case DDLogFlagWarning : aslLogLevel = ASL_LEVEL_ERR; break; + case DDLogFlagInfo : aslLogLevel = ASL_LEVEL_WARNING; break; // Regular NSLog's level + case DDLogFlagDebug : + case DDLogFlagVerbose : + default : aslLogLevel = ASL_LEVEL_NOTICE; break; + } + + static char const *const level_strings[] = { "0", "1", "2", "3", "4", "5", "6", "7" }; + + // NSLog uses the current euid to set the ASL_KEY_READ_UID. + const __auto_type readUID = geteuid(); + + char readUIDString[16]; +#ifndef NS_BLOCK_ASSERTIONS + __auto_type l = (size_t)snprintf(readUIDString, sizeof(readUIDString), "%d", readUID); +#else + snprintf(readUIDString, sizeof(readUIDString), "%d", readUID); +#endif + + NSAssert(l < sizeof(readUIDString), + @"Formatted euid is too long."); + NSAssert(aslLogLevel < (sizeof(level_strings) / sizeof(level_strings[0])), + @"Unhandled ASL log level."); + + __auto_type m = asl_new(ASL_TYPE_MSG); + if (m != NULL) { + if (asl_set(m, ASL_KEY_LEVEL, level_strings[aslLogLevel]) == 0 && + asl_set(m, ASL_KEY_MSG, msg) == 0 && + asl_set(m, ASL_KEY_READ_UID, readUIDString) == 0 && + asl_set(m, kDDASLKeyDDLog, kDDASLDDLogValue) == 0) { + asl_send(_client, m); + } + asl_free(m); + } + //TODO handle asl_* failures non-silently? + } +} + +@end + +#endif diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDAbstractDatabaseLogger.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDAbstractDatabaseLogger.m new file mode 100644 index 000000000..232846aac --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDAbstractDatabaseLogger.m @@ -0,0 +1,636 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import + +@interface DDAbstractDatabaseLogger () + +- (void)destroySaveTimer; +- (void)updateAndResumeSaveTimer; +- (void)createSuspendedSaveTimer; +- (void)destroyDeleteTimer; +- (void)updateDeleteTimer; +- (void)createAndStartDeleteTimer; + +@end + +#pragma mark - + +@implementation DDAbstractDatabaseLogger + +- (instancetype)init { + if ((self = [super init])) { + _saveThreshold = 500; + _saveInterval = 60; // 60 seconds + _maxAge = (60 * 60 * 24 * 7); // 7 days + _deleteInterval = (60 * 5); // 5 minutes + } + + return self; +} + +- (void)dealloc { + [self destroySaveTimer]; + [self destroyDeleteTimer]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Override Me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)db_log:(__unused DDLogMessage *)logMessage { + // Override me and add your implementation. + // + // Return YES if an item was added to the buffer. + // Return NO if the logMessage was ignored. + + return NO; +} + +- (void)db_save { + // Override me and add your implementation. +} + +- (void)db_delete { + // Override me and add your implementation. +} + +- (void)db_saveAndDelete { + // Override me and add your implementation. +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Private API +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)performSaveAndSuspendSaveTimer { + if (_unsavedCount > 0) { + if (_deleteOnEverySave) { + [self db_saveAndDelete]; + } else { + [self db_save]; + } + } + + _unsavedCount = 0; + _unsavedTime = 0; + + if (_saveTimer != NULL && _saveTimerSuspended == 0) { + dispatch_suspend(_saveTimer); + _saveTimerSuspended = 1; + } +} + +- (void)performDelete { + if (_maxAge > 0.0) { + [self db_delete]; + + _lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Timers +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)destroySaveTimer { + if (_saveTimer != NULL) { + dispatch_source_cancel(_saveTimer); + + // Must activate a timer before releasing it (or it will crash) + if (_saveTimerSuspended < 0) { + dispatch_activate(_saveTimer); + } else if (_saveTimerSuspended > 0) { + dispatch_resume(_saveTimer); + } + +#if !OS_OBJECT_USE_OBJC + dispatch_release(_saveTimer); +#endif + _saveTimer = NULL; + _saveTimerSuspended = 0; + } +} + +- (void)updateAndResumeSaveTimer { + if ((_saveTimer != NULL) && (_saveInterval > 0.0) && (_unsavedTime > 0)) { + __auto_type interval = (uint64_t)(_saveInterval * (NSTimeInterval) NSEC_PER_SEC); + __auto_type startTime = dispatch_time(_unsavedTime, (int64_t)interval); + + dispatch_source_set_timer(_saveTimer, startTime, interval, 1ull * NSEC_PER_SEC); + + if (_saveTimerSuspended < 0) { + dispatch_activate(_saveTimer); + _saveTimerSuspended = 0; + } else if (_saveTimerSuspended > 0) { + dispatch_resume(_saveTimer); + _saveTimerSuspended = 0; + } + } +} + +- (void)createSuspendedSaveTimer { + if ((_saveTimer == NULL) && (_saveInterval > 0.0)) { + _saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue); + + dispatch_source_set_event_handler(_saveTimer, ^{ @autoreleasepool { + [self performSaveAndSuspendSaveTimer]; + } }); + + _saveTimerSuspended = -1; + } +} + +- (void)destroyDeleteTimer { + if (_deleteTimer != NULL) { + dispatch_source_cancel(_deleteTimer); +#if !OS_OBJECT_USE_OBJC + dispatch_release(_deleteTimer); +#endif + _deleteTimer = NULL; + } +} + +- (void)updateDeleteTimer { + if ((_deleteTimer != NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) { + __auto_type interval = (int64_t)(_deleteInterval * (NSTimeInterval) NSEC_PER_SEC); + dispatch_time_t startTime; + + if (_lastDeleteTime > 0) { + startTime = dispatch_time(_lastDeleteTime, interval); + } else { + startTime = dispatch_time(DISPATCH_TIME_NOW, interval); + } + + dispatch_source_set_timer(_deleteTimer, startTime, (uint64_t)interval, 1ull * NSEC_PER_SEC); + } +} + +- (void)createAndStartDeleteTimer { + if ((_deleteTimer == NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) { + _deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue); + + if (_deleteTimer != NULL) { + dispatch_source_set_event_handler(_deleteTimer, ^{ @autoreleasepool { + [self performDelete]; + } }); + + [self updateDeleteTimer]; + + // We are sure that -updateDeleteTimer did call dispatch_source_set_timer() + // since it has the same guards on _deleteInterval and _maxAge + dispatch_activate(_deleteTimer); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSUInteger)saveThreshold { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block NSUInteger result; + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = self->_saveThreshold; + }); + }); + + return result; +} + +- (void)setSaveThreshold:(NSUInteger)threshold { + dispatch_block_t block = ^{ + @autoreleasepool { + if (self->_saveThreshold != threshold) { + self->_saveThreshold = threshold; + + // Since the saveThreshold has changed, + // we check to see if the current unsavedCount has surpassed the new threshold. + // + // If it has, we immediately save the log. + + if ((self->_unsavedCount >= self->_saveThreshold) && (self->_saveThreshold > 0)) { + [self performSaveAndSuspendSaveTimer]; + } + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (NSTimeInterval)saveInterval { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block NSTimeInterval result; + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = self->_saveInterval; + }); + }); + + return result; +} + +- (void)setSaveInterval:(NSTimeInterval)interval { + __auto_type block = ^{ + @autoreleasepool { + // C99 recommended floating point comparison macro + // Read: isLessThanOrGreaterThan(floatA, floatB) + + if (/* saveInterval != interval */ islessgreater(self->_saveInterval, interval)) { + self->_saveInterval = interval; + + // There are several cases we need to handle here. + // + // 1. If the saveInterval was previously enabled and it just got disabled, + // then we need to stop the saveTimer. (And we might as well release it.) + // + // 2. If the saveInterval was previously disabled and it just got enabled, + // then we need to setup the saveTimer. (Plus we might need to do an immediate save.) + // + // 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date. + // + // 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date. + // (Plus we might need to do an immediate save.) + + if (self->_saveInterval > 0.0) { + if (self->_saveTimer == NULL) { + // Handles #2 + // + // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self createSuspendedSaveTimer]; + [self updateAndResumeSaveTimer]; + } else { + // Handles #3 + // Handles #4 + // + // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self updateAndResumeSaveTimer]; + } + } else if (self->_saveTimer) { + // Handles #1 + + [self destroySaveTimer]; + } + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (NSTimeInterval)maxAge { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block NSTimeInterval result; + + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = self->_maxAge; + }); + }); + + return result; +} + +- (void)setMaxAge:(NSTimeInterval)interval { + __auto_type block = ^{ + @autoreleasepool { + // C99 recommended floating point comparison macro + // Read: isLessThanOrGreaterThan(floatA, floatB) + + if (/* maxAge != interval */ islessgreater(self->_maxAge, interval)) { + __auto_type oldMaxAge = self->_maxAge; + __auto_type newMaxAge = interval; + + self->_maxAge = interval; + + // There are several cases we need to handle here. + // + // 1. If the maxAge was previously enabled and it just got disabled, + // then we need to stop the deleteTimer. (And we might as well release it.) + // + // 2. If the maxAge was previously disabled and it just got enabled, + // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) + // + // 3. If the maxAge was increased, + // then we don't need to do anything. + // + // 4. If the maxAge was decreased, + // then we should do an immediate delete. + + __auto_type shouldDeleteNow = NO; + + if (oldMaxAge > 0.0) { + if (newMaxAge <= 0.0) { + // Handles #1 + + [self destroyDeleteTimer]; + } else if (oldMaxAge > newMaxAge) { + // Handles #4 + shouldDeleteNow = YES; + } + } else if (newMaxAge > 0.0) { + // Handles #2 + shouldDeleteNow = YES; + } + + if (shouldDeleteNow) { + [self performDelete]; + + if (self->_deleteTimer) { + [self updateDeleteTimer]; + } else { + [self createAndStartDeleteTimer]; + } + } + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (NSTimeInterval)deleteInterval { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block NSTimeInterval result; + + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = self->_deleteInterval; + }); + }); + + return result; +} + +- (void)setDeleteInterval:(NSTimeInterval)interval { + __auto_type block = ^{ + @autoreleasepool { + // C99 recommended floating point comparison macro + // Read: isLessThanOrGreaterThan(floatA, floatB) + + if (/* deleteInterval != interval */ islessgreater(self->_deleteInterval, interval)) { + self->_deleteInterval = interval; + + // There are several cases we need to handle here. + // + // 1. If the deleteInterval was previously enabled and it just got disabled, + // then we need to stop the deleteTimer. (And we might as well release it.) + // + // 2. If the deleteInterval was previously disabled and it just got enabled, + // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) + // + // 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date. + // + // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date. + // (Plus we might need to do an immediate delete.) + + if (self->_deleteInterval > 0.0) { + if (self->_deleteTimer == NULL) { + // Handles #2 + // + // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, + // if a delete is needed the timer will fire immediately. + + [self createAndStartDeleteTimer]; + } else { + // Handles #3 + // Handles #4 + // + // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self updateDeleteTimer]; + } + } else if (self->_deleteTimer) { + // Handles #1 + + [self destroyDeleteTimer]; + } + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (BOOL)deleteOnEverySave { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block BOOL result; + + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = self->_deleteOnEverySave; + }); + }); + + return result; +} + +- (void)setDeleteOnEverySave:(BOOL)flag { + __auto_type block = ^{ + self->_deleteOnEverySave = flag; + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Public API +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)savePendingLogEntries { + __auto_type block = ^{ + @autoreleasepool { + [self performSaveAndSuspendSaveTimer]; + } + }; + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_async(self.loggerQueue, block); + } +} + +- (void)deleteOldLogEntries { + __auto_type block = ^{ + @autoreleasepool { + [self performDelete]; + } + }; + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_async(self.loggerQueue, block); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark DDLogger +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)didAddLogger { + // If you override me be sure to invoke [super didAddLogger]; + [self createSuspendedSaveTimer]; + [self createAndStartDeleteTimer]; +} + +- (void)willRemoveLogger { + // If you override me be sure to invoke [super willRemoveLogger]; + [self performSaveAndSuspendSaveTimer]; + [self destroySaveTimer]; + [self destroyDeleteTimer]; +} + +- (void)logMessage:(DDLogMessage *)logMessage { + if ([self db_log:logMessage]) { + __auto_type firstUnsavedEntry = (++_unsavedCount == 1); + + if ((_unsavedCount >= _saveThreshold) && (_saveThreshold > 0)) { + [self performSaveAndSuspendSaveTimer]; + } else if (firstUnsavedEntry) { + _unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0); + [self updateAndResumeSaveTimer]; + } + } +} + +- (void)flush { + // This method is invoked by DDLog's flushLog method. + // + // It is called automatically when the application quits, + // or if the developer invokes DDLog's flushLog method prior to crashing or something. + [self performSaveAndSuspendSaveTimer]; +} + +@end diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger+Internal.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger+Internal.h new file mode 100644 index 000000000..0ca060946 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger+Internal.h @@ -0,0 +1,31 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DDFileLogger (Internal) + +- (void)logData:(NSData *)data; + +// Will assert if used outside logger's queue. +- (void)lt_logData:(NSData *)data; + +- (nullable NSData *)lt_dataForMessage:(DDLogMessage *)message; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger.m new file mode 100644 index 000000000..5caee8cac --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger.m @@ -0,0 +1,1865 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import +#import +#import +#import + +#import "DDFileLogger+Internal.h" + +// We probably shouldn't be using DDLog() statements within the DDLog implementation. +// But we still want to leave our log statements for any future debugging, +// and to allow other developers to trace the implementation (which is a great learning tool). +// +// So we use primitive logging macros around NSLog. +// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog. + +#ifndef DD_NSLOG_LEVEL + #define DD_NSLOG_LEVEL 2 +#endif + +#define NSLogError(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogWarn(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogInfo(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogDebug(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogVerbose(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0) + + +#if TARGET_OS_IPHONE +BOOL doesAppRunInBackground(void); +#endif + +unsigned long long const kDDDefaultLogMaxFileSize = 1024 * 1024; // 1 MB +NSTimeInterval const kDDDefaultLogRollingFrequency = 60 * 60 * 24; // 24 Hours +NSUInteger const kDDDefaultLogMaxNumLogFiles = 5; // 5 Files +unsigned long long const kDDDefaultLogFilesDiskQuota = 20 * 1024 * 1024; // 20 MB + +NSTimeInterval const kDDRollingLeeway = 1.0; // 1s + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDFileLogPlainTextMessageSerializer + +- (instancetype)init { + return [super init]; +} + +- (NSData *)dataForString:(NSString *)string originatingFromMessage:(DDLogMessage *)message { + return [string dataUsingEncoding:NSUTF8StringEncoding] ?: [NSData data]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDLogFileManagerDefault () { + NSDateFormatter *_fileDateFormatter; + NSUInteger _maximumNumberOfLogFiles; + unsigned long long _logFilesDiskQuota; + NSString *_logsDirectory; + BOOL _wasAddedToLogger; +#if TARGET_OS_IPHONE + NSFileProtectionType _defaultFileProtectionLevel; +#endif +} + +@end + +@implementation DDLogFileManagerDefault + +@synthesize maximumNumberOfLogFiles = _maximumNumberOfLogFiles; +@synthesize logFilesDiskQuota = _logFilesDiskQuota; +@synthesize logMessageSerializer = _logMessageSerializer; + +- (instancetype)initWithLogsDirectory:(nullable NSString *)aLogsDirectory { + if ((self = [super init])) { + _maximumNumberOfLogFiles = kDDDefaultLogMaxNumLogFiles; + _logFilesDiskQuota = kDDDefaultLogFilesDiskQuota; + _wasAddedToLogger = NO; + + _fileDateFormatter = [[NSDateFormatter alloc] init]; + [_fileDateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]]; + [_fileDateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + [_fileDateFormatter setDateFormat: @"yyyy'-'MM'-'dd'--'HH'-'mm'-'ss'-'SSS'"]; + + if (aLogsDirectory.length > 0) { + _logsDirectory = [aLogsDirectory copy]; + } else { + _logsDirectory = [[self defaultLogsDirectory] copy]; + } + + _logMessageSerializer = [[DDFileLogPlainTextMessageSerializer alloc] init]; + + NSLogVerbose(@"DDFileLogManagerDefault: logsDirectory:\n%@", [self logsDirectory]); + NSLogVerbose(@"DDFileLogManagerDefault: sortedLogFileNames:\n%@", [self sortedLogFileNames]); + } + + return self; +} + +#if TARGET_OS_IPHONE +- (instancetype)initWithLogsDirectory:(NSString *)logsDirectory + defaultFileProtectionLevel:(NSFileProtectionType)fileProtectionLevel { + + if ((self = [self initWithLogsDirectory:logsDirectory])) { + if ([fileProtectionLevel isEqualToString:NSFileProtectionNone] || + [fileProtectionLevel isEqualToString:NSFileProtectionComplete] || + [fileProtectionLevel isEqualToString:NSFileProtectionCompleteUnlessOpen] || + [fileProtectionLevel isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication]) { + _defaultFileProtectionLevel = fileProtectionLevel; + } + } + + return self; +} +#endif + +- (instancetype)init { + return [self initWithLogsDirectory:nil]; +} + +- (void)didAddToFileLogger:(DDFileLogger *)fileLogger { + _wasAddedToLogger = YES; +} + +- (void)deleteOldFilesForConfigurationChange { + if (!_wasAddedToLogger) return; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + @autoreleasepool { + // See method header for queue reasoning. + [self deleteOldLogFilesWithError:nil]; + } + }); +} + +- (void)setLogFilesDiskQuota:(unsigned long long)logFilesDiskQuota { + if (_logFilesDiskQuota != logFilesDiskQuota) { + _logFilesDiskQuota = logFilesDiskQuota; + NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: logFilesDiskQuota"); + [self deleteOldFilesForConfigurationChange]; + } +} + +- (void)setMaximumNumberOfLogFiles:(NSUInteger)maximumNumberOfLogFiles { + if (_maximumNumberOfLogFiles != maximumNumberOfLogFiles) { + _maximumNumberOfLogFiles = maximumNumberOfLogFiles; + NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: maximumNumberOfLogFiles"); + [self deleteOldFilesForConfigurationChange]; + } +} + +#if TARGET_OS_IPHONE +- (NSFileProtectionType)logFileProtection { + if (_defaultFileProtectionLevel.length > 0) { + return _defaultFileProtectionLevel; + } else if (doesAppRunInBackground()) { + return NSFileProtectionCompleteUntilFirstUserAuthentication; + } else { + return NSFileProtectionCompleteUnlessOpen; + } +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark File Deleting +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Deletes archived log files that exceed the maximumNumberOfLogFiles or logFilesDiskQuota configuration values. + * Method may take a while to execute since we're performing IO. It's not critical that this is synchronized with + * log output, since the files we're deleting are all archived and not in use, therefore this method is called on a + * background queue. + **/ +- (BOOL)deleteOldLogFilesWithError:(NSError *__autoreleasing _Nullable *)error { + NSLogVerbose(@"DDLogFileManagerDefault: %@", NSStringFromSelector(_cmd)); + + if (error) *error = nil; + + __auto_type sortedLogFileInfos = [self sortedLogFileInfos]; + NSUInteger firstIndexToDelete = NSNotFound; + + const unsigned long long diskQuota = self.logFilesDiskQuota; + const NSUInteger maxNumLogFiles = self.maximumNumberOfLogFiles; + + if (diskQuota) { + unsigned long long used = 0; + + for (NSUInteger i = 0; i < sortedLogFileInfos.count; i++) { + DDLogFileInfo *info = sortedLogFileInfos[i]; + used += info.fileSize; + + if (used > diskQuota) { + firstIndexToDelete = i; + break; + } + } + } + + if (maxNumLogFiles) { + if (firstIndexToDelete == NSNotFound) { + firstIndexToDelete = maxNumLogFiles; + } else { + firstIndexToDelete = MIN(firstIndexToDelete, maxNumLogFiles); + } + } + + if (firstIndexToDelete == 0) { + // Do we consider the first file? + // We are only supposed to be deleting archived files. + // In most cases, the first file is likely the log file that is currently being written to. + // So in most cases, we do not want to consider this file for deletion. + + if (sortedLogFileInfos.count > 0) { + if (!sortedLogFileInfos[0].isArchived) { + // Don't delete active file. + firstIndexToDelete++; + } + } + } + + if (firstIndexToDelete != NSNotFound) { + // removing all log files starting with firstIndexToDelete + for (NSUInteger i = firstIndexToDelete; i < sortedLogFileInfos.count; i++) { + __auto_type logFileInfo = sortedLogFileInfos[i]; + + __autoreleasing NSError *deletionError = nil; + __auto_type success = [[NSFileManager defaultManager] removeItemAtPath:logFileInfo.filePath error:&deletionError]; + if (success) { + NSLogInfo(@"DDLogFileManagerDefault: Deleting file: %@", logFileInfo.fileName); + } else { + NSLogError(@"DDLogFileManagerDefault: Error deleting file %@", deletionError); + if (error) { + *error = deletionError; + return NO; // If we were given an error, stop after the first failure! + } + } + } + } + + return YES; +} + +- (BOOL)cleanupLogFilesWithError:(NSError *__autoreleasing _Nullable *)error { + return [self deleteOldLogFilesWithError:error]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Log Files +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns the path to the default logs directory. + * If the logs directory doesn't exist, this method automatically creates it. + **/ +- (NSString *)defaultLogsDirectory { +#if TARGET_OS_IPHONE + __auto_type paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + __auto_type baseDir = paths.firstObject; + __auto_type logsDirectory = [baseDir stringByAppendingPathComponent:@"Logs"]; +#else + __auto_type appName = [[NSProcessInfo processInfo] processName]; + __auto_type paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); + __auto_type basePath = ([paths count] > 0) ? paths[0] : NSTemporaryDirectory(); + __auto_type logsDirectory = [[basePath stringByAppendingPathComponent:@"Logs"] stringByAppendingPathComponent:appName]; +#endif + + return logsDirectory; +} + +- (NSString *)logsDirectory { + // We could do this check once, during initialization, and not bother again. + // But this way the code continues to work if the directory gets deleted while the code is running. + + NSAssert(_logsDirectory.length > 0, @"Directory must be set."); + + __autoreleasing NSError *error = nil; + __auto_type success = [[NSFileManager defaultManager] createDirectoryAtPath:_logsDirectory + withIntermediateDirectories:YES + attributes:nil + error:&error]; + if (!success) { + NSLogError(@"DDFileLogManagerDefault: Error creating logsDirectory: %@", error); + } + + return _logsDirectory; +} + +- (BOOL)isLogFile:(NSString *)fileName { + __auto_type appName = [self applicationName]; + + // We need to add a space to the name as otherwise we could match applications that have the name prefix. + return [fileName hasPrefix:[appName stringByAppendingString:@" "]] && [fileName hasSuffix:@".log"]; +} + +// if you change formatter, then change sortedLogFileInfos method also accordingly +- (NSDateFormatter *)logFileDateFormatter { + return _fileDateFormatter; +} + +- (NSArray *)unsortedLogFilePaths { + __auto_type logsDirectory = [self logsDirectory]; + + __autoreleasing NSError *error = nil; + __auto_type fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:logsDirectory error:&error]; + if (!fileNames && error) { + NSLogError(@"DDFileLogManagerDefault: Error listing log file directory: %@", error); + return [[NSArray alloc] init]; + } + + __auto_type unsortedLogFilePaths = [NSMutableArray arrayWithCapacity:[fileNames count]]; + + for (NSString *fileName in fileNames) { + // Filter out any files that aren't log files. (Just for extra safety) +#if TARGET_IPHONE_SIMULATOR + // This is only used on the iPhone simulator for backward compatibility reason. + // + // In case of iPhone simulator there can be 'archived' extension. isLogFile: + // method knows nothing about it. Thus removing it for this method. + __auto_type theFileName = [fileName stringByReplacingOccurrencesOfString:@".archived" + withString:@""]; + + if ([self isLogFile:theFileName]) +#else + if ([self isLogFile:fileName]) +#endif + { + __auto_type filePath = [logsDirectory stringByAppendingPathComponent:fileName]; + [unsortedLogFilePaths addObject:filePath]; + } + } + + return unsortedLogFilePaths; +} + +- (NSArray *)unsortedLogFileNames { + __auto_type unsortedLogFilePaths = [self unsortedLogFilePaths]; + __auto_type unsortedLogFileNames = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]]; + + for (NSString *filePath in unsortedLogFilePaths) { + [unsortedLogFileNames addObject:[filePath lastPathComponent]]; + } + + return unsortedLogFileNames; +} + +- (NSArray *)unsortedLogFileInfos { + __auto_type unsortedLogFilePaths = [self unsortedLogFilePaths]; + __auto_type unsortedLogFileInfos = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]]; + + for (NSString *filePath in unsortedLogFilePaths) { + __auto_type logFileInfo = [[DDLogFileInfo alloc] initWithFilePath:filePath]; + [unsortedLogFileInfos addObject:logFileInfo]; + } + + return unsortedLogFileInfos; +} + +- (NSArray *)sortedLogFilePaths { + __auto_type sortedLogFileInfos = [self sortedLogFileInfos]; + __auto_type sortedLogFilePaths = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]]; + + for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) { + [sortedLogFilePaths addObject:[logFileInfo filePath]]; + } + + return sortedLogFilePaths; +} + +- (NSArray *)sortedLogFileNames { + __auto_type sortedLogFileInfos = [self sortedLogFileInfos]; + __auto_type sortedLogFileNames = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]]; + + for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) { + [sortedLogFileNames addObject:[logFileInfo fileName]]; + } + + return sortedLogFileNames; +} + +- (NSArray *)sortedLogFileInfos { + return [[self unsortedLogFileInfos] sortedArrayUsingComparator:^NSComparisonResult(DDLogFileInfo *obj1, + DDLogFileInfo *obj2) { + NSDate *date1 = [NSDate date]; + NSDate *date2 = [NSDate date]; + + __auto_type arrayComponent = [[obj1 fileName] componentsSeparatedByString:@" "]; + if (arrayComponent.count > 0) { + NSString *stringDate = arrayComponent.lastObject; + stringDate = [stringDate stringByReplacingOccurrencesOfString:@".log" withString:@""]; +#if TARGET_IPHONE_SIMULATOR + // This is only used on the iPhone simulator for backward compatibility reason. + stringDate = [stringDate stringByReplacingOccurrencesOfString:@".archived" withString:@""]; +#endif + date1 = [[self logFileDateFormatter] dateFromString:stringDate] ?: [obj1 creationDate]; + } + + arrayComponent = [[obj2 fileName] componentsSeparatedByString:@" "]; + if (arrayComponent.count > 0) { + NSString *stringDate = arrayComponent.lastObject; + stringDate = [stringDate stringByReplacingOccurrencesOfString:@".log" withString:@""]; +#if TARGET_IPHONE_SIMULATOR + // This is only used on the iPhone simulator for backward compatibility reason. + stringDate = [stringDate stringByReplacingOccurrencesOfString:@".archived" withString:@""]; +#endif + date2 = [[self logFileDateFormatter] dateFromString:stringDate] ?: [obj2 creationDate]; + } + + return [date2 compare:date1 ?: [NSDate date]]; + }]; + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Creation +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// If you change newLogFileName, then change `isLogFile:` method also accordingly. +- (NSString *)newLogFileName { + __auto_type appName = [self applicationName]; + __auto_type dateFormatter = [self logFileDateFormatter]; + __auto_type formattedDate = [dateFormatter stringFromDate:[NSDate date]]; + + return [NSString stringWithFormat:@"%@ %@.log", appName, formattedDate]; +} + +- (nullable NSString *)logFileHeader { + return nil; +} + +- (NSData *)logFileHeaderData { + NSString *fileHeaderStr = [self logFileHeader]; + + if (fileHeaderStr.length == 0) { + return nil; + } + + if (![fileHeaderStr hasSuffix:@"\n"]) { + fileHeaderStr = [fileHeaderStr stringByAppendingString:@"\n"]; + } + + return [_logMessageSerializer dataForString:fileHeaderStr originatingFromMessage:nil]; +} + +- (NSString *)createNewLogFileWithError:(NSError *__autoreleasing _Nullable *)error { + static NSUInteger MAX_ALLOWED_ERROR = 5; + + __auto_type fileName = [self newLogFileName]; + __auto_type logsDirectory = [self logsDirectory]; + __auto_type fileHeader = [self logFileHeaderData] ?: [NSData data]; + + NSString *baseName = nil; + NSString *extension; + NSUInteger attempt = 1; + NSUInteger criticalErrors = 0; + NSError *lastCriticalError; + + if (error) *error = nil; + do { + if (criticalErrors >= MAX_ALLOWED_ERROR) { + NSLogError(@"DDLogFileManagerDefault: Bailing file creation, encountered %ld errors.", + (unsigned long)criticalErrors); + if (error) *error = lastCriticalError; + return nil; + } + + NSString *actualFileName; + if (attempt > 1) { + if (baseName == nil) { + baseName = [fileName stringByDeletingPathExtension]; + extension = [fileName pathExtension]; + } + + actualFileName = [baseName stringByAppendingFormat:@" %lu", (unsigned long)attempt]; + if (extension.length) { + actualFileName = [actualFileName stringByAppendingPathExtension:extension]; + } + } else { + actualFileName = fileName; + } + + __auto_type filePath = [logsDirectory stringByAppendingPathComponent:actualFileName]; + + __autoreleasing NSError *currentError = nil; + __auto_type success = [fileHeader writeToFile:filePath options:NSDataWritingAtomic error:¤tError]; + +#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST + if (success) { + // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen. + // + // But in case if app is able to launch from background we need to have an ability to open log file any time we + // want (even if device is locked). Thats why that attribute have to be changed to + // NSFileProtectionCompleteUntilFirstUserAuthentication. + NSDictionary *attributes = @{NSFileProtectionKey: [self logFileProtection]}; + success = [[NSFileManager defaultManager] setAttributes:attributes + ofItemAtPath:filePath + error:¤tError]; + } +#endif + + if (success) { + NSLogVerbose(@"DDLogFileManagerDefault: Created new log file: %@", actualFileName); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // Since we just created a new log file, we may need to delete some old log files + // Note that we don't on errors here! The new log file was created, so this method technically succeeded! + [self deleteOldLogFilesWithError:nil]; + }); + return filePath; + } else if (currentError.code == NSFileWriteFileExistsError) { + attempt++; + } else { + NSLogError(@"DDLogFileManagerDefault: Critical error while creating log file: %@", currentError); + criticalErrors++; + lastCriticalError = currentError; + } + } while (YES); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Utility +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSString *)applicationName { + static NSString *_appName; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + _appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]; + + if (_appName.length == 0) { + _appName = [[NSProcessInfo processInfo] processName]; + } + + if (_appName.length == 0) { + _appName = @""; + } + }); + + return _appName; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDLogFileFormatterDefault () { + NSDateFormatter *_dateFormatter; +} + +@end + +@implementation DDLogFileFormatterDefault + +- (instancetype)init { + return [self initWithDateFormatter:nil]; +} + +- (instancetype)initWithDateFormatter:(nullable NSDateFormatter *)aDateFormatter { + if ((self = [super init])) { + if (aDateFormatter) { + _dateFormatter = aDateFormatter; + } else { + _dateFormatter = [[NSDateFormatter alloc] init]; + [_dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style + [_dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]]; + [_dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + [_dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss:SSS"]; + } + } + + return self; +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + __auto_type dateAndTime = [_dateFormatter stringFromDate:logMessage->_timestamp]; + // Note: There are two spaces between the date and the message. + return [NSString stringWithFormat:@"%@ %@", dateAndTime, logMessage->_message]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDFileLogger () { + id _logFileManager; + + DDLogFileInfo *_currentLogFileInfo; + NSFileHandle *_currentLogFileHandle; + + dispatch_source_t _currentLogFileVnode; + + NSTimeInterval _rollingFrequency; + dispatch_source_t _rollingTimer; + + unsigned long long _maximumFileSize; + + dispatch_queue_t _completionQueue; +} + +@end + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" +@implementation DDFileLogger +#pragma clang diagnostic pop + +- (instancetype)init { + return [self initWithLogFileManager:[[DDLogFileManagerDefault alloc] init] + completionQueue:nil]; +} + +- (instancetype)initWithLogFileManager:(id)logFileManager { + return [self initWithLogFileManager:logFileManager completionQueue:nil]; +} + +- (instancetype)initWithLogFileManager:(id )aLogFileManager + completionQueue:(nullable dispatch_queue_t)dispatchQueue { + if ((self = [super init])) { + _completionQueue = dispatchQueue ?: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + _maximumFileSize = kDDDefaultLogMaxFileSize; + _rollingFrequency = kDDDefaultLogRollingFrequency; + _automaticallyAppendNewlineForCustomFormatters = YES; + + _logFileManager = aLogFileManager; + _logFormatter = [DDLogFileFormatterDefault new]; + + if ([_logFileManager respondsToSelector:@selector(didAddToFileLogger:)]) { + [_logFileManager didAddToFileLogger:self]; + } + } + + return self; +} + +- (void)lt_cleanup { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if (_currentLogFileHandle != nil) { + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + __autoreleasing NSError *error = nil; + __auto_type success = [_currentLogFileHandle synchronizeAndReturnError:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error); + } + success = [_currentLogFileHandle closeAndReturnError:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to close file: %@", error); + } + } else { + @try { + [_currentLogFileHandle synchronizeFile]; + } + @catch (NSException *exception) { + NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception); + } + [_currentLogFileHandle closeFile]; + } + _currentLogFileHandle = nil; + } + + if (_currentLogFileVnode) { + dispatch_source_cancel(_currentLogFileVnode); + _currentLogFileVnode = NULL; + } + + if (_rollingTimer) { + dispatch_source_cancel(_rollingTimer); + _rollingTimer = NULL; + } +} + +- (void)dealloc { + if (self.isOnInternalLoggerQueue) { + [self lt_cleanup]; + } else { + dispatch_sync(self.loggerQueue, ^{ + [self lt_cleanup]; + }); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Properties +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (unsigned long long)maximumFileSize { + __block unsigned long long result; + + __auto_type block = ^{ + result = self->_maximumFileSize; + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the maximumFileSize variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, block); + }); + + return result; +} + +- (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize { + __auto_type block = ^{ + @autoreleasepool { + self->_maximumFileSize = newMaximumFileSize; + if (self->_currentLogFileHandle != nil) { + [self lt_maybeRollLogFileDueToSize]; + } + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the maximumFileSize variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); +} + +- (NSTimeInterval)rollingFrequency { + __block NSTimeInterval result; + + __auto_type block = ^{ + result = self->_rollingFrequency; + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation should access the rollingFrequency variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, block); + }); + + return result; +} + +- (void)setRollingFrequency:(NSTimeInterval)newRollingFrequency { + __auto_type block = ^{ + @autoreleasepool { + self->_rollingFrequency = newRollingFrequency; + if (self->_currentLogFileHandle != nil) { + [self lt_maybeRollLogFileDueToAge]; + } + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation should access the rollingFrequency variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark File Rolling +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)lt_scheduleTimerToRollLogFileDueToAge { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if (_rollingTimer) { + dispatch_source_cancel(_rollingTimer); + _rollingTimer = NULL; + } + + if (_currentLogFileInfo == nil || _rollingFrequency <= 0.0) { + return; + } + + __auto_type logFileCreationDate = [_currentLogFileInfo creationDate]; + __auto_type frequency = MIN(_rollingFrequency, DBL_MAX - [logFileCreationDate timeIntervalSinceReferenceDate]); + __auto_type logFileRollingDate = [logFileCreationDate dateByAddingTimeInterval:frequency]; + + NSLogVerbose(@"DDFileLogger: scheduleTimerToRollLogFileDueToAge"); + NSLogVerbose(@"DDFileLogger: logFileCreationDate : %@", logFileCreationDate); + NSLogVerbose(@"DDFileLogger: actual rollingFrequency: %f", frequency); + NSLogVerbose(@"DDFileLogger: logFileRollingDate : %@", logFileRollingDate); + + _rollingTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _loggerQueue); + + __weak __auto_type weakSelf = self; + dispatch_source_set_event_handler(_rollingTimer, ^{ @autoreleasepool { + [weakSelf lt_maybeRollLogFileDueToAge]; + } }); + +#if !OS_OBJECT_USE_OBJC + dispatch_source_t theRollingTimer = _rollingTimer; + dispatch_source_set_cancel_handler(_rollingTimer, ^{ + dispatch_release(theRollingTimer); + }); +#endif + + static NSTimeInterval const kDDMaxTimerDelay = LLONG_MAX / NSEC_PER_SEC; + __auto_type delay = (int64_t)(MIN([logFileRollingDate timeIntervalSinceNow], kDDMaxTimerDelay) * (NSTimeInterval)NSEC_PER_SEC); + __auto_type fireTime = dispatch_walltime(NULL, delay); // `NULL` uses `gettimeofday` internally + + dispatch_source_set_timer(_rollingTimer, fireTime, DISPATCH_TIME_FOREVER, (uint64_t)kDDRollingLeeway * NSEC_PER_SEC); + dispatch_activate(_rollingTimer); +} + +- (void)rollLogFile { + [self rollLogFileWithCompletionBlock:nil]; +} + +- (void)rollLogFileWithCompletionBlock:(nullable void (^)(void))completionBlock { + // This method is public. + // We need to execute the rolling on our logging thread/queue. + + __auto_type block = ^{ + @autoreleasepool { + [self lt_rollLogFileNow]; + + if (completionBlock) { + dispatch_async(self->_completionQueue, ^{ + completionBlock(); + }); + } + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)lt_rollLogFileNow { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + NSLogVerbose(@"DDFileLogger: %@", NSStringFromSelector(_cmd)); + + if (_currentLogFileHandle == nil) { + return; + } + + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + __autoreleasing NSError *error = nil; + __auto_type success = [_currentLogFileHandle synchronizeAndReturnError:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error); + } + success = [_currentLogFileHandle closeAndReturnError:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to close file: %@", error); + } + } else { + @try { + [_currentLogFileHandle synchronizeFile]; + } + @catch (NSException *exception) { + NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception); + } + [_currentLogFileHandle closeFile]; + } + _currentLogFileHandle = nil; + + _currentLogFileInfo.isArchived = YES; + + const __auto_type logFileManagerRespondsToNewArchiveSelector = [_logFileManager respondsToSelector:@selector(didArchiveLogFile:wasRolled:)]; + const __auto_type logFileManagerRespondsToSelector = (logFileManagerRespondsToNewArchiveSelector + || [_logFileManager respondsToSelector:@selector(didRollAndArchiveLogFile:)]); + NSString *archivedFilePath = (logFileManagerRespondsToSelector) ? [_currentLogFileInfo.filePath copy] : nil; + _currentLogFileInfo = nil; + + if (logFileManagerRespondsToSelector) { + dispatch_block_t block; + if (logFileManagerRespondsToNewArchiveSelector) { + block = ^{ + [self->_logFileManager didArchiveLogFile:archivedFilePath wasRolled:YES]; + }; + } else { + block = ^{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self->_logFileManager didRollAndArchiveLogFile:archivedFilePath]; +#pragma clang diagnostic pop + }; + } + dispatch_async(_completionQueue, block); + } + + if (_currentLogFileVnode) { + dispatch_source_cancel(_currentLogFileVnode); + _currentLogFileVnode = nil; + } + + if (_rollingTimer) { + dispatch_source_cancel(_rollingTimer); + _rollingTimer = nil; + } +} + +- (void)lt_maybeRollLogFileDueToAge { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if (_rollingFrequency > 0.0 && (_currentLogFileInfo.age + kDDRollingLeeway) >= _rollingFrequency) { + NSLogVerbose(@"DDFileLogger: Rolling log file due to age..."); + [self lt_rollLogFileNow]; + } else { + [self lt_scheduleTimerToRollLogFileDueToAge]; + } +} + +- (void)lt_maybeRollLogFileDueToSize { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + // This method is called from logMessage. + // Keep it FAST. + + // Note: Use direct access to maximumFileSize variable. + // We specifically wrote our own getter/setter method to allow us to do this (for performance reasons). + + if (_currentLogFileHandle != nil && _maximumFileSize > 0) { + unsigned long long fileSize; + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + __autoreleasing NSError *error = nil; + __auto_type success = [_currentLogFileHandle getOffset:&fileSize error:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to get offset: %@", error); + return; + } + } else { + fileSize = [_currentLogFileHandle offsetInFile]; + } + + if (fileSize >= _maximumFileSize) { + NSLogVerbose(@"DDFileLogger: Rolling log file due to size (%qu)...", fileSize); + + [self lt_rollLogFileNow]; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark File Logging +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)lt_shouldLogFileBeArchived:(DDLogFileInfo *)mostRecentLogFileInfo { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if ([self shouldArchiveRecentLogFileInfo:mostRecentLogFileInfo]) { + return YES; + } else if (_maximumFileSize > 0 && mostRecentLogFileInfo.fileSize >= _maximumFileSize) { + return YES; + } else if (_rollingFrequency > 0.0 && mostRecentLogFileInfo.age >= _rollingFrequency) { + return YES; + } + +#if TARGET_OS_IPHONE + // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen. + // + // But in case if app is able to launch from background we need to have an ability to open log file any time we + // want (even if device is locked). Thats why that attribute have to be changed to + // NSFileProtectionCompleteUntilFirstUserAuthentication. + // + // If previous log was created when app wasn't running in background, but now it is - we archive it and create + // a new one. + // + // If user has overwritten to NSFileProtectionNone there is no need to create a new one. + if (doesAppRunInBackground()) { + NSFileProtectionType key = mostRecentLogFileInfo.fileAttributes[NSFileProtectionKey]; + __auto_type isUntilFirstAuth = [key isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication]; + __auto_type isNone = [key isEqualToString:NSFileProtectionNone]; + + if (key != nil && !isUntilFirstAuth && !isNone) { + return YES; + } + } +#endif + + return NO; +} + +/** + * Returns the log file that should be used. + * If there is an existing log file that is suitable, within the + * constraints of maximumFileSize and rollingFrequency, then it is returned. + * + * Otherwise a new file is created and returned. + **/ +- (DDLogFileInfo *)currentLogFileInfo { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + // Do not access this method on any Lumberjack queue, will deadlock. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block DDLogFileInfo *info = nil; + __auto_type block = ^{ + info = [self lt_currentLogFileInfo]; + }; + + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self->_loggerQueue, block); + }); + + return info; +} + +- (DDLogFileInfo *)lt_currentLogFileInfo { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + // Get the current log file info ivar (might be nil). + __auto_type newCurrentLogFile = _currentLogFileInfo; + + // Check if we're resuming and if so, get the first of the sorted log file infos. + __auto_type isResuming = newCurrentLogFile == nil; + if (isResuming) { + NSArray *sortedLogFileInfos = [_logFileManager sortedLogFileInfos]; + newCurrentLogFile = sortedLogFileInfos.firstObject; + } + + // Check if the file we've found is still valid. Otherwise create a new one. + if (newCurrentLogFile != nil && [self lt_shouldUseLogFile:newCurrentLogFile isResuming:isResuming]) { + if (isResuming) { + NSLogVerbose(@"DDFileLogger: Resuming logging with file %@", newCurrentLogFile.fileName); + } + _currentLogFileInfo = newCurrentLogFile; + } else { + NSString *currentLogFilePath; + if ([_logFileManager respondsToSelector:@selector(createNewLogFileWithError:)]) { + __autoreleasing NSError *error; // Don't initialize error to nil since it will be done in -createNewLogFileWithError: + currentLogFilePath = [_logFileManager createNewLogFileWithError:&error]; + if (!currentLogFilePath) { + NSLogError(@"DDFileLogger: Failed to create new log file: %@", error); + } + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + NSAssert([_logFileManager respondsToSelector:@selector(createNewLogFile)], + @"Invalid log file manager! Responds neither to `-createNewLogFileWithError:` nor `-createNewLogFile`!"); + currentLogFilePath = [_logFileManager createNewLogFile]; +#pragma clang diagnostic pop + if (!currentLogFilePath) { + NSLogError(@"DDFileLogger: Failed to create new log file"); + } + } + // Use static factory method here, since it checks for nil (and is unavailable to Swift). + _currentLogFileInfo = [DDLogFileInfo logFileWithPath:currentLogFilePath]; + } + + return _currentLogFileInfo; +} + +- (BOOL)lt_shouldUseLogFile:(nonnull DDLogFileInfo *)logFileInfo isResuming:(BOOL)isResuming { + NSParameterAssert(logFileInfo); + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + // Check if the log file is archived. We must not use archived log files. + if (logFileInfo.isArchived) { + return NO; + } + + // Don't follow symlink + if (logFileInfo.isSymlink) { + return NO; + } + + // If we're resuming, we need to check if the log file is allowed for reuse or needs to be archived. + if (isResuming && (_doNotReuseLogFiles || [self lt_shouldLogFileBeArchived:logFileInfo])) { + logFileInfo.isArchived = YES; + + const __auto_type logFileManagerRespondsToNewArchiveSelector = [_logFileManager respondsToSelector:@selector(didArchiveLogFile:wasRolled:)]; + if (logFileManagerRespondsToNewArchiveSelector || [_logFileManager respondsToSelector:@selector(didArchiveLogFile:)]) { + NSString *archivedFilePath = [logFileInfo.filePath copy]; + dispatch_block_t block; + if (logFileManagerRespondsToNewArchiveSelector) { + block = ^{ + [self->_logFileManager didArchiveLogFile:archivedFilePath wasRolled:NO]; + }; + } else { + block = ^{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self->_logFileManager didArchiveLogFile:archivedFilePath]; +#pragma clang diagnostic pop + }; + } + dispatch_async(_completionQueue, block); + } + + return NO; + } + + // All checks have passed. It's valid. + return YES; +} + +- (void)lt_monitorCurrentLogFileForExternalChanges { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + NSAssert(_currentLogFileHandle, @"Can not monitor without handle."); + + // This seems to work around crashes when an active source is replaced / released. + // See https://github.com/CocoaLumberjack/CocoaLumberjack/issues/1341 + // And https://stackoverflow.com/questions/36296528/what-does-this-dispatch-xref-dispose-error-mean + if (_currentLogFileVnode) { + dispatch_source_cancel(_currentLogFileVnode); + } + + _currentLogFileVnode = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, + (uintptr_t)[_currentLogFileHandle fileDescriptor], + DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE, + _loggerQueue); + + __weak __auto_type weakSelf = self; + dispatch_source_set_event_handler(_currentLogFileVnode, ^{ @autoreleasepool { + NSLogInfo(@"DDFileLogger: Current logfile was moved. Rolling it and creating a new one"); + [weakSelf lt_rollLogFileNow]; + } }); + +#if !OS_OBJECT_USE_OBJC + dispatch_source_t vnode = _currentLogFileVnode; + dispatch_source_set_cancel_handler(_currentLogFileVnode, ^{ + dispatch_release(vnode); + }); +#endif + + dispatch_activate(_currentLogFileVnode); +} + +- (NSFileHandle *)lt_currentLogFileHandle { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if (_currentLogFileHandle == nil) { + __auto_type logFilePath = [[self lt_currentLogFileInfo] filePath]; + _currentLogFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath]; + if (_currentLogFileHandle != nil) { + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + __autoreleasing NSError *error = nil; + __auto_type success = [_currentLogFileHandle seekToEndReturningOffset:nil error:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to seek to end of file: %@", error); + } + } else { + [_currentLogFileHandle seekToEndOfFile]; + } + + [self lt_scheduleTimerToRollLogFileDueToAge]; + [self lt_monitorCurrentLogFileForExternalChanges]; + } + } + + return _currentLogFileHandle; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark DDLogger Protocol +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static int exception_count = 0; + +- (void)logMessage:(DDLogMessage *)logMessage { + // Don't need to check for isOnInternalLoggerQueue, -lt_dataForMessage: will do it for us. + NSData *data = [self lt_dataForMessage:logMessage]; + if (data.length == 0) { + return; + } + + [self lt_logData:data]; +} + +- (void)willLogMessage:(DDLogFileInfo *)logFileInfo {} + +- (void)didLogMessage:(DDLogFileInfo *)logFileInfo { + [self lt_maybeRollLogFileDueToSize]; +} + +- (BOOL)shouldArchiveRecentLogFileInfo:(__unused DDLogFileInfo *)recentLogFileInfo { + return NO; +} + +- (void)willRemoveLogger { + [self lt_rollLogFileNow]; +} + +- (void)flush { + // This method is public. + // We need to execute the rolling on our logging thread/queue. + + dispatch_block_t block = ^{ + @autoreleasepool { + [self lt_flush]; + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, block); + }); + } +} + +- (void)lt_flush { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if (_currentLogFileHandle != nil) { + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + __autoreleasing NSError *error = nil; + __auto_type success = [_currentLogFileHandle synchronizeAndReturnError:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error); + } + } else { + @try { + [_currentLogFileHandle synchronizeFile]; + } @catch (NSException *exception) { + NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception); + } + } + } +} + +- (DDLoggerName)loggerName { + return DDLoggerNameFile; +} + +@end + +@implementation DDFileLogger (Internal) + +- (void)logData:(NSData *)data { + // This method is public. + // We need to execute the rolling on our logging thread/queue. + + __auto_type block = ^{ + @autoreleasepool { + [self lt_logData:data]; + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, block); + }); + } +} + +- (void)lt_deprecationCatchAll {} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { + if (aSelector == @selector(willLogMessage) || aSelector == @selector(didLogMessage)) { + // Ignore calls to deprecated methods. + return [self methodSignatureForSelector:@selector(lt_deprecationCatchAll)]; + } + + return [super methodSignatureForSelector:aSelector]; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation { + if (anInvocation.selector != @selector(lt_deprecationCatchAll)) { + [super forwardInvocation:anInvocation]; + } +} + +- (void)lt_logData:(NSData *)data { + static __auto_type implementsDeprecatedWillLog = NO; + static __auto_type implementsDeprecatedDidLog = NO; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + implementsDeprecatedWillLog = [self respondsToSelector:@selector(willLogMessage)]; + implementsDeprecatedDidLog = [self respondsToSelector:@selector(didLogMessage)]; + }); + + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if (data.length == 0) { + return; + } + + @try { + // Make sure that _currentLogFileInfo is initialised before being used. + __auto_type handle = [self lt_currentLogFileHandle]; + + if (implementsDeprecatedWillLog) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self willLogMessage]; +#pragma clang diagnostic pop + } else { + [self willLogMessage:_currentLogFileInfo]; + } + + // use an advisory lock to coordinate write with other processes + __auto_type fd = [handle fileDescriptor]; + while(flock(fd, LOCK_EX) != 0) { + NSLogError(@"DDFileLogger: Could not lock logfile, retrying in 1ms: %s (%d)", strerror(errno), errno); + usleep(1000); + } + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + __autoreleasing NSError *error = nil; + __auto_type success = [handle seekToEndReturningOffset:nil error:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to seek to end of file: %@", error); + } + success = [handle writeData:data error:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to write data: %@", error); + } + } else { + [handle seekToEndOfFile]; + [handle writeData:data]; + } + flock(fd, LOCK_UN); + + if (implementsDeprecatedDidLog) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self didLogMessage]; +#pragma clang diagnostic pop + } else { + [self didLogMessage:_currentLogFileInfo]; + } + + } + @catch (NSException *exception) { + exception_count++; + + if (exception_count <= 10) { + NSLogError(@"DDFileLogger.logMessage: %@", exception); + + if (exception_count == 10) { + NSLogError(@"DDFileLogger.logMessage: Too many exceptions -- will not log any more of them."); + } + } + } +} + +- (id )lt_logFileSerializer { + if ([_logFileManager respondsToSelector:@selector(logMessageSerializer)]) { + return _logFileManager.logMessageSerializer; + } else { + return [[DDFileLogPlainTextMessageSerializer alloc] init]; + } +} + +- (NSData *)lt_dataForMessage:(DDLogMessage *)logMessage { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + __auto_type messageString = logMessage->_message; + __auto_type isFormatted = NO; + + if (_logFormatter != nil) { + messageString = [_logFormatter formatLogMessage:logMessage]; + isFormatted = messageString != logMessage->_message; + } + + if (messageString.length == 0) { + return nil; + } + + __auto_type shouldFormat = !isFormatted || _automaticallyAppendNewlineForCustomFormatters; + if (shouldFormat && ![messageString hasSuffix:@"\n"]) { + messageString = [messageString stringByAppendingString:@"\n"]; + } + + return [[self lt_logFileSerializer] dataForString:messageString originatingFromMessage:logMessage]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static NSString * const kDDXAttrArchivedName = @"lumberjack.log.archived"; + +@interface DDLogFileInfo () { + __strong NSString *_filePath; + __strong NSString *_fileName; + + __strong NSDictionary *_fileAttributes; + + __strong NSDate *_creationDate; + __strong NSDate *_modificationDate; + + unsigned long long _fileSize; +} + +#if TARGET_IPHONE_SIMULATOR +// Old implementation of extended attributes on the simulator. +- (BOOL)_hasExtensionAttributeWithName:(NSString *)attrName; +- (void)_removeExtensionAttributeWithName:(NSString *)attrName; +#endif + +@end + + +@implementation DDLogFileInfo + +@synthesize filePath; + +@dynamic fileName; +@dynamic fileAttributes; +@dynamic creationDate; +@dynamic modificationDate; +@dynamic fileSize; +@dynamic age; + +@dynamic isArchived; + +#pragma mark Lifecycle + ++ (instancetype)logFileWithPath:(NSString *)aFilePath { + if (!aFilePath) return nil; + return [[self alloc] initWithFilePath:aFilePath]; +} + +- (instancetype)initWithFilePath:(NSString *)aFilePath { + NSParameterAssert(aFilePath); + if ((self = [super init])) { + filePath = [aFilePath copy]; + } + + return self; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Standard Info +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSDictionary *)fileAttributes { + if (_fileAttributes == nil && filePath != nil) { + __autoreleasing NSError *error = nil; + _fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error]; + if (!_fileAttributes) { + NSLogError(@"DDLogFileInfo: Failed to read file attributes: %@", error); + } + } + + return _fileAttributes ?: @{}; +} + +- (NSString *)fileName { + if (_fileName == nil) { + _fileName = [filePath lastPathComponent]; + } + + return _fileName; +} + +- (NSDate *)modificationDate { + if (_modificationDate == nil) { + _modificationDate = self.fileAttributes[NSFileModificationDate]; + } + + return _modificationDate; +} + +- (NSDate *)creationDate { + if (_creationDate == nil) { + _creationDate = self.fileAttributes[NSFileCreationDate]; + } + + return _creationDate; +} + +- (unsigned long long)fileSize { + if (_fileSize == 0) { + _fileSize = [self.fileAttributes[NSFileSize] unsignedLongLongValue]; + } + + return _fileSize; +} + +- (NSTimeInterval)age { + return -[[self creationDate] timeIntervalSinceNow]; +} + +- (BOOL)isSymlink { + return self.fileAttributes[NSFileType] == NSFileTypeSymbolicLink; +} + +- (NSString *)description { + return [@{ @"filePath": self.filePath ? : @"", + @"fileName": self.fileName ? : @"", + @"fileAttributes": self.fileAttributes ? : @"", + @"creationDate": self.creationDate ? : @"", + @"modificationDate": self.modificationDate ? : @"", + @"fileSize": @(self.fileSize), + @"age": @(self.age), + @"isArchived": @(self.isArchived) } description]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Archiving +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)isArchived { + return [self hasExtendedAttributeWithName:kDDXAttrArchivedName]; +} + +- (void)setIsArchived:(BOOL)flag { + if (flag) { + [self addExtendedAttributeWithName:kDDXAttrArchivedName]; + } else { + [self removeExtendedAttributeWithName:kDDXAttrArchivedName]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Changes +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)reset { + _fileName = nil; + _fileAttributes = nil; + _creationDate = nil; + _modificationDate = nil; +} + +- (void)renameFile:(NSString *)newFileName { + // This method is only used on the iPhone simulator, where normal extended attributes are broken. + // See full explanation in the header file. + + if (![newFileName isEqualToString:[self fileName]]) { + __auto_type fileManager = [NSFileManager defaultManager]; + __auto_type fileDir = [filePath stringByDeletingLastPathComponent]; + __auto_type newFilePath = [fileDir stringByAppendingPathComponent:newFileName]; + + // We only want to assert when we're not using the simulator, as we're "archiving" a log file with this method in the sim + // (in which case the file might not exist anymore and neither does it parent folder). +#if defined(DEBUG) && (!defined(TARGET_IPHONE_SIMULATOR) || !TARGET_IPHONE_SIMULATOR) + __auto_type directory = NO; + [fileManager fileExistsAtPath:fileDir isDirectory:&directory]; + NSAssert(directory, @"Containing directory must exist."); +#endif + + __autoreleasing NSError *error = nil; + __auto_type success = [fileManager removeItemAtPath:newFilePath error:&error]; + if (!success && error.code != NSFileNoSuchFileError) { + NSLogError(@"DDLogFileInfo: Error deleting archive (%@): %@", self.fileName, error); + } + + success = [fileManager moveItemAtPath:filePath toPath:newFilePath error:&error]; + + // When a log file is deleted, moved or renamed on the simulator, we attempt to rename it as a + // result of "archiving" it, but since the file doesn't exist anymore, needless error logs are printed + // We therefore ignore this error, and assert that the directory we are copying into exists (which + // is the only other case where this error code can come up). +#if TARGET_IPHONE_SIMULATOR + if (!success && error.code != NSFileNoSuchFileError) +#else + if (!success) +#endif + { + NSLogError(@"DDLogFileInfo: Error renaming file (%@): %@", self.fileName, error); + } + + filePath = newFilePath; + [self reset]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Attribute Management +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if TARGET_IPHONE_SIMULATOR + +// Old implementation of extended attributes on the simulator. + +// Extended attributes were not working properly on the simulator +// due to misuse of setxattr() function. +// Now that this is fixed in the new implementation, we want to keep +// backward compatibility with previous simulator installations. + +static NSString * const kDDExtensionSeparator = @"."; + +static NSString *_xattrToExtensionName(NSString *attrName) { + static NSDictionary* _xattrToExtensionNameMap; + static dispatch_once_t _token; + dispatch_once(&_token, ^{ + _xattrToExtensionNameMap = @{ kDDXAttrArchivedName: @"archived" }; + }); + return [_xattrToExtensionNameMap objectForKey:attrName]; +} + +- (BOOL)_hasExtensionAttributeWithName:(NSString *)attrName { + // This method is only used on the iPhone simulator for backward compatibility reason. + + // Split the file name into components. File name may have various format, but generally + // structure is same: + // + // . and .archived. + // or + // and .archived + // + // So we want to search for the attrName in the components (ignoring the first array index). + + __auto_type components = [[self fileName] componentsSeparatedByString:kDDExtensionSeparator]; + + // Watch out for file names without an extension + + for (NSUInteger i = 1; i < components.count; i++) { + if ([attrName isEqualToString:components[i]]) { + return YES; + } + } + + return NO; +} + +- (void)_removeExtensionAttributeWithName:(NSString *)attrName { + // This method is only used on the iPhone simulator for backward compatibility reason. + + if ([attrName length] == 0) { + return; + } + + // Example: + // attrName = "archived" + // + // "mylog.archived.txt" -> "mylog.txt" + // "mylog.archived" -> "mylog" + + __auto_type components = [[self fileName] componentsSeparatedByString:kDDExtensionSeparator]; + + __auto_type count = [components count]; + + __auto_type estimatedNewLength = [[self fileName] length]; + __auto_type newFileName = [NSMutableString stringWithCapacity:estimatedNewLength]; + + if (count > 0) { + [newFileName appendString:components[0]]; + } + + __auto_type found = NO; + + NSUInteger i; + + for (i = 1; i < count; i++) { + __auto_type attr = components[i]; + + if ([attrName isEqualToString:attr]) { + found = YES; + } else { + [newFileName appendString:kDDExtensionSeparator]; + [newFileName appendString:attr]; + } + } + + if (found) { + [self renameFile:newFileName]; + } +} + +#endif /* if TARGET_IPHONE_SIMULATOR */ + +- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName { + __auto_type path = [filePath fileSystemRepresentation]; + __auto_type name = [attrName UTF8String]; + __auto_type hasExtendedAttribute = NO; + char buffer[1]; + + __auto_type result = getxattr(path, name, buffer, 1, 0, 0); + + // Fast path + if (result > 0 && buffer[0] == '\1') { + hasExtendedAttribute = YES; + } + // Maintain backward compatibility, but fix it for future checks + else if (result >= 0) { + hasExtendedAttribute = YES; + + [self addExtendedAttributeWithName:attrName]; + } +#if TARGET_IPHONE_SIMULATOR + else if ([self _hasExtensionAttributeWithName:_xattrToExtensionName(attrName)]) { + hasExtendedAttribute = YES; + + [self addExtendedAttributeWithName:attrName]; + } +#endif + + return hasExtendedAttribute; +} + +- (void)addExtendedAttributeWithName:(NSString *)attrName { + __auto_type path = [filePath fileSystemRepresentation]; + __auto_type name = [attrName UTF8String]; + + __auto_type result = setxattr(path, name, "\1", 1, 0, 0); + + if (result < 0) { + if (errno != ENOENT) { + NSLogError(@"DDLogFileInfo: setxattr(%@, %@): error = %@", + attrName, + filePath, + @(strerror(errno))); + } else { + NSLogDebug(@"DDLogFileInfo: File does not exist in setxattr(%@, %@): error = %@", + attrName, + filePath, + @(strerror(errno))); + } + } +#if TARGET_IPHONE_SIMULATOR + else { + [self _removeExtensionAttributeWithName:_xattrToExtensionName(attrName)]; + } +#endif +} + +- (void)removeExtendedAttributeWithName:(NSString *)attrName { + __auto_type path = [filePath fileSystemRepresentation]; + __auto_type name = [attrName UTF8String]; + + __auto_type result = removexattr(path, name, 0); + + if (result < 0 && errno != ENOATTR) { + NSLogError(@"DDLogFileInfo: removexattr(%@, %@): error = %@", + attrName, + self.fileName, + @(strerror(errno))); + } + +#if TARGET_IPHONE_SIMULATOR + [self _removeExtensionAttributeWithName:_xattrToExtensionName(attrName)]; +#endif +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Comparisons +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)isEqual:(id)object { + if ([object isKindOfClass:[self class]]) { + __auto_type another = (DDLogFileInfo *)object; + + return [filePath isEqualToString:[another filePath]]; + } + + return NO; +} + +- (NSUInteger)hash { + return [filePath hash]; +} + +- (NSComparisonResult)reverseCompareDatesUs:(NSDate *_Nullable)us them:(NSDate *_Nullable)them { + if (us != nil && them != nil) { + return [them compare:(NSDate * _Nonnull)us]; + } else if (us == nil && them == nil) { + return NSOrderedSame; + } + return them == nil ? NSOrderedAscending : NSOrderedDescending; +} + +- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another { + return [self reverseCompareDatesUs:[self creationDate] them:[another creationDate]]; +} + +- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another { + return [self reverseCompareDatesUs:[self modificationDate] them:[another modificationDate]]; +} + +@end + +#if TARGET_OS_IPHONE +/** + * When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen. + * + * But in case if app is able to launch from background we need to have an ability to open log file any time we + * want (even if device is locked). Thats why that attribute have to be changed to + * NSFileProtectionCompleteUntilFirstUserAuthentication. + */ +BOOL doesAppRunInBackground(void) { + if ([[[NSBundle mainBundle] executablePath] containsString:@".appex/"]) { + return YES; + } + + __auto_type answer = NO; + NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"]; + for (NSString *mode in backgroundModes) { + if (mode.length > 0) { + answer = YES; + break; + } + } + + return answer; +} +#endif diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLog.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLog.m new file mode 100644 index 000000000..6c0977203 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLog.m @@ -0,0 +1,1321 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import +#import +#import + +#if TARGET_OS_IOS + #import + #import +#elif !defined(DD_CLI) && __has_include() + #import +#endif + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import + +// We probably shouldn't be using DDLog() statements within the DDLog implementation. +// But we still want to leave our log statements for any future debugging, +// and to allow other developers to trace the implementation (which is a great learning tool). +// +// So we use a primitive logging macro around NSLog. +// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog. + +#ifndef DD_DEBUG + #define DD_DEBUG 0 +#endif + +#define NSLogDebug(frmt, ...) do{ if(DD_DEBUG) NSLog((frmt), ##__VA_ARGS__); } while(0) + +#define DDLogAssertOnGlobalLoggingQueue() \ +NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey), @"This method must be called on the logging thread/queue!") +#define DDLogAssertNotOnGlobalLoggingQueue() \ +NSAssert(!dispatch_get_specific(GlobalLoggingQueueIdentityKey), @"This method must not be called on the logging thread/queue!") + +// The "global logging queue" refers to [DDLog loggingQueue]. +// It is the queue that all log statements go through. +// +// The logging queue sets a flag via dispatch_queue_set_specific using this key. +// We can check for this key via dispatch_get_specific() to see if we're on the "global logging queue". + +static void *const GlobalLoggingQueueIdentityKey = (void *)&GlobalLoggingQueueIdentityKey; + +@interface DDLoggerNode : NSObject +{ + // Direct accessors to be used only for performance + @public + id _logger; + DDLogLevel _level; + dispatch_queue_t _loggerQueue; +} + +@property (nonatomic, readonly) id logger; +@property (nonatomic, readonly) DDLogLevel level; +@property (nonatomic, readonly) dispatch_queue_t loggerQueue; + ++ (instancetype)nodeWithLogger:(id )logger + loggerQueue:(dispatch_queue_t)loggerQueue + level:(DDLogLevel)level; + +@end + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDLog () + +// An array used to manage all the individual loggers. +// The array is only modified on the loggingQueue/loggingThread. +@property (nonatomic, strong) NSMutableArray *_loggers; + +@end + +@implementation DDLog + +// All logging statements are added to the same queue to ensure FIFO operation. +static dispatch_queue_t _loggingQueue; + +// Individual loggers are executed concurrently per log statement. +// Each logger has it's own associated queue, and a dispatch group is used for synchronization. +static dispatch_group_t _loggingGroup; + +// Minor optimization for uniprocessor machines +static NSUInteger _numProcessors; + +/** + * Returns the singleton `DDLog`. + * The instance is used by `DDLog` class methods. + * + * @return The singleton `DDLog`. + */ ++ (instancetype)sharedInstance { + static DDLog *sharedInstance = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + + return sharedInstance; +} + +/** + * The runtime sends initialize to each class in a program exactly one time just before the class, + * or any class that inherits from it, is sent its first message from within the program. (Thus the + * method may never be invoked if the class is not used.) The runtime sends the initialize message to + * classes in a thread-safe manner. Superclasses receive this message before their subclasses. + * + * This method may also be called directly, hence the safety mechanism. + **/ ++ (void)initialize { + static dispatch_once_t DDLogOnceToken; + + dispatch_once(&DDLogOnceToken, ^{ + NSLogDebug(@"DDLog: Using grand central dispatch"); + + _loggingQueue = dispatch_queue_create("cocoa.lumberjack", NULL); + _loggingGroup = dispatch_group_create(); + + void *nonNullValue = GlobalLoggingQueueIdentityKey; // Whatever, just not null + dispatch_queue_set_specific(_loggingQueue, GlobalLoggingQueueIdentityKey, nonNullValue, NULL); + + // Figure out how many processors are available. + // This may be used later for an optimization on uniprocessor machines. + + _numProcessors = MAX([NSProcessInfo processInfo].processorCount, (NSUInteger) 1); + + NSLogDebug(@"DDLog: numProcessors = %@", @(_numProcessors)); + }); +} + +/** + * The `DDLog` initializer. + * Static variables are set only once. + * + * @return An initialized `DDLog` instance. + */ +- (instancetype)init { + self = [super init]; + + if (self) { + self._loggers = [[NSMutableArray alloc] initWithCapacity:4]; + +#if TARGET_OS_IOS + __auto_type notificationName = UIApplicationWillTerminateNotification; +#else + NSString *notificationName = nil; + + // On Command Line Tool apps AppKit may not be available +#if !defined(DD_CLI) && __has_include() + if (NSApp) { + notificationName = NSApplicationWillTerminateNotification; + } +#endif + + if (!notificationName) { + // If there is no NSApp -> we are running Command Line Tool app. + // In this case terminate notification wouldn't be fired, so we use workaround. + __weak __auto_type weakSelf = self; + atexit_b (^{ + [weakSelf applicationWillTerminate:nil]; + }); + } + +#endif /* if TARGET_OS_IOS */ + + if (notificationName) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillTerminate:) + name:notificationName + object:nil]; + } + } + + return self; +} + +/** + * Provides access to the logging queue. + **/ ++ (dispatch_queue_t)loggingQueue { + return _loggingQueue; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Notifications +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)applicationWillTerminate:(NSNotification * __attribute__((unused)))notification { + [self flushLog]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Logger Management +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (void)addLogger:(id )logger { + [self.sharedInstance addLogger:logger]; +} + +- (void)addLogger:(id )logger { + [self addLogger:logger withLevel:DDLogLevelAll]; // DDLogLevelAll has all bits set +} + ++ (void)addLogger:(id )logger withLevel:(DDLogLevel)level { + [self.sharedInstance addLogger:logger withLevel:level]; +} + +- (void)addLogger:(id )logger withLevel:(DDLogLevel)level { + if (!logger) { + return; + } + + dispatch_async(_loggingQueue, ^{ @autoreleasepool { + [self lt_addLogger:logger level:level]; + } }); +} + ++ (void)removeLogger:(id )logger { + [self.sharedInstance removeLogger:logger]; +} + +- (void)removeLogger:(id )logger { + if (!logger) { + return; + } + + dispatch_async(_loggingQueue, ^{ @autoreleasepool { + [self lt_removeLogger:logger]; + } }); +} + ++ (void)removeAllLoggers { + [self.sharedInstance removeAllLoggers]; +} + +- (void)removeAllLoggers { + dispatch_async(_loggingQueue, ^{ @autoreleasepool { + [self lt_removeAllLoggers]; + } }); +} + ++ (NSArray> *)allLoggers { + return [self.sharedInstance allLoggers]; +} + +- (NSArray> *)allLoggers { + __block NSArray *theLoggers; + + dispatch_sync(_loggingQueue, ^{ @autoreleasepool { + theLoggers = [self lt_allLoggers]; + } }); + + return theLoggers; +} + ++ (NSArray *)allLoggersWithLevel { + return [self.sharedInstance allLoggersWithLevel]; +} + +- (NSArray *)allLoggersWithLevel { + __block NSArray *theLoggersWithLevel; + + dispatch_sync(_loggingQueue, ^{ @autoreleasepool { + theLoggersWithLevel = [self lt_allLoggersWithLevel]; + } }); + + return theLoggersWithLevel; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - Master Logging +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)queueLogMessage:(DDLogMessage *)logMessage asynchronously:(BOOL)asyncFlag { + // We have a tricky situation here... + // + // In the common case, when the queueSize is below the maximumQueueSize, + // we want to simply enqueue the logMessage. And we want to do this as fast as possible, + // which means we don't want to block and we don't want to use any locks. + // + // However, if the queueSize gets too big, we want to block. + // But we have very strict requirements as to when we block, and how long we block. + // + // The following example should help illustrate our requirements: + // + // Imagine that the maximum queue size is configured to be 5, + // and that there are already 5 log messages queued. + // Let us call these 5 queued log messages A, B, C, D, and E. (A is next to be executed) + // + // Now if our thread issues a log statement (let us call the log message F), + // it should block before the message is added to the queue. + // Furthermore, it should be unblocked immediately after A has been unqueued. + // + // The requirements are strict in this manner so that we block only as long as necessary, + // and so that blocked threads are unblocked in the order in which they were blocked. + // + // Returning to our previous example, let us assume that log messages A through E are still queued. + // Our aforementioned thread is blocked attempting to queue log message F. + // Now assume we have another separate thread that attempts to issue log message G. + // It should block until log messages A and B have been unqueued. + + __auto_type logBlock = ^{ + // We're now sure we won't overflow the queue. + // It is time to queue our log message. + @autoreleasepool { + [self lt_log:logMessage]; + } + }; + + if (asyncFlag) { + dispatch_async(_loggingQueue, logBlock); + } else if (dispatch_get_specific(GlobalLoggingQueueIdentityKey)) { + // We've logged an error message while on the logging queue... + logBlock(); + } else { + dispatch_sync(_loggingQueue, logBlock); + } +} + ++ (void)log:(BOOL)asynchronous + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(const char *)file + function:(const char *)function + line:(NSUInteger)line + tag:(id)tag + format:(NSString *)format, ... { + va_list args; + + if (format) { + va_start(args, format); + + [self log:asynchronous + level:level + flag:flag + context:context + file:file + function:function + line:line + tag:tag + format:format + args:args]; + + va_end(args); + } +} + +- (void)log:(BOOL)asynchronous + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(const char *)file + function:(const char *)function + line:(NSUInteger)line + tag:(id)tag + format:(NSString *)format, ... { + va_list args; + + if (format) { + va_start(args, format); + + [self log:asynchronous + level:level + flag:flag + context:context + file:file + function:function + line:line + tag:tag + format:format + args:args]; + + va_end(args); + } +} + ++ (void)log:(BOOL)asynchronous + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(const char *)file + function:(const char *)function + line:(NSUInteger)line + tag:(id)tag + format:(NSString *)format + args:(va_list)args { + [self.sharedInstance log:asynchronous level:level flag:flag context:context file:file function:function line:line tag:tag format:format args:args]; +} + +- (void)log:(BOOL)asynchronous + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(const char *)file + function:(const char *)function + line:(NSUInteger)line + tag:(id)tag + format:(NSString *)format + args:(va_list)args { + if (format) { + // Null checks are handled by -initWithMessage: +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion" + __auto_type logMessage = [[DDLogMessage alloc] initWithFormat:format + args:args + level:level + flag:flag + context:context + file:@(file) + function:@(function) + line:line + tag:tag + options:(DDLogMessageOptions)0 + timestamp:nil]; +#pragma clang diagnostic pop + + [self queueLogMessage:logMessage asynchronously:asynchronous]; + } +} + ++ (void)log:(BOOL)asynchronous message:(DDLogMessage *)logMessage { + [self.sharedInstance log:asynchronous message:logMessage]; +} + +- (void)log:(BOOL)asynchronous message:(DDLogMessage *)logMessage { + [self queueLogMessage:logMessage asynchronously:asynchronous]; +} + ++ (void)flushLog { + [self.sharedInstance flushLog]; +} + +- (void)flushLog { + DDLogAssertNotOnGlobalLoggingQueue(); + dispatch_sync(_loggingQueue, ^{ + @autoreleasepool { + [self lt_flush]; + } + }); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Registered Dynamic Logging +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (BOOL)isRegisteredClass:(Class)class { + __auto_type getterSel = @selector(ddLogLevel); + __auto_type setterSel = @selector(ddSetLogLevel:); + + // Issue #6 (GoogleCode) - Crashes on iOS 4.2.1 and iPhone 4 + // Crash caused by class_getClassMethod(2). + // "It's a bug with UIAccessibilitySafeCategory__NSObject so it didn't pop up until + // users had VoiceOver enabled [...]. I was able to work around it by searching the + // result of class_copyMethodList() instead of calling class_getClassMethod()" + // + // Issue #24 (GitHub) - Crashing in in ARC+Simulator + // The method +[DDLog isRegisteredClass] will crash a project when using it with ARC + Simulator. + // For running in the Simulator, it needs to execute the non-iOS code. Unless we're running on iOS 17+. + +#if TARGET_OS_IPHONE +#if TARGET_OS_SIMULATOR + if (@available(iOS 17, tvOS 17, *)) { +#endif + __auto_type result = NO; + unsigned int methodCount, i; + __auto_type methodList = class_copyMethodList(object_getClass(class), &methodCount); + + if (methodList != NULL) { + __auto_type getterFound = NO; + __auto_type setterFound = NO; + + for (i = 0; i < methodCount; ++i) { + __auto_type currentSel = method_getName(methodList[i]); + + if (currentSel == getterSel) { + getterFound = YES; + } else if (currentSel == setterSel) { + setterFound = YES; + } + + if (getterFound && setterFound) { + result = YES; + break; + } + } + + free(methodList); + } + + return result; +#if TARGET_OS_SIMULATOR + } else { +#endif /* TARGET_OS_SIMULATOR */ +#endif /* TARGET_OS_IPHONE */ +#if !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR + __auto_type getter = class_getClassMethod(class, getterSel); + __auto_type setter = class_getClassMethod(class, setterSel); + return (getter != NULL) && (setter != NULL); +#endif /* !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR */ +#if TARGET_OS_IPHONE && TARGET_OS_SIMULATOR + } +#endif /* TARGET_OS_IPHONE && TARGET_OS_SIMULATOR */ +} + ++ (NSArray *)registeredClasses { + // We're going to get the list of all registered classes. + // The Objective-C runtime library automatically registers all the classes defined in your source code. + // + // To do this we use the following method (documented in the Objective-C Runtime Reference): + // + // int objc_getClassList(Class *buffer, int bufferLen) + // + // We can pass (NULL, 0) to obtain the total number of + // registered class definitions without actually retrieving any class definitions. + // This allows us to allocate the minimum amount of memory needed for the application. + + NSUInteger numClasses = 0; + Class *classes = NULL; + + while (numClasses == 0) { + numClasses = (NSUInteger)MAX(objc_getClassList(NULL, 0), 0); + + // numClasses now tells us how many classes we have (but it might change) + // So we can allocate our buffer, and get pointers to all the class definitions. + __auto_type bufferSize = numClasses; + classes = numClasses ? (Class *)calloc(bufferSize, sizeof(Class)) : NULL; + if (classes == NULL) { + return @[]; // no memory or classes? + } + + numClasses = (NSUInteger)MAX(objc_getClassList(classes, (int)bufferSize),0); + if (numClasses > bufferSize || numClasses == 0) { + // apparently more classes added between calls (or a problem); try again + free(classes); + classes = NULL; + numClasses = 0; + } + } + + // We can now loop through the classes, and test each one to see if it is a DDLogging class. + __auto_type result = [NSMutableArray arrayWithCapacity:numClasses]; + for (NSUInteger i = 0; i < numClasses; i++) { + // Cannot use `__auto_type` here, since this will lead to crashes when deallocating! + Class class = classes[i]; + + if ([self isRegisteredClass:class]) { + [result addObject:class]; + } + } + + free(classes); + + return result; +} + ++ (NSArray *)registeredClassNames { + __auto_type registeredClasses = [self registeredClasses]; + __auto_type result = [NSMutableArray arrayWithCapacity:[registeredClasses count]]; + + for (Class class in registeredClasses) { + [result addObject:NSStringFromClass(class)]; + } + return result; +} + ++ (DDLogLevel)levelForClass:(Class)aClass { + if ([self isRegisteredClass:aClass]) { + return [aClass ddLogLevel]; + } + return (DDLogLevel)-1; +} + ++ (DDLogLevel)levelForClassWithName:(NSString *)aClassName { + Class clazz = NSClassFromString(aClassName); + if (clazz == nil) return (DDLogLevel)-1; + return [self levelForClass:clazz]; +} + ++ (void)setLevel:(DDLogLevel)level forClass:(Class)aClass { + if ([self isRegisteredClass:aClass]) { + [aClass ddSetLogLevel:level]; + } +} + ++ (void)setLevel:(DDLogLevel)level forClassWithName:(NSString *)aClassName { + Class clazz = NSClassFromString(aClassName); + if (clazz == nil) return; + [self setLevel:level forClass:clazz]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Logging Thread +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)lt_addLogger:(id )logger level:(DDLogLevel)level { + // Add to loggers array. + // Need to create loggerQueue if loggerNode doesn't provide one. + + for (DDLoggerNode *node in self._loggers) { + if (node->_logger == logger && node->_level == level) { + // Exactly same logger already added, exit + return; + } + } + + DDLogAssertOnGlobalLoggingQueue(); + + dispatch_queue_t loggerQueue = NULL; + if ([logger respondsToSelector:@selector(loggerQueue)]) { + // Logger may be providing its own queue + loggerQueue = logger.loggerQueue; + } + + if (loggerQueue == nil) { + // Automatically create queue for the logger. + // Use the logger name as the queue name if possible. + const char *loggerQueueName = NULL; + + if ([logger respondsToSelector:@selector(loggerName)]) { + loggerQueueName = logger.loggerName.UTF8String; + } + + loggerQueue = dispatch_queue_create(loggerQueueName, NULL); + } + + __auto_type loggerNode = [DDLoggerNode nodeWithLogger:logger loggerQueue:loggerQueue level:level]; + [self._loggers addObject:loggerNode]; + + if ([logger respondsToSelector:@selector(didAddLoggerInQueue:)]) { + dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool { + [logger didAddLoggerInQueue:loggerNode->_loggerQueue]; + } }); + } else if ([logger respondsToSelector:@selector(didAddLogger)]) { + dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool { + [logger didAddLogger]; + } }); + } +} + +- (void)lt_removeLogger:(id )logger { + // Find associated loggerNode in list of added loggers + + DDLogAssertOnGlobalLoggingQueue(); + + DDLoggerNode *loggerNode = nil; + + for (DDLoggerNode *node in self._loggers) { + if (node->_logger == logger) { + loggerNode = node; + break; + } + } + + if (loggerNode == nil) { + NSLogDebug(@"DDLog: Request to remove logger which wasn't added"); + return; + } + + // Notify logger + if ([logger respondsToSelector:@selector(willRemoveLogger)]) { + dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool { + [logger willRemoveLogger]; + } }); + } + + // Remove from loggers array + [self._loggers removeObject:loggerNode]; +} + +- (void)lt_removeAllLoggers { + DDLogAssertOnGlobalLoggingQueue(); + + // Notify all loggers + for (DDLoggerNode *loggerNode in self._loggers) { + if ([loggerNode->_logger respondsToSelector:@selector(willRemoveLogger)]) { + dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool { + [loggerNode->_logger willRemoveLogger]; + } }); + } + } + + // Remove all loggers from array + [self._loggers removeAllObjects]; +} + +- (NSArray *)lt_allLoggers { + DDLogAssertOnGlobalLoggingQueue(); + + __auto_type loggerNodes = self._loggers; + __auto_type theLoggers = [NSMutableArray arrayWithCapacity:loggerNodes.count]; + + for (DDLoggerNode *loggerNode in loggerNodes) { + [theLoggers addObject:loggerNode->_logger]; + } + + return [theLoggers copy]; +} + +- (NSArray *)lt_allLoggersWithLevel { + DDLogAssertOnGlobalLoggingQueue(); + + + __auto_type loggerNodes = self._loggers; + __auto_type theLoggersWithLevel = [NSMutableArray arrayWithCapacity:loggerNodes.count]; + + for (DDLoggerNode *loggerNode in loggerNodes) { + [theLoggersWithLevel addObject:[DDLoggerInformation informationWithLogger:loggerNode->_logger + andLevel:loggerNode->_level]]; + } + + return [theLoggersWithLevel copy]; +} + +- (void)lt_log:(DDLogMessage *)logMessage { + DDLogAssertOnGlobalLoggingQueue(); + + // Execute the given log message on each of our loggers. + + if (_numProcessors > 1) { + // Execute each logger concurrently, each within its own queue. + // All blocks are added to same group. + // After each block has been queued, wait on group. + // + // The waiting ensures that a slow logger doesn't end up with a large queue of pending log messages. + // This would defeat the purpose of the efforts we made earlier to restrict the max queue size. + + for (DDLoggerNode *loggerNode in self._loggers) { + // skip the loggers that shouldn't write this message based on the log level + + if (!(logMessage->_flag & loggerNode->_level)) { + continue; + } + + dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool { + [loggerNode->_logger logMessage:logMessage]; + } }); + } + + dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER); + } else { + // Execute each logger serially, each within its own queue. + + for (DDLoggerNode *loggerNode in self._loggers) { + // skip the loggers that shouldn't write this message based on the log level + + if (!(logMessage->_flag & loggerNode->_level)) { + continue; + } + +#if DD_DEBUG + // we must assure that we aren not on loggerNode->_loggerQueue. + if (loggerNode->_loggerQueue == NULL) { + // tell that we can't dispatch logger node on queue that is NULL. + NSLogDebug(@"DDLog: current node has loggerQueue == NULL"); + } + else { + dispatch_async(loggerNode->_loggerQueue, ^{ + if (dispatch_get_specific(GlobalLoggingQueueIdentityKey)) { + // tell that we somehow on logging queue? + NSLogDebug(@"DDLog: current node has loggerQueue == globalLoggingQueue"); + } + }); + } +#endif + // next, we must check that node is OK. + dispatch_sync(loggerNode->_loggerQueue, ^{ @autoreleasepool { + [loggerNode->_logger logMessage:logMessage]; + } }); + } + } +} + +- (void)lt_flush { + // All log statements issued before the flush method was invoked have now been executed. + // + // Now we need to propagate the flush request to any loggers that implement the flush method. + // This is designed for loggers that buffer IO. + + DDLogAssertOnGlobalLoggingQueue(); + + for (DDLoggerNode *loggerNode in self._loggers) { + if ([loggerNode->_logger respondsToSelector:@selector(flush)]) { + dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool { + [loggerNode->_logger flush]; + } }); + } + } + + dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Utilities +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +NSString * __nullable DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy) { + if (filePath == NULL) { + return nil; + } + + char *lastSlash = NULL; + char *lastDot = NULL; + + __auto_type p = (char *)filePath; + while (*p != '\0') { + if (*p == '/') { + lastSlash = p; + } else if (*p == '.') { + lastDot = p; + } + + p++; + } + + char *subStr; + NSUInteger subLen; + + if (lastSlash) { + if (lastDot) { + // lastSlash -> lastDot + subStr = lastSlash + 1; + subLen = (NSUInteger)(lastDot - subStr); + } else { + // lastSlash -> endOfString + subStr = lastSlash + 1; + subLen = (NSUInteger)(p - subStr); + } + } else { + if (lastDot) { + // startOfString -> lastDot + subStr = (char *)filePath; + subLen = (NSUInteger)(lastDot - subStr); + } else { + // startOfString -> endOfString + subStr = (char *)filePath; + subLen = (NSUInteger)(p - subStr); + } + } + + if (copy) { + return [[NSString alloc] initWithBytes:subStr + length:subLen + encoding:NSUTF8StringEncoding]; + } else { + // We can take advantage of the fact that __FILE__ is a string literal. + // Specifically, we don't need to waste time copying the string. + // We can just tell NSString to point to a range within the string literal. + + return [[NSString alloc] initWithBytesNoCopy:subStr + length:subLen + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + } +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDLoggerNode + +- (instancetype)initWithLogger:(id )logger loggerQueue:(dispatch_queue_t)loggerQueue level:(DDLogLevel)level { + if ((self = [super init])) { + _logger = logger; + + if (loggerQueue) { + _loggerQueue = loggerQueue; +#if !OS_OBJECT_USE_OBJC + dispatch_retain(loggerQueue); +#endif + } + + _level = level; + } + return self; +} + ++ (instancetype)nodeWithLogger:(id )logger loggerQueue:(dispatch_queue_t)loggerQueue level:(DDLogLevel)level { + return [[self alloc] initWithLogger:logger loggerQueue:loggerQueue level:level]; +} + +- (void)dealloc { +#if !OS_OBJECT_USE_OBJC + if (_loggerQueue) { + dispatch_release(_loggerQueue); + } +#endif +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDLogMessage + +- (instancetype)init { + self = [super init]; + return self; +} + +- (instancetype)initWithFormat:(NSString *)messageFormat + formatted:(NSString *)message + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(NSString *)file + function:(NSString *)function + line:(NSUInteger)line + tag:(id)tag + options:(DDLogMessageOptions)options + timestamp:(NSDate *)timestamp { + NSParameterAssert(messageFormat); + NSParameterAssert(message); + NSParameterAssert(file); + + if ((self = [super init])) { + __auto_type copyMessage = (options & DDLogMessageDontCopyMessage) == 0; + _messageFormat = copyMessage ? [messageFormat copy] : messageFormat; + _message = copyMessage ? [message copy] : message; + _level = level; + _flag = flag; + _context = context; + + __auto_type copyFile = (options & DDLogMessageCopyFile) != 0; + _file = copyFile ? [file copy] : file; + + __auto_type copyFunction = (options & DDLogMessageCopyFunction) != 0; + _function = copyFunction ? [function copy] : function; + + _line = line; + _representedObject = tag; +#if DD_LEGACY_MESSAGE_TAG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + _tag = tag; +#pragma clang diagnostic pop +#endif + _options = options; + _timestamp = timestamp ?: [NSDate date]; + + __uint64_t tid; + if (pthread_threadid_np(NULL, &tid) == 0) { + _threadID = [[NSString alloc] initWithFormat:@"%llu", tid]; + } else { + _threadID = @"N/A"; + } + _threadName = NSThread.currentThread.name; + + // Get the file name without extension + _fileName = [_file lastPathComponent]; + __auto_type dotLocation = [_fileName rangeOfString:@"." options:NSBackwardsSearch].location; + if (dotLocation != NSNotFound) { + _fileName = [_fileName substringToIndex:dotLocation]; + } + + // Try to get the current queue's label + _queueLabel = @(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)); + _qos = (NSUInteger) qos_class_self(); + } + return self; +} + +- (instancetype)initWithFormat:(NSString *)messageFormat + args:(va_list)messageArgs + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(NSString *)file + function:(NSString *)function + line:(NSUInteger)line + tag:(id)tag + options:(DDLogMessageOptions)options + timestamp:(NSDate *)timestamp { + __auto_type copyMessage = (options & DDLogMessageDontCopyMessage) == 0; + NSString *format = copyMessage ? [messageFormat copy] : messageFormat; + self = [self initWithFormat:format + formatted:[[NSString alloc] initWithFormat:format arguments:messageArgs] + level:level + flag:flag + context:context + file:file + function:function + line:line + tag:tag + options:options | DDLogMessageDontCopyMessage // we already did the copying if needed. + timestamp:timestamp]; + return self; +} + +- (instancetype)initWithMessage:(NSString *)message + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(NSString *)file + function:(NSString *)function + line:(NSUInteger)line + tag:(id)tag + options:(DDLogMessageOptions)options + timestamp:(NSDate *)timestamp { + self = [self initWithFormat:message + formatted:message + level:level + flag:flag + context:context + file:file + function:function + line:line + tag:tag + options:options + timestamp:timestamp]; + return self; +} + +NS_INLINE BOOL _nullable_strings_equal(NSString* _Nullable lhs, NSString* _Nullable rhs) +{ + if (lhs == nil) { + if (rhs == nil) { + return YES; + } + } else if (rhs != nil && [lhs isEqualToString:(NSString* _Nonnull)rhs]) { + return YES; + } + return NO; +} + +- (BOOL)isEqual:(id)other { + // Subclasses of NSObject should not call [super isEqual:] here. + // See https://stackoverflow.com/questions/36593038/confused-about-the-default-isequal-and-hash-implements + if (other == self) { + return YES; + } else if (!other || ![other isKindOfClass:[DDLogMessage class]]) { + return NO; + } else { + __auto_type otherMsg = (DDLogMessage *)other; + return [otherMsg->_message isEqualToString:_message] + && [otherMsg->_messageFormat isEqualToString:_messageFormat] + && otherMsg->_level == _level + && otherMsg->_flag == _flag + && otherMsg->_context == _context + && [otherMsg->_file isEqualToString:_file] + && _nullable_strings_equal(otherMsg->_function, _function) + && otherMsg->_line == _line + && (([otherMsg->_representedObject respondsToSelector:@selector(isEqual:)] && [otherMsg->_representedObject isEqual:_representedObject]) || otherMsg->_representedObject == _representedObject) + && [otherMsg->_timestamp isEqualToDate:_timestamp] + && [otherMsg->_threadID isEqualToString:_threadID] // If the thread ID is the same, the name will likely be the same as well. + && [otherMsg->_queueLabel isEqualToString:_queueLabel] + && otherMsg->_qos == _qos; + } +} + +- (NSUInteger)hash { + // Subclasses of NSObject should not call [super hash] here. + // See https://stackoverflow.com/questions/36593038/confused-about-the-default-isequal-and-hash-implements + return _message.hash + ^ _messageFormat.hash + ^ _level + ^ _flag + ^ _context + ^ _file.hash + ^ _function.hash + ^ _line + ^ ([_representedObject respondsToSelector:@selector(hash)] ? [_representedObject hash] : (NSUInteger)_representedObject) + ^ _timestamp.hash + ^ _threadID.hash + ^ _queueLabel.hash + ^ _qos; +} + +- (id)copyWithZone:(NSZone * __attribute__((unused)))zone { + DDLogMessage *newMessage = [DDLogMessage new]; + + newMessage->_messageFormat = _messageFormat; + newMessage->_message = _message; + newMessage->_level = _level; + newMessage->_flag = _flag; + newMessage->_context = _context; + newMessage->_file = _file; + newMessage->_fileName = _fileName; + newMessage->_function = _function; + newMessage->_line = _line; + newMessage->_representedObject = _representedObject; +#if DD_LEGACY_MESSAGE_TAG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + newMessage->_tag = _tag; +#pragma clang diagnostic pop +#endif + newMessage->_options = _options; + newMessage->_timestamp = _timestamp; + newMessage->_threadID = _threadID; + newMessage->_threadName = _threadName; + newMessage->_queueLabel = _queueLabel; + newMessage->_qos = _qos; + + return newMessage; +} + +// ensure compatibility even when built with DD_LEGACY_MESSAGE_TAG to 0. +- (id)tag { + return _representedObject; +} + +@end + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDAbstractLogger + +- (instancetype)init { + if ((self = [super init])) { + const char *loggerQueueName = NULL; + + if ([self respondsToSelector:@selector(loggerName)]) { + loggerQueueName = self.loggerName.UTF8String; + } + + _loggerQueue = dispatch_queue_create(loggerQueueName, NULL); + + // We're going to use dispatch_queue_set_specific() to "mark" our loggerQueue. + // Later we can use dispatch_get_specific() to determine if we're executing on our loggerQueue. + // The documentation states: + // + // > Keys are only compared as pointers and are never dereferenced. + // > Thus, you can use a pointer to a static variable for a specific subsystem or + // > any other value that allows you to identify the value uniquely. + // > Specifying a pointer to a string constant is not recommended. + // + // So we're going to use the very convenient key of "self", + // which also works when multiple logger classes extend this class, as each will have a different "self" key. + // + // This is used primarily for thread-safety assertions (via the isOnInternalLoggerQueue method below). + + __auto_type key = (__bridge void *)self; + __auto_type nonNullValue = (__bridge void *)self; + + dispatch_queue_set_specific(_loggerQueue, key, nonNullValue, NULL); + } + + return self; +} + +- (void)dealloc { +#if !OS_OBJECT_USE_OBJC + if (_loggerQueue) { + dispatch_release(_loggerQueue); + } +#endif +} + +- (void)logMessage:(DDLogMessage * __attribute__((unused)))logMessage { + // Override me +} + +- (id )logFormatter { + // This method must be thread safe and intuitive. + // Therefore if somebody executes the following code: + // + // [logger setLogFormatter:myFormatter]; + // formatter = [logger logFormatter]; + // + // They would expect formatter to equal myFormatter. + // This functionality must be ensured by the getter and setter method. + // + // The thread safety must not come at a cost to the performance of the logMessage method. + // This method is likely called sporadically, while the logMessage method is called repeatedly. + // This means, the implementation of this method: + // - Must NOT require the logMessage method to acquire a lock. + // - Must NOT require the logMessage method to access an atomic property (also a lock of sorts). + // + // Thread safety is ensured by executing access to the formatter variable on the loggerQueue. + // This is the same queue that the logMessage method operates on. + // + // Note: The last time I benchmarked the performance of direct access vs atomic property access, + // direct access was over twice as fast on the desktop and over 6 times as fast on the iPhone. + // + // Furthermore, consider the following code: + // + // DDLogVerbose(@"log msg 1"); + // DDLogVerbose(@"log msg 2"); + // [logger setFormatter:myFormatter]; + // DDLogVerbose(@"log msg 3"); + // + // Our intuitive requirement means that the new formatter will only apply to the 3rd log message. + // This must remain true even when using asynchronous logging. + // We must keep in mind the various queue's that are in play here: + // + // loggerQueue : Our own private internal queue that the logMessage method runs on. + // Operations are added to this queue from the global loggingQueue. + // + // globalLoggingQueue : The queue that all log messages go through before they arrive in our loggerQueue. + // + // All log statements go through the serial globalLoggingQueue before they arrive at our loggerQueue. + // Thus this method also goes through the serial globalLoggingQueue to ensure intuitive operation. + + // IMPORTANT NOTE: + // + // Methods within the DDLogger implementation MUST access the formatter ivar directly. + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block id result; + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self->_loggerQueue, ^{ + result = self->_logFormatter; + }); + }); + + return result; +} + +- (void)setLogFormatter:(id )logFormatter { + // The design of this method is documented extensively in the logFormatter message (above in code). + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __auto_type block = ^{ + @autoreleasepool { + if (self->_logFormatter != logFormatter) { + if ([self->_logFormatter respondsToSelector:@selector(willRemoveFromLogger:)]) { + [self->_logFormatter willRemoveFromLogger:self]; + } + + self->_logFormatter = logFormatter; + + if ([self->_logFormatter respondsToSelector:@selector(didAddToLogger:inQueue:)]) { + [self->_logFormatter didAddToLogger:self inQueue:self->_loggerQueue]; + } else if ([self->_logFormatter respondsToSelector:@selector(didAddToLogger:)]) { + [self->_logFormatter didAddToLogger:self]; + } + } + } + }; + + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self->_loggerQueue, block); + }); +} + +- (dispatch_queue_t)loggerQueue { + return _loggerQueue; +} + +- (NSString *)loggerName { + return NSStringFromClass([self class]); +} + +- (BOOL)isOnGlobalLoggingQueue { + return (dispatch_get_specific(GlobalLoggingQueueIdentityKey) != NULL); +} + +- (BOOL)isOnInternalLoggerQueue { + return dispatch_get_specific((__bridge void *)self) != NULL; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDLoggerInformation() +{ + // Direct accessors to be used only for performance + @public + id _logger; + DDLogLevel _level; +} + +@end + +@implementation DDLoggerInformation + +- (instancetype)initWithLogger:(id )logger andLevel:(DDLogLevel)level { + if ((self = [super init])) { + _logger = logger; + _level = level; + } + return self; +} + ++ (instancetype)informationWithLogger:(id )logger andLevel:(DDLogLevel)level { + return [[self alloc] initWithLogger:logger andLevel:level]; +} + +@end diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLoggerNames.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLoggerNames.m new file mode 100644 index 000000000..ffe6e3313 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLoggerNames.m @@ -0,0 +1,21 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +DDLoggerName const DDLoggerNameASL = @"cocoa.lumberjack.aslLogger"; +DDLoggerName const DDLoggerNameTTY = @"cocoa.lumberjack.ttyLogger"; +DDLoggerName const DDLoggerNameOS = @"cocoa.lumberjack.osLogger"; +DDLoggerName const DDLoggerNameFile = @"cocoa.lumberjack.fileLogger"; diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDOSLogger.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDOSLogger.m new file mode 100644 index 000000000..3ee583c6a --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDOSLogger.m @@ -0,0 +1,158 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import +#import + +#import + +@implementation DDOSLogLevelMapperDefault + +- (instancetype)init { + self = [super init]; + return self; +} + +- (os_log_type_t)osLogTypeForLogFlag:(DDLogFlag)logFlag { + switch (logFlag) { + case DDLogFlagError: + case DDLogFlagWarning: + return OS_LOG_TYPE_ERROR; + case DDLogFlagInfo: + return OS_LOG_TYPE_INFO; + case DDLogFlagDebug: + case DDLogFlagVerbose: + return OS_LOG_TYPE_DEBUG; + default: + return OS_LOG_TYPE_DEFAULT; + } +} + +@end + +#if TARGET_OS_SIMULATOR +@implementation DDOSLogLevelMapperSimulatorConsoleAppWorkaround + +- (os_log_type_t)osLogTypeForLogFlag:(DDLogFlag)logFlag { + __auto_type defaultMapping = [super osLogTypeForLogFlag:logFlag]; + return (defaultMapping == OS_LOG_TYPE_DEBUG) ? OS_LOG_TYPE_DEFAULT : defaultMapping; +} + +@end +#endif + +@interface DDOSLogger () + +@property (nonatomic, copy, readonly, nullable) NSString *subsystem; +@property (nonatomic, copy, readonly, nullable) NSString *category; +@property (nonatomic, strong, readonly, nonnull) os_log_t logger; + +@end + +@implementation DDOSLogger + +@synthesize subsystem = _subsystem; +@synthesize category = _category; +@synthesize logLevelMapper = _logLevelMapper; +@synthesize logger = _logger; + +#pragma mark - Shared Instance + +API_AVAILABLE(macos(10.12), ios(10.0), watchos(3.0), tvos(10.0)) +static DDOSLogger *sharedInstance; + ++ (instancetype)sharedInstance { + static dispatch_once_t DDOSLoggerOnceToken; + + dispatch_once(&DDOSLoggerOnceToken, ^{ + sharedInstance = [[[self class] alloc] init]; + }); + + return sharedInstance; +} + +#pragma mark - Initialization +- (instancetype)initWithSubsystem:(NSString *)subsystem category:(NSString *)category { + NSAssert((subsystem == nil) == (category == nil), + @"Either both subsystem and category or neither should be nil."); + if (self = [super init]) { + _subsystem = [subsystem copy]; + _category = [category copy]; + _logLevelMapper = [[DDOSLogLevelMapperDefault alloc] init]; + } + return self; +} + +- (instancetype)init { + return [self initWithSubsystem:nil category:nil]; +} + +- (instancetype)initWithSubsystem:(NSString *)subsystem + category:(NSString *)category + logLevelMapper:(id)logLevelMapper { + if (self = [self initWithSubsystem:subsystem category:category]) { + NSParameterAssert(logLevelMapper); + _logLevelMapper = logLevelMapper; + } + return self; +} + +- (instancetype)initWithLogLevelMapper:(id)logLevelMapper { + return [self initWithSubsystem:nil category:nil logLevelMapper:logLevelMapper]; +} + +#pragma mark - Mapper +- (id)logLevelMapper { + if (_logLevelMapper == nil) { + _logLevelMapper = [[DDOSLogLevelMapperDefault alloc] init]; + } + return _logLevelMapper; +} + +#pragma mark - os_log +- (os_log_t)logger { + if (_logger == nil) { + if (self.subsystem == nil || self.category == nil) { + _logger = OS_LOG_DEFAULT; + } else { + _logger = os_log_create(self.subsystem.UTF8String, self.category.UTF8String); + } + } + return _logger; +} + +#pragma mark - DDLogger +- (DDLoggerName)loggerName { + return DDLoggerNameOS; +} + +- (void)logMessage:(DDLogMessage *)logMessage { +#if !TARGET_OS_WATCH // See DDASLLogCapture.m -> Was never supported on watchOS. + // Skip captured log messages. + if ([logMessage->_fileName isEqualToString:@"DDASLLogCapture"]) { + return; + } +#endif + + if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) { + __auto_type message = _logFormatter ? [_logFormatter formatLogMessage:logMessage] : logMessage->_message; + if (message != nil) { + __auto_type logType = [self.logLevelMapper osLogTypeForLogFlag:logMessage->_flag]; + os_log_with_type(self.logger, logType, "%{public}s", message.UTF8String); + } + } +} + +@end diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDTTYLogger.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDTTYLogger.m new file mode 100644 index 000000000..20d08b715 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDTTYLogger.m @@ -0,0 +1,1446 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import + +#import + +// We probably shouldn't be using DDLog() statements within the DDLog implementation. +// But we still want to leave our log statements for any future debugging, +// and to allow other developers to trace the implementation (which is a great learning tool). +// +// So we use primitive logging macros around NSLog. +// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog. + +#ifndef DD_NSLOG_LEVEL + #define DD_NSLOG_LEVEL 2 +#endif + +#define NSLogError(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogWarn(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogInfo(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogDebug(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogVerbose(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0) + +// Xcode does NOT natively support colors in the Xcode debugging console. +// You'll need to install the XcodeColors plugin to see colors in the Xcode console. +// https://github.com/robbiehanson/XcodeColors +// +// The following is documentation from the XcodeColors project: +// +// +// How to apply color formatting to your log statements: +// +// To set the foreground color: +// Insert the ESCAPE_SEQ into your string, followed by "fg124,12,255;" where r=124, g=12, b=255. +// +// To set the background color: +// Insert the ESCAPE_SEQ into your string, followed by "bg12,24,36;" where r=12, g=24, b=36. +// +// To reset the foreground color (to default value): +// Insert the ESCAPE_SEQ into your string, followed by "fg;" +// +// To reset the background color (to default value): +// Insert the ESCAPE_SEQ into your string, followed by "bg;" +// +// To reset the foreground and background color (to default values) in one operation: +// Insert the ESCAPE_SEQ into your string, followed by ";" + +#define XCODE_COLORS_ESCAPE_SEQ "\033[" + +#define XCODE_COLORS_RESET_FG XCODE_COLORS_ESCAPE_SEQ "fg;" // Clear any foreground color +#define XCODE_COLORS_RESET_BG XCODE_COLORS_ESCAPE_SEQ "bg;" // Clear any background color +#define XCODE_COLORS_RESET XCODE_COLORS_ESCAPE_SEQ ";" // Clear any foreground or background color + +// If running in a shell, not all RGB colors will be supported. +// In this case we automatically map to the closest available color. +// In order to provide this mapping, we have a hard-coded set of the standard RGB values available in the shell. +// However, not every shell is the same, and Apple likes to think different even when it comes to shell colors. +// +// Map to standard Terminal.app colors (1), or +// map to standard xterm colors (0). + +#define MAP_TO_TERMINAL_APP_COLORS 1 + +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; +} DDRGBColor; + +@interface DDTTYLoggerColorProfile : NSObject { +@public + DDLogFlag mask; + NSInteger context; + + DDRGBColor fg; + DDRGBColor bg; + + NSUInteger fgCodeIndex; + NSString *fgCodeRaw; + + NSUInteger bgCodeIndex; + NSString *bgCodeRaw; + + char fgCode[24]; + size_t fgCodeLen; + + char bgCode[24]; + size_t bgCodeLen; + + char resetCode[8]; + size_t resetCodeLen; +} + +- (nullable instancetype)initWithForegroundColor:(nullable DDColor *)fgColor backgroundColor:(nullable DDColor *)bgColor flag:(DDLogFlag)mask context:(NSInteger)ctxt; + +@end + +@interface DDTTYLogger () { + NSString *_appName; + char *_app; + size_t _appLen; + + NSString *_processID; + char *_pid; + size_t _pidLen; + + BOOL _colorsEnabled; + NSMutableArray *_colorProfilesArray; + NSMutableDictionary *_colorProfilesDict; +} + +@end + +#pragma mark - + +@implementation DDTTYLogger + +static BOOL isaColorTTY; +static BOOL isaColor256TTY; +static BOOL isaXcodeColorTTY; + +static NSArray *codesFg = nil; +static NSArray *codesBg = nil; +static NSArray *colors = nil; + +static DDTTYLogger *sharedInstance; + +/** + * Initializes the colors array, as well as the `codesFg` and `codesBg` arrays, for 16 color mode. + * + * This method is used when the application is running from within a shell that only supports 16 color mode. + * This method is not invoked if the application is running within Xcode, or via normal UI app launch. + **/ ++ (void)initializeColors16 { + if (codesFg || codesBg || colors) { + return; + } + + __auto_type mColors = [NSMutableArray arrayWithCapacity:16]; + + // In a standard shell only 16 colors are supported. + // + // More information about ansi escape codes can be found online. + // http://en.wikipedia.org/wiki/ANSI_escape_code + codesFg = @[ + @"30m", // normal - black + @"31m", // normal - red + @"32m", // normal - green + @"33m", // normal - yellow + @"34m", // normal - blue + @"35m", // normal - magenta + @"36m", // normal - cyan + @"37m", // normal - gray + @"1;30m", // bright - darkgray + @"1;31m", // bright - red + @"1;32m", // bright - green + @"1;33m", // bright - yellow + @"1;34m", // bright - blue + @"1;35m", // bright - magenta + @"1;36m", // bright - cyan + @"1;37m", // bright - white + ]; + + codesBg = @[ + @"40m", // normal - black + @"41m", // normal - red + @"42m", // normal - green + @"43m", // normal - yellow + @"44m", // normal - blue + @"45m", // normal - magenta + @"46m", // normal - cyan + @"47m", // normal - gray + @"1;40m", // bright - darkgray + @"1;41m", // bright - red + @"1;42m", // bright - green + @"1;43m", // bright - yellow + @"1;44m", // bright - blue + @"1;45m", // bright - magenta + @"1;46m", // bright - cyan + @"1;47m", // bright - white + ]; + +#if MAP_TO_TERMINAL_APP_COLORS + + // Standard Terminal.app colors: + // + // These are the default colors used by Apple's Terminal.app. + const DDRGBColor rgbColors[] = { + { 0, 0, 0}, // normal - black + {194, 54, 33}, // normal - red + { 37, 188, 36}, // normal - green + {173, 173, 39}, // normal - yellow + { 73, 46, 225}, // normal - blue + {211, 56, 211}, // normal - magenta + { 51, 187, 200}, // normal - cyan + {203, 204, 205}, // normal - gray + {129, 131, 131}, // bright - darkgray + {252, 57, 31}, // bright - red + { 49, 231, 34}, // bright - green + {234, 236, 35}, // bright - yellow + { 88, 51, 255}, // bright - blue + {249, 53, 248}, // bright - magenta + { 20, 240, 240}, // bright - cyan + {233, 235, 235}, // bright - white + }; + +#else /* if MAP_TO_TERMINAL_APP_COLORS */ + + // Standard xterm colors: + // + // These are the default colors used by most xterm shells. + const DDRGBColor rgbColors[] = { + { 0, 0, 0}, // normal - black + {205, 0, 0}, // normal - red + { 0, 205, 0}, // normal - green + {205, 205, 0}, // normal - yellow + { 0, 0, 238}, // normal - blue + {205, 0, 205}, // normal - magenta + { 0, 205, 205}, // normal - cyan + {229, 229, 229}, // normal - gray + {127, 127, 127}, // bright - darkgray + {255, 0, 0}, // bright - red + { 0, 255, 0}, // bright - green + {255, 255, 0}, // bright - yellow + { 92, 92, 255}, // bright - blue + {255, 0, 255}, // bright - magenta + { 0, 255, 255}, // bright - cyan + {255, 255, 255}, // bright - white + }; +#endif /* if MAP_TO_TERMINAL_APP_COLORS */ + + for (size_t i = 0; i < sizeof(rgbColors) / sizeof(rgbColors[0]); ++i) { + [mColors addObject:DDMakeColor(rgbColors[i].r, rgbColors[i].g, rgbColors[i].b)]; + } + colors = [mColors copy]; + + NSAssert([codesFg count] == [codesBg count], @"Invalid colors/codes array(s)"); + NSAssert([codesFg count] == [colors count], @"Invalid colors/codes array(s)"); +} + +/** + * Initializes the colors array, as well as the `codesFg` and `codesBg` arrays, for 256 color mode. + * + * This method is used when the application is running from within a shell that supports 256 color mode. + * This method is not invoked if the application is running within Xcode, or via normal UI app launch. + **/ ++ (void)initializeColors256 { + if (codesFg || codesBg || colors) { + return; + } + + __auto_type mCodesFg = [NSMutableArray arrayWithCapacity:(256 - 16)]; + __auto_type mCodesBg = [NSMutableArray arrayWithCapacity:(256 - 16)]; + __auto_type mColors = [NSMutableArray arrayWithCapacity:(256 - 16)]; + +#if MAP_TO_TERMINAL_APP_COLORS + + // Standard Terminal.app colors: + // + // These are the colors the Terminal.app uses in xterm-256color mode. + // In this mode, the terminal supports 256 different colors, specified by 256 color codes. + // + // The first 16 color codes map to the original 16 color codes supported by the earlier xterm-color mode. + // These are actually configurable, and thus we ignore them for the purposes of mapping, + // as we can't rely on them being constant. They are largely duplicated anyway. + // + // The next 216 color codes are designed to run the spectrum, with several shades of every color. + // While the color codes are standardized, the actual RGB values for each color code is not. + // Apple's Terminal.app uses different RGB values from that of a standard xterm. + // Apple's choices in colors are designed to be a little nicer on the eyes. + // + // The last 24 color codes represent a grayscale. + // + // Unfortunately, unlike the standard xterm color chart, + // Apple's RGB values cannot be calculated using a simple formula (at least not that I know of). + // Also, I don't know of any ways to programmatically query the shell for the RGB values. + // So this big giant color chart had to be made by hand. + // + // More information about ansi escape codes can be found online. + // http://en.wikipedia.org/wiki/ANSI_escape_code + + // Colors + const DDRGBColor rgbColors[] = { + { 47, 49, 49}, + { 60, 42, 144}, + { 66, 44, 183}, + { 73, 46, 222}, + { 81, 50, 253}, + { 88, 51, 255}, + + { 42, 128, 37}, + { 42, 127, 128}, + { 44, 126, 169}, + { 56, 125, 209}, + { 59, 124, 245}, + { 66, 123, 255}, + + { 51, 163, 41}, + { 39, 162, 121}, + { 42, 161, 162}, + { 53, 160, 202}, + { 45, 159, 240}, + { 58, 158, 255}, + + { 31, 196, 37}, + { 48, 196, 115}, + { 39, 195, 155}, + { 49, 195, 195}, + { 32, 194, 235}, + { 53, 193, 255}, + + { 50, 229, 35}, + { 40, 229, 109}, + { 27, 229, 149}, + { 49, 228, 189}, + { 33, 228, 228}, + { 53, 227, 255}, + + { 27, 254, 30}, + { 30, 254, 103}, + { 45, 254, 143}, + { 38, 253, 182}, + { 38, 253, 222}, + { 42, 253, 252}, + + {140, 48, 40}, + {136, 51, 136}, + {135, 52, 177}, + {134, 52, 217}, + {135, 56, 248}, + {134, 53, 255}, + + {125, 125, 38}, + {124, 125, 125}, + {122, 124, 166}, + {123, 124, 207}, + {123, 122, 247}, + {124, 121, 255}, + + {119, 160, 35}, + {117, 160, 120}, + {117, 160, 160}, + {115, 159, 201}, + {116, 158, 240}, + {117, 157, 255}, + + {113, 195, 39}, + {110, 194, 114}, + {111, 194, 154}, + {108, 194, 194}, + {109, 193, 234}, + {108, 192, 255}, + + {105, 228, 30}, + {103, 228, 109}, + {105, 228, 148}, + {100, 227, 188}, + { 99, 227, 227}, + { 99, 226, 253}, + + { 92, 253, 34}, + { 96, 253, 103}, + { 97, 253, 142}, + { 88, 253, 182}, + { 93, 253, 221}, + { 88, 254, 251}, + + {177, 53, 34}, + {174, 54, 131}, + {172, 55, 172}, + {171, 57, 213}, + {170, 55, 249}, + {170, 57, 255}, + + {165, 123, 37}, + {163, 123, 123}, + {162, 123, 164}, + {161, 122, 205}, + {161, 121, 241}, + {161, 121, 255}, + + {158, 159, 33}, + {157, 158, 118}, + {157, 158, 159}, + {155, 157, 199}, + {155, 157, 239}, + {154, 156, 255}, + + {152, 193, 40}, + {151, 193, 113}, + {150, 193, 153}, + {150, 192, 193}, + {148, 192, 232}, + {149, 191, 253}, + + {146, 227, 28}, + {144, 227, 108}, + {144, 227, 147}, + {144, 227, 187}, + {142, 226, 227}, + {142, 225, 252}, + + {138, 253, 36}, + {137, 253, 102}, + {136, 253, 141}, + {138, 254, 181}, + {135, 255, 220}, + {133, 255, 250}, + + {214, 57, 30}, + {211, 59, 126}, + {209, 57, 168}, + {208, 55, 208}, + {207, 58, 247}, + {206, 61, 255}, + + {204, 121, 32}, + {202, 121, 121}, + {201, 121, 161}, + {200, 120, 202}, + {200, 120, 241}, + {198, 119, 255}, + + {198, 157, 37}, + {196, 157, 116}, + {195, 156, 157}, + {195, 156, 197}, + {194, 155, 236}, + {193, 155, 255}, + + {191, 192, 36}, + {190, 191, 112}, + {189, 191, 152}, + {189, 191, 191}, + {188, 190, 230}, + {187, 190, 253}, + + {185, 226, 28}, + {184, 226, 106}, + {183, 225, 146}, + {183, 225, 186}, + {182, 225, 225}, + {181, 224, 252}, + + {178, 255, 35}, + {178, 255, 101}, + {177, 254, 141}, + {176, 254, 180}, + {176, 254, 220}, + {175, 253, 249}, + + {247, 56, 30}, + {245, 57, 122}, + {243, 59, 163}, + {244, 60, 204}, + {242, 59, 241}, + {240, 55, 255}, + + {241, 119, 36}, + {240, 120, 118}, + {238, 119, 158}, + {237, 119, 199}, + {237, 118, 238}, + {236, 118, 255}, + + {235, 154, 36}, + {235, 154, 114}, + {234, 154, 154}, + {232, 154, 194}, + {232, 153, 234}, + {232, 153, 255}, + + {230, 190, 30}, + {229, 189, 110}, + {228, 189, 150}, + {227, 189, 190}, + {227, 189, 229}, + {226, 188, 255}, + + {224, 224, 35}, + {223, 224, 105}, + {222, 224, 144}, + {222, 223, 184}, + {222, 223, 224}, + {220, 223, 253}, + + {217, 253, 28}, + {217, 253, 99}, + {216, 252, 139}, + {216, 252, 179}, + {215, 252, 218}, + {215, 251, 250}, + + {255, 61, 30}, + {255, 60, 118}, + {255, 58, 159}, + {255, 56, 199}, + {255, 55, 238}, + {255, 59, 255}, + + {255, 117, 29}, + {255, 117, 115}, + {255, 117, 155}, + {255, 117, 195}, + {255, 116, 235}, + {254, 116, 255}, + + {255, 152, 27}, + {255, 152, 111}, + {254, 152, 152}, + {255, 152, 192}, + {254, 151, 231}, + {253, 151, 253}, + + {255, 187, 33}, + {253, 187, 107}, + {252, 187, 148}, + {253, 187, 187}, + {254, 187, 227}, + {252, 186, 252}, + + {252, 222, 34}, + {251, 222, 103}, + {251, 222, 143}, + {250, 222, 182}, + {251, 221, 222}, + {252, 221, 252}, + + {251, 252, 15}, + {251, 252, 97}, + {249, 252, 137}, + {247, 252, 177}, + {247, 253, 217}, + {254, 255, 255}, + + // Grayscale + + { 52, 53, 53}, + { 57, 58, 59}, + { 66, 67, 67}, + { 75, 76, 76}, + { 83, 85, 85}, + { 92, 93, 94}, + + {101, 102, 102}, + {109, 111, 111}, + {118, 119, 119}, + {126, 127, 128}, + {134, 136, 136}, + {143, 144, 145}, + + {151, 152, 153}, + {159, 161, 161}, + {167, 169, 169}, + {176, 177, 177}, + {184, 185, 186}, + {192, 193, 194}, + + {200, 201, 202}, + {208, 209, 210}, + {216, 218, 218}, + {224, 226, 226}, + {232, 234, 234}, + {240, 242, 242}, + }; + + for (size_t i = 0; i < sizeof(rgbColors) / sizeof(rgbColors[0]); ++i) { + [mColors addObject:DDMakeColor(rgbColors[i].r, rgbColors[i].g, rgbColors[i].b)]; + } + + // Color codes + int index = 16; + while (index < 256) { + [mCodesFg addObject:[NSString stringWithFormat:@"38;5;%dm", index]]; + [mCodesBg addObject:[NSString stringWithFormat:@"48;5;%dm", index]]; + + index++; + } + +#else /* if MAP_TO_TERMINAL_APP_COLORS */ + + // Standard xterm colors: + // + // These are the colors xterm shells use in xterm-256color mode. + // In this mode, the shell supports 256 different colors, specified by 256 color codes. + // + // The first 16 color codes map to the original 16 color codes supported by the earlier xterm-color mode. + // These are generally configurable, and thus we ignore them for the purposes of mapping, + // as we can't rely on them being constant. They are largely duplicated anyway. + // + // The next 216 color codes are designed to run the spectrum, with several shades of every color. + // The last 24 color codes represent a grayscale. + // + // While the color codes are standardized, the actual RGB values for each color code is not. + // However most standard xterms follow a well known color chart, + // which can easily be calculated using the simple formula below. + // + // More information about ansi escape codes can be found online. + // http://en.wikipedia.org/wiki/ANSI_escape_code + + int index = 16; + + int r; // red + int g; // green + int b; // blue + + int ri; // r increment + int gi; // g increment + int bi; // b increment + + // Calculate xterm colors (using standard algorithm) + + int r = 0; + int g = 0; + int b = 0; + + for (ri = 0; ri < 6; ri++) { + r = (ri == 0) ? 0 : 95 + (40 * (ri - 1)); + + for (gi = 0; gi < 6; gi++) { + g = (gi == 0) ? 0 : 95 + (40 * (gi - 1)); + + for (bi = 0; bi < 6; bi++) { + b = (bi == 0) ? 0 : 95 + (40 * (bi - 1)); + + [mCodesFg addObject:[NSString stringWithFormat:@"38;5;%dm", index]]; + [mCodesBg addObject:[NSString stringWithFormat:@"48;5;%dm", index]]; + [mColors addObject:DDMakeColor(r, g, b)]; + + index++; + } + } + } + + // Calculate xterm grayscale (using standard algorithm) + + r = 8; + g = 8; + b = 8; + + while (index < 256) { + [mCodesFg addObject:[NSString stringWithFormat:@"38;5;%dm", index]]; + [mCodesBg addObject:[NSString stringWithFormat:@"48;5;%dm", index]]; + [mColor s addObject:DDMakeColor(r, g, b)]; + + r += 10; + g += 10; + b += 10; + + index++; + } + +#endif /* if MAP_TO_TERMINAL_APP_COLORS */ + + codesFg = [mCodesFg copy]; + codesBg = [mCodesBg copy]; + colors = [mColors copy]; + + NSAssert([codesFg count] == [codesBg count], @"Invalid colors/codes array(s)"); + NSAssert([codesFg count] == [colors count], @"Invalid colors/codes array(s)"); +} + ++ (void)getRed:(CGFloat *)rPtr green:(CGFloat *)gPtr blue:(CGFloat *)bPtr fromColor:(DDColor *)color { +#if TARGET_OS_IPHONE + + // iOS + __auto_type done = NO; + + if ([color respondsToSelector:@selector(getRed:green:blue:alpha:)]) { + done = [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL]; + } + + if (!done) { + // The method getRed:green:blue:alpha: was only available starting iOS 5. + // So in iOS 4 and earlier, we have to jump through hoops. + + __auto_type rgbColorSpace = CGColorSpaceCreateDeviceRGB(); + + unsigned char pixel[4]; + __auto_type context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, rgbColorSpace, (CGBitmapInfo)(kCGBitmapAlphaInfoMask & kCGImageAlphaNoneSkipLast)); + + CGContextSetFillColorWithColor(context, [color CGColor]); + CGContextFillRect(context, CGRectMake(0, 0, 1, 1)); + + if (rPtr) { + *rPtr = pixel[0] / 255.0; + } + if (gPtr) { + *gPtr = pixel[1] / 255.0; + } + if (bPtr) { + *bPtr = pixel[2] / 255.0; + } + + CGContextRelease(context); + CGColorSpaceRelease(rgbColorSpace); + } + +#elif defined(DD_CLI) || !__has_include() + + // OS X without AppKit + [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL]; + +#else /* if TARGET_OS_IPHONE */ + + // OS X with AppKit + NSColor *safeColor; + if (@available(macOS 10.14,*)) { + safeColor = [color colorUsingColorSpace:NSColorSpace.deviceRGBColorSpace]; + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + safeColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; +#pragma clang diagnostic pop + } + + [safeColor getRed:rPtr green:gPtr blue:bPtr alpha:NULL]; + +#endif /* if TARGET_OS_IPHONE */ +} + +/** + * Maps the given color to the closest available color supported by the shell. + * The shell may support 256 colors, or only 16. + * + * This method loops through the known supported color set, and calculates the closest color. + * The array index of that color, within the colors array, is then returned. + * This array index may also be used as the index within the `codesFg` and `codesBg` arrays. + **/ ++ (NSUInteger)codeIndexForColor:(DDColor *)inColor { + CGFloat inR, inG, inB; + + [self getRed:&inR green:&inG blue:&inB fromColor:inColor]; + + NSUInteger bestIndex = 0; + CGFloat lowestDistance = 100.0; + + NSUInteger i = 0; + + for (DDColor *color in colors) { + // Calculate Euclidean distance (lower value means closer to given color) + + CGFloat r, g, b; + [self getRed:&r green:&g blue:&b fromColor:color]; + +#if CGFLOAT_IS_DOUBLE + __auto_type distance = sqrt(pow(r - inR, 2.0) + pow(g - inG, 2.0) + pow(b - inB, 2.0)); +#else + __auto_type distance = sqrtf(powf(r - inR, 2.0f) + powf(g - inG, 2.0f) + powf(b - inB, 2.0f)); +#endif + + NSLogVerbose(@"DDTTYLogger: %3lu : %.3f,%.3f,%.3f & %.3f,%.3f,%.3f = %.6f", + (unsigned long)i, (double)inR, (double)inG, (double)inB, (double)r, (double)g, (double)b, (double)distance); + + if (distance < lowestDistance) { + bestIndex = i; + lowestDistance = distance; + + NSLogVerbose(@"DDTTYLogger: New best index = %lu", (unsigned long)bestIndex); + } + + i++; + } + + return bestIndex; +} + ++ (instancetype)sharedInstance { + static dispatch_once_t DDTTYLoggerOnceToken; + + dispatch_once(&DDTTYLoggerOnceToken, ^{ + // Xcode does NOT natively support colors in the Xcode debugging console. + // You'll need to install the XcodeColors plugin to see colors in the Xcode console. + // + // PS - Please read the header file before diving into the source code. + + __auto_type xcodeColors = getenv("XcodeColors"); + __auto_type term = getenv("TERM"); + + if (xcodeColors && (strcmp(xcodeColors, "YES") == 0)) { + isaXcodeColorTTY = YES; + } else if (term) { + if (strcasestr(term, "color") != NULL) { + isaColorTTY = YES; + isaColor256TTY = (strcasestr(term, "256") != NULL); + + if (isaColor256TTY) { + [self initializeColors256]; + } else { + [self initializeColors16]; + } + } + } + + NSLogInfo(@"DDTTYLogger: isaColorTTY = %@", (isaColorTTY ? @"YES" : @"NO")); + NSLogInfo(@"DDTTYLogger: isaColor256TTY: %@", (isaColor256TTY ? @"YES" : @"NO")); + NSLogInfo(@"DDTTYLogger: isaXcodeColorTTY: %@", (isaXcodeColorTTY ? @"YES" : @"NO")); + + sharedInstance = [[self alloc] init]; + }); + + return sharedInstance; +} + +- (instancetype)init { + if (sharedInstance != nil) { + return nil; + } + +#if !defined(DD_CLI) || __has_include() + if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) { + NSLogWarn(@"CocoaLumberjack: Warning: Usage of DDTTYLogger detected when DDOSLogger is available and can be used! Please consider migrating to DDOSLogger."); + } +#endif + + if ((self = [super init])) { + // Initialize 'app' variable (char *) + _appName = [[NSProcessInfo processInfo] processName]; + _appLen = [_appName lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + if (_appLen == 0) { + _appName = @""; + _appLen = [_appName lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + } + + _app = (char *)calloc(_appLen + 1, sizeof(char)); + if (_app == NULL) { + return nil; + } + + BOOL processedAppName = [_appName getCString:_app maxLength:(_appLen + 1) encoding:NSUTF8StringEncoding]; + if (!processedAppName) { + free(_app); + return nil; + } + + // Initialize 'pid' variable (char *) + + _processID = [NSString stringWithFormat:@"%i", (int)getpid()]; + + _pidLen = [_processID lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + _pid = (char *)calloc(_pidLen + 1, sizeof(char)); + + if (_pid == NULL) { + free(_app); + return nil; + } + + BOOL processedID = [_processID getCString:_pid maxLength:(_pidLen + 1) encoding:NSUTF8StringEncoding]; + if (!processedID) { + free(_app); + free(_pid); + return nil; + } + + // Initialize color stuff + + _colorsEnabled = NO; + _colorProfilesArray = [[NSMutableArray alloc] initWithCapacity:8]; + _colorProfilesDict = [[NSMutableDictionary alloc] initWithCapacity:8]; + + _automaticallyAppendNewlineForCustomFormatters = YES; + } + + return self; +} + +- (DDLoggerName)loggerName { + return DDLoggerNameTTY; +} + +- (void)loadDefaultColorProfiles { + [self setForegroundColor:DDMakeColor(214, 57, 30) backgroundColor:nil forFlag:DDLogFlagError]; + [self setForegroundColor:DDMakeColor(204, 121, 32) backgroundColor:nil forFlag:DDLogFlagWarning]; +} + +- (BOOL)colorsEnabled { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + __block BOOL result; + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = self->_colorsEnabled; + }); + }); + + return result; +} + +- (void)setColorsEnabled:(BOOL)newColorsEnabled { + __auto_type block = ^{ + @autoreleasepool { + self->_colorsEnabled = newColorsEnabled; + + if ([self->_colorProfilesArray count] == 0) { + [self loadDefaultColorProfiles]; + } + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); +} + +- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask { + [self setForegroundColor:txtColor backgroundColor:bgColor forFlag:mask context:LOG_CONTEXT_ALL]; +} + +- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask context:(NSInteger)ctxt { + dispatch_block_t block = ^{ + @autoreleasepool { + DDTTYLoggerColorProfile *newColorProfile = [[DDTTYLoggerColorProfile alloc] initWithForegroundColor:txtColor + backgroundColor:bgColor + flag:mask + context:ctxt]; + if (!newColorProfile) return; + + NSLogInfo(@"DDTTYLogger: newColorProfile: %@", newColorProfile); + + NSUInteger i = 0; + + for (DDTTYLoggerColorProfile *colorProfile in self->_colorProfilesArray) { + if ((colorProfile->mask == mask) && (colorProfile->context == ctxt)) { + break; + } + + i++; + } + + if (i < [self->_colorProfilesArray count]) { + self->_colorProfilesArray[i] = newColorProfile; + } else { + [self->_colorProfilesArray addObject:newColorProfile]; + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forTag:(id )tag { + NSAssert([(id )tag conformsToProtocol: @protocol(NSCopying)], @"Invalid tag"); + + __auto_type block = ^{ + @autoreleasepool { + __auto_type newColorProfile = [[DDTTYLoggerColorProfile alloc] initWithForegroundColor:txtColor + backgroundColor:bgColor + flag:(DDLogFlag)0 + context:0]; + + NSLogInfo(@"DDTTYLogger: newColorProfile: %@", newColorProfile); + + self->_colorProfilesDict[tag] = newColorProfile; + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)clearColorsForFlag:(DDLogFlag)mask { + [self clearColorsForFlag:mask context:0]; +} + +- (void)clearColorsForFlag:(DDLogFlag)mask context:(NSInteger)context { + __auto_type block = ^{ + @autoreleasepool { + NSUInteger i = 0; + + for (DDTTYLoggerColorProfile *colorProfile in self->_colorProfilesArray) { + if ((colorProfile->mask == mask) && (colorProfile->context == context)) { + break; + } + + i++; + } + + if (i < [self->_colorProfilesArray count]) { + [self->_colorProfilesArray removeObjectAtIndex:i]; + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)clearColorsForTag:(id )tag { + NSAssert([(id ) tag conformsToProtocol: @protocol(NSCopying)], @"Invalid tag"); + + __auto_type block = ^{ + @autoreleasepool { + [self->_colorProfilesDict removeObjectForKey:tag]; + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)clearColorsForAllFlags { + __auto_type block = ^{ + @autoreleasepool { + [self->_colorProfilesArray removeAllObjects]; + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)clearColorsForAllTags { + __auto_type block = ^{ + @autoreleasepool { + [self->_colorProfilesDict removeAllObjects]; + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)clearAllColors { + __auto_type block = ^{ + @autoreleasepool { + [self->_colorProfilesArray removeAllObjects]; + [self->_colorProfilesDict removeAllObjects]; + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)logMessage:(DDLogMessage *)logMessage { + __auto_type logMsg = logMessage->_message; + __auto_type isFormatted = NO; + + if (_logFormatter) { + logMsg = [_logFormatter formatLogMessage:logMessage]; + isFormatted = logMsg != logMessage->_message; + } + + if (logMsg) { + // Search for a color profile associated with the log message + + DDTTYLoggerColorProfile *colorProfile = nil; + + if (_colorsEnabled) { + if (logMessage->_representedObject) { + colorProfile = _colorProfilesDict[logMessage->_representedObject]; + } + + if (colorProfile == nil) { + for (DDTTYLoggerColorProfile *cp in _colorProfilesArray) { + if (logMessage->_flag & cp->mask) { + // Color profile set for this context? + if (logMessage->_context == cp->context) { + colorProfile = cp; + + // Stop searching + break; + } + + // Check if LOG_CONTEXT_ALL was specified as a default color for this flag + if (cp->context == LOG_CONTEXT_ALL) { + colorProfile = cp; + + // We don't break to keep searching for more specific color profiles for the context + } + } + } + } + } + + // Convert log message to C string. + // + // We use the stack instead of the heap for speed if possible. + // But we're extra cautious to avoid a stack overflow. + + __auto_type msgLen = [logMsg lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + const __auto_type useStack = msgLen < (1024 * 4); + + char *msg; + if (useStack) { + msg = (char *)alloca(msgLen + 1); + } else { + msg = (char *)calloc(msgLen + 1, sizeof(char)); + } + if (msg == NULL) { + return; + } + + BOOL logMsgEnc = [logMsg getCString:msg maxLength:(msgLen + 1) encoding:NSUTF8StringEncoding]; + if (!logMsgEnc) { + if (!useStack) { + free(msg); + } + return; + } + + // Write the log message to STDERR + + if (isFormatted) { + // The log message has already been formatted. + const size_t maxIovecLen = 5; + size_t iovecLen = _automaticallyAppendNewlineForCustomFormatters ? 5 : 4; + struct iovec v[maxIovecLen] = { 0 }; + + if (colorProfile) { + v[0].iov_base = colorProfile->fgCode; + v[0].iov_len = colorProfile->fgCodeLen; + + v[1].iov_base = colorProfile->bgCode; + v[1].iov_len = colorProfile->bgCodeLen; + + v[maxIovecLen - 1].iov_base = colorProfile->resetCode; + v[maxIovecLen - 1].iov_len = colorProfile->resetCodeLen; + } + + v[2].iov_base = msg; + v[2].iov_len = (msgLen > SIZE_MAX - 1) ? SIZE_MAX - 1 : msgLen; + + if (_automaticallyAppendNewlineForCustomFormatters && (v[2].iov_len == 0 || msg[v[2].iov_len - 1] != '\n')) { + v[3].iov_base = "\n"; + v[3].iov_len = 1; + iovecLen = 5; + } + + writev(STDERR_FILENO, v, (int)iovecLen); + } else { + // The log message is unformatted, so apply standard NSLog style formatting. + + int len; + char ts[24] = ""; + size_t tsLen = 0; + + // Calculate timestamp. + // The technique below is faster than using NSDateFormatter. + if (logMessage->_timestamp) { + __auto_type epoch = [logMessage->_timestamp timeIntervalSince1970]; + double integral; + __auto_type fract = modf(epoch, &integral); + struct tm tm; + __auto_type time = (time_t)integral; + (void)localtime_r(&time, &tm); + __auto_type milliseconds = (long)(fract * 1000.0); + + len = snprintf(ts, 24, "%04d-%02d-%02d %02d:%02d:%02d:%03ld", // yyyy-MM-dd HH:mm:ss:SSS + tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, milliseconds); + + tsLen = (NSUInteger)MAX(MIN(24 - 1, len), 0); + } + + // Calculate thread ID + // + // How many characters do we need for the thread id? + // logMessage->machThreadID is of type mach_port_t, which is an unsigned int. + // + // 1 hex char = 4 bits + // 8 hex chars for 32 bit, plus ending '\0' = 9 + + char tid[9]; + len = snprintf(tid, 9, "%s", [logMessage->_threadID cStringUsingEncoding:NSUTF8StringEncoding]); + + __auto_type tidLen = (NSUInteger)MAX(MIN(9 - 1, len), 0); + + // Here is our format: "%s %s[%i:%s] %s", timestamp, appName, processID, threadID, logMsg + + struct iovec v[13]; + + if (colorProfile) { + v[0].iov_base = colorProfile->fgCode; + v[0].iov_len = colorProfile->fgCodeLen; + + v[1].iov_base = colorProfile->bgCode; + v[1].iov_len = colorProfile->bgCodeLen; + + v[12].iov_base = colorProfile->resetCode; + v[12].iov_len = colorProfile->resetCodeLen; + } else { + v[0].iov_base = ""; + v[0].iov_len = 0; + + v[1].iov_base = ""; + v[1].iov_len = 0; + + v[12].iov_base = ""; + v[12].iov_len = 0; + } + + v[2].iov_base = ts; + v[2].iov_len = tsLen; + + v[3].iov_base = " "; + v[3].iov_len = 1; + + v[4].iov_base = _app; + v[4].iov_len = _appLen; + + v[5].iov_base = "["; + v[5].iov_len = 1; + + v[6].iov_base = _pid; + v[6].iov_len = _pidLen; + + v[7].iov_base = ":"; + v[7].iov_len = 1; + + v[8].iov_base = tid; + v[8].iov_len = MIN((size_t)8, tidLen); // snprintf doesn't return what you might think + + v[9].iov_base = "] "; + v[9].iov_len = 2; + + v[10].iov_base = (char *)msg; + v[10].iov_len = msgLen; + + v[11].iov_base = "\n"; + v[11].iov_len = (msg[msgLen] == '\n') ? 0 : 1; + + writev(STDERR_FILENO, v, 13); + } + + if (!useStack) { + free(msg); + } + } +} + +@end + +#pragma mark - + +@implementation DDTTYLoggerColorProfile + +- (instancetype)initWithForegroundColor:(DDColor *)fgColor backgroundColor:(DDColor *)bgColor flag:(DDLogFlag)aMask context:(NSInteger)ctxt { + if ((self = [super init])) { + mask = aMask; + context = ctxt; + + CGFloat r, g, b; + + if (fgColor) { + [DDTTYLogger getRed:&r green:&g blue:&b fromColor:fgColor]; + + fg.r = (uint8_t)(r * (CGFloat)255.0); + fg.g = (uint8_t)(g * (CGFloat)255.0); + fg.b = (uint8_t)(b * (CGFloat)255.0); + } + + if (bgColor) { + [DDTTYLogger getRed:&r green:&g blue:&b fromColor:bgColor]; + + bg.r = (uint8_t)(r * (CGFloat)255.0); + bg.g = (uint8_t)(g * (CGFloat)255.0); + bg.b = (uint8_t)(b * (CGFloat)255.0); + } + + if (fgColor && isaColorTTY) { + // Map foreground color to closest available shell color + + fgCodeIndex = [DDTTYLogger codeIndexForColor:fgColor]; + fgCodeRaw = codesFg[fgCodeIndex]; + + const __auto_type escapeSeq = @"\033["; + + __auto_type len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + __auto_type len2 = [fgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + BOOL escapeSeqEnc = [escapeSeq getCString:(fgCode) maxLength:(len1 + 1) encoding:NSUTF8StringEncoding]; + BOOL fgCodeRawEsc = [fgCodeRaw getCString:(fgCode + len1) maxLength:(len2 + 1) encoding:NSUTF8StringEncoding]; + + if (!escapeSeqEnc || !fgCodeRawEsc) { + return nil; + } + + fgCodeLen = len1 + len2; + } else if (fgColor && isaXcodeColorTTY) { + // Convert foreground color to color code sequence + const char *escapeSeq = XCODE_COLORS_ESCAPE_SEQ; + __auto_type result = snprintf(fgCode, 24, "%sfg%u,%u,%u;", escapeSeq, fg.r, fg.g, fg.b); + fgCodeLen = (NSUInteger)MAX(MIN(result, (24 - 1)), 0); + } else { + // No foreground color or no color support + fgCode[0] = '\0'; + fgCodeLen = 0; + } + + if (bgColor && isaColorTTY) { + // Map background color to closest available shell color + + bgCodeIndex = [DDTTYLogger codeIndexForColor:bgColor]; + bgCodeRaw = codesBg[bgCodeIndex]; + + const __auto_type escapeSeq = @"\033["; + + __auto_type len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + __auto_type len2 = [bgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + BOOL escapeSeqEnc = [escapeSeq getCString:(bgCode) maxLength:(len1 + 1) encoding:NSUTF8StringEncoding]; + BOOL bgCodeRawEsc = [bgCodeRaw getCString:(bgCode + len1) maxLength:(len2 + 1) encoding:NSUTF8StringEncoding]; + + if (!escapeSeqEnc || !bgCodeRawEsc) { + return nil; + } + + bgCodeLen = len1 + len2; + } else if (bgColor && isaXcodeColorTTY) { + // Convert background color to color code sequence + const char *escapeSeq = XCODE_COLORS_ESCAPE_SEQ; + __auto_type result = snprintf(bgCode, 24, "%sbg%u,%u,%u;", escapeSeq, bg.r, bg.g, bg.b); + bgCodeLen = (NSUInteger)MAX(MIN(result, (24 - 1)), 0); + } else { + // No background color or no color support + bgCode[0] = '\0'; + bgCodeLen = 0; + } + + if (isaColorTTY) { + resetCodeLen = (NSUInteger)MAX(snprintf(resetCode, 8, "\033[0m"), 0); + } else if (isaXcodeColorTTY) { + resetCodeLen = (NSUInteger)MAX(snprintf(resetCode, 8, XCODE_COLORS_RESET), 0); + } else { + resetCode[0] = '\0'; + resetCodeLen = 0; + } + } + + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat: + @"", + self, (int)mask, (long)context, fg.r, fg.g, fg.b, bg.r, bg.g, bg.b, fgCodeRaw, bgCodeRaw]; +} + +@end diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter+Deprecated.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter+Deprecated.m new file mode 100644 index 000000000..c05db19f3 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter+Deprecated.m @@ -0,0 +1,57 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +@implementation DDContextAllowlistFilterLogFormatter (Deprecated) + +- (void)addToWhitelist:(NSInteger)loggingContext { + [self addToAllowlist:loggingContext]; +} + +- (void)removeFromWhitelist:(NSInteger)loggingContext { + [self removeFromAllowlist:loggingContext]; +} + +- (NSArray *)whitelist { + return [self allowlist]; +} + +- (BOOL)isOnWhitelist:(NSInteger)loggingContext { + return [self isOnAllowlist:loggingContext]; +} + +@end + + +@implementation DDContextDenylistFilterLogFormatter (Deprecated) + +- (void)addToBlacklist:(NSInteger)loggingContext { + [self addToDenylist:loggingContext]; +} + +- (void)removeFromBlacklist:(NSInteger)loggingContext { + [self removeFromDenylist:loggingContext]; +} + +- (NSArray *)blacklist { + return [self denylist]; +} + +- (BOOL)isOnBlacklist:(NSInteger)loggingContext { + return [self isOnDenylist:loggingContext]; +} + +@end diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter.m new file mode 100755 index 000000000..271cd4b9a --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter.m @@ -0,0 +1,185 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import + +#import + +@interface DDLoggingContextSet : NSObject + +@property (readonly, copy, nonnull) NSArray *currentSet; + +- (void)addToSet:(NSInteger)loggingContext; +- (void)removeFromSet:(NSInteger)loggingContext; + +- (BOOL)isInSet:(NSInteger)loggingContext; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDContextAllowlistFilterLogFormatter () { + DDLoggingContextSet *_contextSet; +} +@end + +@implementation DDContextAllowlistFilterLogFormatter + +- (instancetype)init { + if ((self = [super init])) { + _contextSet = [[DDLoggingContextSet alloc] init]; + } + return self; +} + +- (void)addToAllowlist:(NSInteger)loggingContext { + [_contextSet addToSet:loggingContext]; +} + +- (void)removeFromAllowlist:(NSInteger)loggingContext { + [_contextSet removeFromSet:loggingContext]; +} + +- (NSArray *)allowlist { + return [_contextSet currentSet]; +} + +- (BOOL)isOnAllowlist:(NSInteger)loggingContext { + return [_contextSet isInSet:loggingContext]; +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + if ([self isOnAllowlist:logMessage->_context]) { + return logMessage->_message; + } else { + return nil; + } +} + +@end + + +@interface DDContextDenylistFilterLogFormatter () { + DDLoggingContextSet *_contextSet; +} +@end + +@implementation DDContextDenylistFilterLogFormatter + +- (instancetype)init { + if ((self = [super init])) { + _contextSet = [[DDLoggingContextSet alloc] init]; + } + return self; +} + +- (void)addToDenylist:(NSInteger)loggingContext { + [_contextSet addToSet:loggingContext]; +} + +- (void)removeFromDenylist:(NSInteger)loggingContext { + [_contextSet removeFromSet:loggingContext]; +} + +- (NSArray *)denylist { + return [_contextSet currentSet]; +} + +- (BOOL)isOnDenylist:(NSInteger)loggingContext { + return [_contextSet isInSet:loggingContext]; +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + if ([self isOnDenylist:logMessage->_context]) { + return nil; + } else { + return logMessage->_message; + } +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDLoggingContextSet () { + pthread_mutex_t _mutex; + NSMutableSet *_set; +} +@end + +@implementation DDLoggingContextSet + +- (instancetype)init { + if ((self = [super init])) { + _set = [[NSMutableSet alloc] init]; + pthread_mutex_init(&_mutex, NULL); + } + + return self; +} + +- (void)dealloc { + pthread_mutex_destroy(&_mutex); +} + +- (void)addToSet:(NSInteger)loggingContext { + pthread_mutex_lock(&_mutex); + { + [_set addObject:@(loggingContext)]; + } + pthread_mutex_unlock(&_mutex); +} + +- (void)removeFromSet:(NSInteger)loggingContext { + pthread_mutex_lock(&_mutex); + { + [_set removeObject:@(loggingContext)]; + } + pthread_mutex_unlock(&_mutex); +} + +- (NSArray *)currentSet { + NSArray *result = nil; + + pthread_mutex_lock(&_mutex); + { + result = [_set allObjects]; + } + pthread_mutex_unlock(&_mutex); + + return result; +} + +- (BOOL)isInSet:(NSInteger)loggingContext { + __auto_type result = NO; + + pthread_mutex_lock(&_mutex); + { + result = [_set containsObject:@(loggingContext)]; + } + pthread_mutex_unlock(&_mutex); + + return result; +} + +@end diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDDispatchQueueLogFormatter.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDDispatchQueueLogFormatter.m new file mode 100755 index 000000000..d34885519 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDDispatchQueueLogFormatter.m @@ -0,0 +1,240 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import +#import +#import + +#import + +DDQualityOfServiceName const DDQualityOfServiceUserInteractive = @"UI"; +DDQualityOfServiceName const DDQualityOfServiceUserInitiated = @"IN"; +DDQualityOfServiceName const DDQualityOfServiceDefault = @"DF"; +DDQualityOfServiceName const DDQualityOfServiceUtility = @"UT"; +DDQualityOfServiceName const DDQualityOfServiceBackground = @"BG"; +DDQualityOfServiceName const DDQualityOfServiceUnspecified = @"UN"; + +static DDQualityOfServiceName _qos_name(NSUInteger qos) { + switch ((qos_class_t) qos) { + case QOS_CLASS_USER_INTERACTIVE: return DDQualityOfServiceUserInteractive; + case QOS_CLASS_USER_INITIATED: return DDQualityOfServiceUserInitiated; + case QOS_CLASS_DEFAULT: return DDQualityOfServiceDefault; + case QOS_CLASS_UTILITY: return DDQualityOfServiceUtility; + case QOS_CLASS_BACKGROUND: return DDQualityOfServiceBackground; + default: return DDQualityOfServiceUnspecified; + } +} + +#pragma mark - DDDispatchQueueLogFormatter + +@interface DDDispatchQueueLogFormatter () { + NSDateFormatter *_dateFormatter; // Use [self stringFromDate] + + pthread_mutex_t _mutex; + + NSUInteger _minQueueLength; // _prefix == Only access via atomic property + NSUInteger _maxQueueLength; // _prefix == Only access via atomic property + NSMutableDictionary *_replacements; // _prefix == Only access from within spinlock +} +@end + + +@implementation DDDispatchQueueLogFormatter + +- (instancetype)init { + if ((self = [super init])) { + _dateFormatter = [self createDateFormatter]; + + pthread_mutex_init(&_mutex, NULL); + _replacements = [[NSMutableDictionary alloc] init]; + + // Set default replacements: + _replacements[@"com.apple.main-thread"] = @"main"; + } + + return self; +} + +- (instancetype)initWithMode:(DDDispatchQueueLogFormatterMode)mode { + return [self init]; +} + +- (void)dealloc { + pthread_mutex_destroy(&_mutex); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@synthesize minQueueLength = _minQueueLength; +@synthesize maxQueueLength = _maxQueueLength; + +- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel { + NSString *result = nil; + + pthread_mutex_lock(&_mutex); + { + result = _replacements[longLabel]; + } + pthread_mutex_unlock(&_mutex); + + return result; +} + +- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel { + pthread_mutex_lock(&_mutex); + { + if (shortLabel) { + _replacements[longLabel] = shortLabel; + } else { + [_replacements removeObjectForKey:longLabel]; + } + } + pthread_mutex_unlock(&_mutex); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark DDLogFormatter +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSDateFormatter *)createDateFormatter { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [self configureDateFormatter:formatter]; + return formatter; +} + +- (void)configureDateFormatter:(NSDateFormatter *)dateFormatter { + [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss:SSS"]; + [dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]]; + [dateFormatter setCalendar:[[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]]; +} + +- (NSString *)stringFromDate:(NSDate *)date { + return [_dateFormatter stringFromDate:date]; +} + +- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage { + // As per the DDLogFormatter contract, this method is always invoked on the same thread/dispatch_queue + + __auto_type useQueueLabel = NO; + if (logMessage->_queueLabel) { + useQueueLabel = YES; + + // If you manually create a thread, it's dispatch_queue will have one of the thread names below. + // Since all such threads have the same name, we'd prefer to use the threadName or the machThreadID. + const NSArray *names = @[ + @"com.apple.root.low-priority", + @"com.apple.root.default-priority", + @"com.apple.root.high-priority", + @"com.apple.root.low-overcommit-priority", + @"com.apple.root.default-overcommit-priority", + @"com.apple.root.high-overcommit-priority", + @"com.apple.root.default-qos.overcommit", + ]; + for (NSString *name in names) { + if ([logMessage->_queueLabel isEqualToString:name]) { + useQueueLabel = NO; + break; + } + } + } + + // Get the name of the queue, thread, or machID (whichever we are to use). + NSString *queueThreadLabel; + if (useQueueLabel || [logMessage->_threadName length] > 0) { + __auto_type fullLabel = useQueueLabel ? logMessage->_queueLabel : logMessage->_threadName; + + NSString *abrvLabel; + pthread_mutex_lock(&_mutex); + { + abrvLabel = _replacements[fullLabel]; + } + pthread_mutex_unlock(&_mutex); + + queueThreadLabel = abrvLabel ?: fullLabel; + } else { + queueThreadLabel = logMessage->_threadID; + } + + // Now use the thread label in the output + // labelLength > maxQueueLength : truncate + // labelLength < minQueueLength : padding + // : exact + __auto_type minQueueLength = self.minQueueLength; + __auto_type maxQueueLength = self.maxQueueLength; + __auto_type labelLength = [queueThreadLabel length]; + if (maxQueueLength > 0 && labelLength > maxQueueLength) { + // Truncate + return [queueThreadLabel substringToIndex:maxQueueLength]; + } else if (labelLength < minQueueLength) { + // Padding + return [queueThreadLabel stringByPaddingToLength:minQueueLength + withString:@" " + startingAtIndex:0]; + } else { + // Exact + return queueThreadLabel; + } +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + __auto_type timestamp = [self stringFromDate:logMessage->_timestamp]; + __auto_type queueThreadLabel = [self queueThreadLabelForLogMessage:logMessage]; + + return [NSString stringWithFormat:@"%@ [%@ (QOS:%@)] %@", timestamp, queueThreadLabel, _qos_name(logMessage->_qos), logMessage->_message]; +} + +@end + +#pragma mark - DDAtomicCounter + +@interface DDAtomicCounter() { + atomic_int_fast32_t _value; +} +@end + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" +@implementation DDAtomicCounter +#pragma clang diagnostic pop + +- (instancetype)initWithDefaultValue:(int32_t)defaultValue { + if ((self = [super init])) { + atomic_init(&_value, defaultValue); + } + return self; +} + +- (int32_t)value { + return atomic_load_explicit(&_value, memory_order_relaxed); +} + +- (int32_t)increment { + int32_t old = atomic_fetch_add_explicit(&_value, 1, memory_order_relaxed); + return (old + 1); +} + +- (int32_t)decrement { + int32_t old = atomic_fetch_sub_explicit(&_value, 1, memory_order_relaxed); + return (old - 1); +} + +@end diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDFileLogger+Buffering.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDFileLogger+Buffering.m new file mode 100644 index 000000000..a620a3ed3 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDFileLogger+Buffering.m @@ -0,0 +1,202 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +#import +#import "../DDFileLogger+Internal.h" + +static const NSUInteger kDDDefaultBufferSize = 4096; // 4 kB, block f_bsize on iphone7 +static const NSUInteger kDDMaxBufferSize = 1048576; // ~1 mB, f_iosize on iphone7 + +// Reads attributes from base file system to determine buffer size. +// see statfs in sys/mount.h for descriptions of f_iosize and f_bsize. +// f_bsize == "default", and f_iosize == "max" +static inline NSUInteger p_DDGetDefaultBufferSizeBytesMax(const BOOL max) { + struct statfs *mountedFileSystems = NULL; + __auto_type count = getmntinfo(&mountedFileSystems, 0); + + for (int i = 0; i < count; i++) { + __auto_type mounted = mountedFileSystems[i]; + __auto_type name = mounted.f_mntonname; + + // We can use 2 as max here, since any length > 1 will fail the if-statement. + if (strnlen(name, 2) == 1 && *name == '/') { + return max ? (NSUInteger)mounted.f_iosize : (NSUInteger)mounted.f_bsize; + } + } + + return max ? kDDMaxBufferSize : kDDDefaultBufferSize; +} + +static NSUInteger DDGetMaxBufferSizeBytes(void) { + static NSUInteger maxBufferSize = 0; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + maxBufferSize = p_DDGetDefaultBufferSizeBytesMax(YES); + }); + return maxBufferSize; +} + +static NSUInteger DDGetDefaultBufferSizeBytes(void) { + static NSUInteger defaultBufferSize = 0; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + defaultBufferSize = p_DDGetDefaultBufferSizeBytesMax(NO); + }); + return defaultBufferSize; +} + +@interface DDBufferedProxy : NSProxy + +@property (nonatomic) DDFileLogger *fileLogger; +@property (nonatomic) NSOutputStream *buffer; + +@property (nonatomic) NSUInteger maxBufferSizeBytes; +@property (nonatomic) NSUInteger currentBufferSizeBytes; + +@end + +@implementation DDBufferedProxy + +- (instancetype)initWithFileLogger:(DDFileLogger *)fileLogger { + _fileLogger = fileLogger; + _maxBufferSizeBytes = DDGetDefaultBufferSizeBytes(); + [self flushBuffer]; + + return self; +} + +- (void)dealloc { + __auto_type block = ^{ + [self lt_sendBufferedDataToFileLogger]; + self.fileLogger = nil; + }; + + if ([self->_fileLogger isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_sync(self->_fileLogger.loggerQueue, block); + } +} + +#pragma mark - Buffering + +- (void)flushBuffer { + [_buffer close]; + _buffer = [NSOutputStream outputStreamToMemory]; + [_buffer open]; + _currentBufferSizeBytes = 0; +} + +- (void)lt_sendBufferedDataToFileLogger { + NSData *data = [_buffer propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; + [_fileLogger lt_logData:data]; + [self flushBuffer]; +} + +#pragma mark - Logging + +- (void)logMessage:(DDLogMessage *)logMessage { + // Don't need to check for isOnInternalLoggerQueue, -lt_dataForMessage: will do it for us. + __auto_type data = [_fileLogger lt_dataForMessage:logMessage]; + + if (data.length == 0) { + return; + } + + [data enumerateByteRangesUsingBlock:^(const void * __nonnull bytes, NSRange byteRange, BOOL * __nonnull __unused stop) { + __auto_type bytesLength = byteRange.length; +#ifdef NS_BLOCK_ASSERTIONS + __unused +#endif + __auto_type written = [_buffer write:bytes maxLength:bytesLength]; + NSAssert(written > 0 && (NSUInteger)written == bytesLength, @"Failed to write to memory buffer."); + + _currentBufferSizeBytes += bytesLength; + + if (_currentBufferSizeBytes >= _maxBufferSizeBytes) { + [self lt_sendBufferedDataToFileLogger]; + } + }]; +} + +- (void)flush { + // This method is public. + // We need to execute the rolling on our logging thread/queue. + + __auto_type block = ^{ + @autoreleasepool { + [self lt_sendBufferedDataToFileLogger]; + [self.fileLogger flush]; + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + if ([self.fileLogger isOnInternalLoggerQueue]) { + block(); + } else { + NSAssert(![self.fileLogger isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.fileLogger.loggerQueue, block); + }); + } +} + +#pragma mark - Properties + +- (void)setMaxBufferSizeBytes:(NSUInteger)newBufferSizeBytes { + _maxBufferSizeBytes = MIN(newBufferSizeBytes, DDGetMaxBufferSizeBytes()); +} + +#pragma mark - Wrapping + +- (DDFileLogger *)wrapWithBuffer { + return (DDFileLogger *)self; +} + +- (DDFileLogger *)unwrapFromBuffer { + return (DDFileLogger *)self.fileLogger; +} + +#pragma mark - NSProxy + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { + return [self.fileLogger methodSignatureForSelector:sel]; +} + +- (BOOL)respondsToSelector:(SEL)aSelector { + return [self.fileLogger respondsToSelector:aSelector]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation { + [invocation invokeWithTarget:self.fileLogger]; +} + +@end + +@implementation DDFileLogger (Buffering) + +- (instancetype)wrapWithBuffer { + return (DDFileLogger *)[[DDBufferedProxy alloc] initWithFileLogger:self]; +} + +- (instancetype)unwrapFromBuffer { + return self; +} + +@end diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDMultiFormatter.m b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDMultiFormatter.m new file mode 100644 index 000000000..078265231 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDMultiFormatter.m @@ -0,0 +1,110 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import + +@interface DDMultiFormatter () { + dispatch_queue_t _queue; + NSMutableArray *_formatters; +} + +- (DDLogMessage *)logMessageForLine:(NSString *)line originalMessage:(DDLogMessage *)message; + +@end + + +@implementation DDMultiFormatter + +- (instancetype)init { + self = [super init]; + + if (self) { + _queue = dispatch_queue_create("cocoa.lumberjack.multiformatter", DISPATCH_QUEUE_CONCURRENT); + _formatters = [NSMutableArray new]; + } + + return self; +} + +#pragma mark Processing + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + __block __auto_type line = logMessage->_message; + + dispatch_sync(_queue, ^{ + for (id formatter in self->_formatters) { + __auto_type message = [self logMessageForLine:line originalMessage:logMessage]; + line = [formatter formatLogMessage:message]; + + if (!line) { + break; + } + } + }); + + return line; +} + +- (DDLogMessage *)logMessageForLine:(NSString *)line originalMessage:(DDLogMessage *)message { + DDLogMessage *newMessage = [message copy]; + newMessage->_message = line; + return newMessage; +} + +#pragma mark Formatters + +- (NSArray *)formatters { + __block NSArray *formatters; + + dispatch_sync(_queue, ^{ + formatters = [self->_formatters copy]; + }); + + return formatters; +} + +- (void)addFormatter:(id)formatter { + dispatch_barrier_async(_queue, ^{ + [self->_formatters addObject:formatter]; + }); +} + +- (void)removeFormatter:(id)formatter { + dispatch_barrier_async(_queue, ^{ + [self->_formatters removeObject:formatter]; + }); +} + +- (void)removeAllFormatters { + dispatch_barrier_async(_queue, ^{ + [self->_formatters removeAllObjects]; + }); +} + +- (BOOL)isFormattingWithFormatter:(id)formatter { + __block BOOL hasFormatter; + + dispatch_sync(_queue, ^{ + hasFormatter = [self->_formatters containsObject:formatter]; + }); + + return hasFormatter; +} + +@end diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/PrivacyInfo.xcprivacy b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..ec783fe6a --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/PrivacyInfo.xcprivacy @@ -0,0 +1,30 @@ + + + + + NSPrivacyTracking + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + 0A2A.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + + + + + diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/CocoaLumberjack.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/CocoaLumberjack.h new file mode 100644 index 000000000..91b240880 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/CocoaLumberjack.h @@ -0,0 +1,104 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +/** + * Welcome to CocoaLumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/CocoaLumberjack/CocoaLumberjack + * + * If you're new to the project you may wish to read "Getting Started" at: + * Documentation/GettingStarted.md + * + * Otherwise, here is a quick refresher. + * There are three steps to using the macros: + * + * Step 1: + * Import the header in your implementation or prefix file: + * + * #import + * + * Step 2: + * Define your logging level in your implementation file: + * + * // Log levels: off, error, warn, info, verbose + * static const DDLogLevel ddLogLevel = DDLogLevelVerbose; + * + * Step 2 [3rd party frameworks]: + * + * Define your LOG_LEVEL_DEF to a different variable/function than ddLogLevel: + * + * // #undef LOG_LEVEL_DEF // Undefine first only if needed + * #define LOG_LEVEL_DEF myLibLogLevel + * + * Define your logging level in your implementation file: + * + * // Log levels: off, error, warn, info, verbose + * static const DDLogLevel myLibLogLevel = DDLogLevelVerbose; + * + * Step 3: + * Replace your NSLog statements with DDLog statements according to the severity of the message. + * + * NSLog(@"Fatal error, no dohickey found!"); -> DDLogError(@"Fatal error, no dohickey found!"); + * + * DDLog works exactly the same as NSLog. + * This means you can pass it multiple variables just like NSLog. + **/ + +#import + +//! Project version number for CocoaLumberjack. +FOUNDATION_EXPORT double CocoaLumberjackVersionNumber; + +//! Project version string for CocoaLumberjack. +FOUNDATION_EXPORT const unsigned char CocoaLumberjackVersionString[]; + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +// Core +#import + +// Main macros +#import +#import + +// Capture ASL +#import + +// Loggers +#import + +#import +#import +#import +#import + +// Extensions +#import +#import +#import +#import +#import + +// CLI +#import + +// etc +#import +#import +#import diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/DDLegacyMacros.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/DDLegacyMacros.h new file mode 100644 index 000000000..5289f33bb --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/DDLegacyMacros.h @@ -0,0 +1,75 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +/** + * Legacy macros used for 1.9.x backwards compatibility. + * + * Imported by default when importing a DDLog.h directly and DD_LEGACY_MACROS is not defined and set to 0. + **/ +#if DD_LEGACY_MACROS + +#warning CocoaLumberjack 1.9.x legacy macros enabled. \ +Disable legacy macros by importing CocoaLumberjack.h or DDLogMacros.h instead of DDLog.h or add `#define DD_LEGACY_MACROS 0` before importing DDLog.h. + +#ifndef LOG_LEVEL_DEF + #define LOG_LEVEL_DEF ddLogLevel +#endif + +#define LOG_FLAG_ERROR DDLogFlagError +#define LOG_FLAG_WARN DDLogFlagWarning +#define LOG_FLAG_INFO DDLogFlagInfo +#define LOG_FLAG_DEBUG DDLogFlagDebug +#define LOG_FLAG_VERBOSE DDLogFlagVerbose + +#define LOG_LEVEL_OFF DDLogLevelOff +#define LOG_LEVEL_ERROR DDLogLevelError +#define LOG_LEVEL_WARN DDLogLevelWarning +#define LOG_LEVEL_INFO DDLogLevelInfo +#define LOG_LEVEL_DEBUG DDLogLevelDebug +#define LOG_LEVEL_VERBOSE DDLogLevelVerbose +#define LOG_LEVEL_ALL DDLogLevelAll + +#define LOG_ASYNC_ENABLED YES + +#define LOG_ASYNC_ERROR ( NO && LOG_ASYNC_ENABLED) +#define LOG_ASYNC_WARN (YES && LOG_ASYNC_ENABLED) +#define LOG_ASYNC_INFO (YES && LOG_ASYNC_ENABLED) +#define LOG_ASYNC_DEBUG (YES && LOG_ASYNC_ENABLED) +#define LOG_ASYNC_VERBOSE (YES && LOG_ASYNC_ENABLED) + +#define LOG_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, ...) \ + [DDLog log : isAsynchronous \ + level : lvl \ + flag : flg \ + context : ctx \ + file : __FILE__ \ + function : fnct \ + line : __LINE__ \ + tag : atag \ + format : (frmt), ## __VA_ARGS__] + +#define LOG_MAYBE(async, lvl, flg, ctx, fnct, frmt, ...) \ + do { if((lvl & flg) != 0) LOG_MACRO(async, lvl, flg, ctx, nil, fnct, frmt, ##__VA_ARGS__); } while(0) + +#define LOG_OBJC_MAYBE(async, lvl, flg, ctx, frmt, ...) \ + LOG_MAYBE(async, lvl, flg, ctx, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__) + +#define DDLogError(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_ERROR, LOG_LEVEL_DEF, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__) +#define DDLogWarn(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_WARN, LOG_LEVEL_DEF, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__) +#define DDLogInfo(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_INFO, LOG_LEVEL_DEF, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__) +#define DDLogDebug(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_DEBUG, LOG_LEVEL_DEF, LOG_FLAG_DEBUG, 0, frmt, ##__VA_ARGS__) +#define DDLogVerbose(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_VERBOSE, LOG_LEVEL_DEF, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__) + +#endif diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/CLIColor.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/CLIColor.h new file mode 100644 index 000000000..d702e9c85 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/CLIColor.h @@ -0,0 +1,54 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +#if TARGET_OS_OSX + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class represents an NSColor replacement for CLI projects that don't link with AppKit + **/ +@interface CLIColor : NSObject + +/** + * Convenience method for creating a `CLIColor` instance from RGBA params + * + * @param red red channel, between 0 and 1 + * @param green green channel, between 0 and 1 + * @param blue blue channel, between 0 and 1 + * @param alpha alpha channel, between 0 and 1 + */ ++ (instancetype)colorWithCalibratedRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha; + +/** + * Get the RGBA components from a `CLIColor` + * + * @param red red channel, between 0 and 1 + * @param green green channel, between 0 and 1 + * @param blue blue channel, between 0 and 1 + * @param alpha alpha channel, between 0 and 1 + */ +- (void)getRed:(nullable CGFloat *)red green:(nullable CGFloat *)green blue:(nullable CGFloat *)blue alpha:(nullable CGFloat *)alpha NS_SWIFT_NAME(get(red:green:blue:alpha:)); + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogCapture.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogCapture.h new file mode 100644 index 000000000..90226a6d0 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogCapture.h @@ -0,0 +1,46 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +@protocol DDLogger; + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class provides the ability to capture the ASL (Apple System Logs) + */ +API_DEPRECATED("Use DDOSLogger instead", macosx(10.4,10.12), ios(2.0,10.0), watchos(2.0,3.0), tvos(9.0,10.0)) +@interface DDASLLogCapture : NSObject + +/** + * Start capturing logs + */ ++ (void)start; + +/** + * Stop capturing logs + */ ++ (void)stop; + +/** + * The current capture level. + * @note Default log level: DDLogLevelVerbose (i.e. capture all ASL messages). + */ +@property (class) DDLogLevel captureLevel; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogger.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogger.h new file mode 100644 index 000000000..8951a8f92 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogger.h @@ -0,0 +1,63 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +// Custom key set on messages sent to ASL +extern const char* const kDDASLKeyDDLog; + +// Value set for kDDASLKeyDDLog +extern const char* const kDDASLDDLogValue; + +/** + * This class provides a logger for the Apple System Log facility. + * + * As described in the "Getting Started" page, + * the traditional NSLog() function directs its output to two places: + * + * - Apple System Log + * - StdErr (if stderr is a TTY) so log statements show up in Xcode console + * + * To duplicate NSLog() functionality you can simply add this logger and a tty logger. + * However, if you instead choose to use file logging (for faster performance), + * you may choose to use a file logger and a tty logger. + **/ +API_DEPRECATED("Use DDOSLogger instead", macosx(10.4,10.12), ios(2.0,10.0), watchos(2.0,3.0), tvos(9.0,10.0)) +@interface DDASLLogger : DDAbstractLogger + +/** + * Singleton method + * + * @return the shared instance + */ +@property (nonatomic, class, readonly, strong) DDASLLogger *sharedInstance; + +// Inherited from DDAbstractLogger + +// - (id )logFormatter; +// - (void)setLogFormatter:(id )formatter; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAbstractDatabaseLogger.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAbstractDatabaseLogger.h new file mode 100644 index 000000000..e7d0b8cc1 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAbstractDatabaseLogger.h @@ -0,0 +1,127 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class provides an abstract implementation of a database logger. + * + * That is, it provides the base implementation for a database logger to build atop of. + * All that is needed for a concrete database logger is to extend this class + * and override the methods in the implementation file that are prefixed with "db_". + **/ +@interface DDAbstractDatabaseLogger : DDAbstractLogger { + +@protected + NSUInteger _saveThreshold; + NSTimeInterval _saveInterval; + NSTimeInterval _maxAge; + NSTimeInterval _deleteInterval; + BOOL _deleteOnEverySave; + + NSInteger _saveTimerSuspended; + NSUInteger _unsavedCount; + dispatch_time_t _unsavedTime; + dispatch_source_t _saveTimer; + dispatch_time_t _lastDeleteTime; + dispatch_source_t _deleteTimer; +} + +/** + * Specifies how often to save the data to disk. + * Since saving is an expensive operation (disk io) it is not done after every log statement. + * These properties allow you to configure how/when the logger saves to disk. + * + * A save is done when either (whichever happens first): + * + * - The number of unsaved log entries reaches saveThreshold + * - The amount of time since the oldest unsaved log entry was created reaches saveInterval + * + * You can optionally disable the saveThreshold by setting it to zero. + * If you disable the saveThreshold you are entirely dependent on the saveInterval. + * + * You can optionally disable the saveInterval by setting it to zero (or a negative value). + * If you disable the saveInterval you are entirely dependent on the saveThreshold. + * + * It's not wise to disable both saveThreshold and saveInterval. + * + * The default saveThreshold is 500. + * The default saveInterval is 60 seconds. + **/ +@property (assign, readwrite) NSUInteger saveThreshold; + +/** + * See the description for the `saveThreshold` property + */ +@property (assign, readwrite) NSTimeInterval saveInterval; + +/** + * It is likely you don't want the log entries to persist forever. + * Doing so would allow the database to grow infinitely large over time. + * + * The maxAge property provides a way to specify how old a log statement can get + * before it should get deleted from the database. + * + * The deleteInterval specifies how often to sweep for old log entries. + * Since deleting is an expensive operation (disk io) is is done on a fixed interval. + * + * An alternative to the deleteInterval is the deleteOnEverySave option. + * This specifies that old log entries should be deleted during every save operation. + * + * You can optionally disable the maxAge by setting it to zero (or a negative value). + * If you disable the maxAge then old log statements are not deleted. + * + * You can optionally disable the deleteInterval by setting it to zero (or a negative value). + * + * If you disable both deleteInterval and deleteOnEverySave then old log statements are not deleted. + * + * It's not wise to enable both deleteInterval and deleteOnEverySave. + * + * The default maxAge is 7 days. + * The default deleteInterval is 5 minutes. + * The default deleteOnEverySave is NO. + **/ +@property (assign, readwrite) NSTimeInterval maxAge; + +/** + * See the description for the `maxAge` property + */ +@property (assign, readwrite) NSTimeInterval deleteInterval; + +/** + * See the description for the `maxAge` property + */ +@property (assign, readwrite) BOOL deleteOnEverySave; + +/** + * Forces a save of any pending log entries (flushes log entries to disk). + **/ +- (void)savePendingLogEntries; + +/** + * Removes any log entries that are older than maxAge. + **/ +- (void)deleteOldLogEntries; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAssertMacros.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAssertMacros.h new file mode 100644 index 000000000..de8d8f121 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAssertMacros.h @@ -0,0 +1,30 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +/** + * NSAssert replacement that will output a log message even when assertions are disabled. + **/ +#define DDAssert(condition, frmt, ...) \ + if (!(condition)) { \ + NSString *description = [NSString stringWithFormat:frmt, ## __VA_ARGS__]; \ + DDLogError(@"%@", description); \ + NSAssert(NO, @"%@", description); \ + } +#define DDAssertCondition(condition) DDAssert(condition, @"Condition not satisfied: %@", @(#condition)) + +/** + * Analog to `DDAssertionFailure` from DDAssert.swift for use in Objective C + */ +#define DDAssertionFailure(frmt, ...) DDAssert(NO, frmt, ##__VA_ARGS__) diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter+Deprecated.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter+Deprecated.h new file mode 100644 index 000000000..3ffc0402c --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter+Deprecated.h @@ -0,0 +1,119 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class provides a log formatter that filters log statements from a logging context not on the whitelist. + * @deprecated Use DDContextAllowlistFilterLogFormatter instead. + * + * A log formatter can be added to any logger to format and/or filter its output. + * You can learn more about log formatters here: + * Documentation/CustomFormatters.md + * + * You can learn more about logging context's here: + * Documentation/CustomContext.md + * + * But here's a quick overview / refresher: + * + * Every log statement has a logging context. + * These come from the underlying logging macros defined in DDLog.h. + * The default logging context is zero. + * You can define multiple logging context's for use in your application. + * For example, logically separate parts of your app each have a different logging context. + * Also 3rd party frameworks that make use of Lumberjack generally use their own dedicated logging context. + **/ +__attribute__((deprecated("Use DDContextAllowlistFilterLogFormatter instead"))) +typedef DDContextAllowlistFilterLogFormatter DDContextWhitelistFilterLogFormatter; + +@interface DDContextAllowlistFilterLogFormatter (Deprecated) + +/** + * Add a context to the whitelist + * @deprecated Use -addToAllowlist: instead. + * + * @param loggingContext the context + */ +- (void)addToWhitelist:(NSInteger)loggingContext __attribute__((deprecated("Use -addToAllowlist: instead"))); + +/** + * Remove context from whitelist + * @deprecated Use -removeFromAllowlist: instead. + * + * @param loggingContext the context + */ +- (void)removeFromWhitelist:(NSInteger)loggingContext __attribute__((deprecated("Use -removeFromAllowlist: instead"))); + +/** + * Return the whitelist + * @deprecated Use allowlist instead. + */ +@property (nonatomic, readonly, copy) NSArray *whitelist __attribute__((deprecated("Use allowlist instead"))); + +/** + * Check if a context is on the whitelist + * @deprecated Use -isOnAllowlist: instead. + * + * @param loggingContext the context + */ +- (BOOL)isOnWhitelist:(NSInteger)loggingContext __attribute__((deprecated("Use -isOnAllowlist: instead"))); + +@end + + +/** + * This class provides a log formatter that filters log statements from a logging context on the blacklist. + * @deprecated Use DDContextDenylistFilterLogFormatter instead. + **/ +__attribute__((deprecated("Use DDContextDenylistFilterLogFormatter instead"))) +typedef DDContextDenylistFilterLogFormatter DDContextBlacklistFilterLogFormatter; + +@interface DDContextDenylistFilterLogFormatter (Deprecated) + +/** + * Add a context to the blacklist + * @deprecated Use -addToDenylist: instead. + * + * @param loggingContext the context + */ +- (void)addToBlacklist:(NSInteger)loggingContext __attribute__((deprecated("Use -addToDenylist: instead"))); + +/** + * Remove context from blacklist + * @deprecated Use -removeFromDenylist: instead. + * + * @param loggingContext the context + */ +- (void)removeFromBlacklist:(NSInteger)loggingContext __attribute__((deprecated("Use -removeFromDenylist: instead"))); + +/** + * Return the blacklist + * @deprecated Use denylist instead. + */ +@property (readonly, copy) NSArray *blacklist __attribute__((deprecated("Use denylist instead"))); + +/** + * Check if a context is on the blacklist + * @deprecated Use -isOnDenylist: instead. + * + * @param loggingContext the context + */ +- (BOOL)isOnBlacklist:(NSInteger)loggingContext __attribute__((deprecated("Use -isOnDenylist: instead"))); + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter.h new file mode 100644 index 000000000..c83a689aa --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter.h @@ -0,0 +1,117 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class provides a log formatter that filters log statements from a logging context not on the allowlist. + * + * A log formatter can be added to any logger to format and/or filter its output. + * You can learn more about log formatters here: + * Documentation/CustomFormatters.md + * + * You can learn more about logging context's here: + * Documentation/CustomContext.md + * + * But here's a quick overview / refresher: + * + * Every log statement has a logging context. + * These come from the underlying logging macros defined in DDLog.h. + * The default logging context is zero. + * You can define multiple logging context's for use in your application. + * For example, logically separate parts of your app each have a different logging context. + * Also 3rd party frameworks that make use of Lumberjack generally use their own dedicated logging context. + **/ +@interface DDContextAllowlistFilterLogFormatter : NSObject + +/** + * Designated default initializer + */ +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/** + * Add a context to the allowlist + * + * @param loggingContext the context + */ +- (void)addToAllowlist:(NSInteger)loggingContext; + +/** + * Remove context from allowlist + * + * @param loggingContext the context + */ +- (void)removeFromAllowlist:(NSInteger)loggingContext; + +/** + * Return the allowlist + */ +@property (nonatomic, readonly, copy) NSArray *allowlist; + +/** + * Check if a context is on the allowlist + * + * @param loggingContext the context + */ +- (BOOL)isOnAllowlist:(NSInteger)loggingContext; + +@end + + +/** + * This class provides a log formatter that filters log statements from a logging context on the denylist. + **/ +@interface DDContextDenylistFilterLogFormatter : NSObject + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/** + * Add a context to the denylist + * + * @param loggingContext the context + */ +- (void)addToDenylist:(NSInteger)loggingContext; + +/** + * Remove context from denylist + * + * @param loggingContext the context + */ +- (void)removeFromDenylist:(NSInteger)loggingContext; + +/** + * Return the denylist + */ +@property (readonly, copy) NSArray *denylist; + +/** + * Check if a context is on the denylist + * + * @param loggingContext the context + */ +- (BOOL)isOnDenylist:(NSInteger)loggingContext; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDDispatchQueueLogFormatter.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDDispatchQueueLogFormatter.h new file mode 100644 index 000000000..90d32d4dd --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDDispatchQueueLogFormatter.h @@ -0,0 +1,223 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Log formatter mode + */ +__attribute__((deprecated("DDDispatchQueueLogFormatter is always shareable"))) +typedef NS_ENUM(NSUInteger, DDDispatchQueueLogFormatterMode){ + /** + * This is the default option, means the formatter can be reused between multiple loggers and therefore is thread-safe. + * There is, of course, a performance cost for the thread-safety + */ + DDDispatchQueueLogFormatterModeShareble = 0, + /** + * If the formatter will only be used by a single logger, then the thread-safety can be removed + * @note: there is an assert checking if the formatter is added to multiple loggers and the mode is non-shareble + */ + DDDispatchQueueLogFormatterModeNonShareble, +}; + +/** + * Quality of Service names. + * + * Since macOS 10.10 and iOS 8.0, pthreads, dispatch queues and NSOperations express their + * scheduling priority by using an abstract classification called Quality of Service (QOS). + * + * This formatter will add a representation of this QOS in the log message by using those + * string constants. + * For example: + * + * `2011-10-17 20:21:45.435 AppName[19928:5207 (QOS:DF)] Your log message here` + * + * Where QOS is one of: + * `- UI = User Interactive` + * `- IN = User Initiated` + * `- DF = Default` + * `- UT = Utility` + * `- BG = Background` + * `- UN = Unspecified` + * + * Note: QOS will be absent in the log messages if running on OS versions that don't support it. + **/ +typedef NSString * DDQualityOfServiceName NS_STRING_ENUM; + +FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceUserInteractive NS_SWIFT_NAME(DDQualityOfServiceName.userInteractive) API_AVAILABLE(macos(10.10), ios(8.0)); +FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceUserInitiated NS_SWIFT_NAME(DDQualityOfServiceName.userInitiated) API_AVAILABLE(macos(10.10), ios(8.0)); +FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceDefault NS_SWIFT_NAME(DDQualityOfServiceName.default) API_AVAILABLE(macos(10.10), ios(8.0)); +FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceUtility NS_SWIFT_NAME(DDQualityOfServiceName.utility) API_AVAILABLE(macos(10.10), ios(8.0)); +FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceBackground NS_SWIFT_NAME(DDQualityOfServiceName.background) API_AVAILABLE(macos(10.10), ios(8.0)); +FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceUnspecified NS_SWIFT_NAME(DDQualityOfServiceName.unspecified) API_AVAILABLE(macos(10.10), ios(8.0)); + +/** + * This class provides a log formatter that prints the dispatch_queue label instead of the mach_thread_id. + * + * A log formatter can be added to any logger to format and/or filter its output. + * You can learn more about log formatters here: + * Documentation/CustomFormatters.md + * + * A typical `NSLog` (or `DDTTYLogger`) prints detailed info as `[:]`. + * For example: + * + * `2011-10-17 20:21:45.435 AppName[19928:5207] Your log message here` + * + * Where: + * `- 19928 = process id` + * `- 5207 = thread id (mach_thread_id printed in hex)` + * + * When using grand central dispatch (GCD), this information is less useful. + * This is because a single serial dispatch queue may be run on any thread from an internally managed thread pool. + * For example: + * + * `2011-10-17 20:32:31.111 AppName[19954:4d07] Message from my_serial_dispatch_queue` + * `2011-10-17 20:32:31.112 AppName[19954:5207] Message from my_serial_dispatch_queue` + * `2011-10-17 20:32:31.113 AppName[19954:2c55] Message from my_serial_dispatch_queue` + * + * This formatter allows you to replace the standard `[box:info]` with the dispatch_queue name. + * For example: + * + * `2011-10-17 20:32:31.111 AppName[img-scaling] Message from my_serial_dispatch_queue` + * `2011-10-17 20:32:31.112 AppName[img-scaling] Message from my_serial_dispatch_queue` + * `2011-10-17 20:32:31.113 AppName[img-scaling] Message from my_serial_dispatch_queue` + * + * If the dispatch_queue doesn't have a set name, then it falls back to the thread name. + * If the current thread doesn't have a set name, then it falls back to the mach_thread_id in hex (like normal). + * + * Note: If manually creating your own background threads (via `NSThread/alloc/init` or `NSThread/detachNeThread`), + * you can use `[[NSThread currentThread] setName:(NSString *)]`. + **/ +@interface DDDispatchQueueLogFormatter : NSObject + +/** + * Standard init method. + * Configure using properties as desired. + **/ +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/** + * Initializer with ability to set the queue mode + * + * @param mode choose between DDDispatchQueueLogFormatterModeShareble and DDDispatchQueueLogFormatterModeNonShareble, depending if the formatter is shared between several loggers or not + */ +- (instancetype)initWithMode:(DDDispatchQueueLogFormatterMode)mode __attribute__((deprecated("DDDispatchQueueLogFormatter is always shareable"))); + +/** + * The minQueueLength restricts the minimum size of the [detail box]. + * If the minQueueLength is set to 0, there is no restriction. + * + * For example, say a dispatch_queue has a label of "diskIO": + * + * If the minQueueLength is 0: [diskIO] + * If the minQueueLength is 4: [diskIO] + * If the minQueueLength is 5: [diskIO] + * If the minQueueLength is 6: [diskIO] + * If the minQueueLength is 7: [diskIO ] + * If the minQueueLength is 8: [diskIO ] + * + * The default minQueueLength is 0 (no minimum, so [detail box] won't be padded). + * + * If you want every [detail box] to have the exact same width, + * set both minQueueLength and maxQueueLength to the same value. + **/ +@property (assign, atomic) NSUInteger minQueueLength; + +/** + * The maxQueueLength restricts the number of characters that will be inside the [detail box]. + * If the maxQueueLength is 0, there is no restriction. + * + * For example, say a dispatch_queue has a label of "diskIO": + * + * If the maxQueueLength is 0: [diskIO] + * If the maxQueueLength is 4: [disk] + * If the maxQueueLength is 5: [diskI] + * If the maxQueueLength is 6: [diskIO] + * If the maxQueueLength is 7: [diskIO] + * If the maxQueueLength is 8: [diskIO] + * + * The default maxQueueLength is 0 (no maximum, so [detail box] won't be truncated). + * + * If you want every [detail box] to have the exact same width, + * set both minQueueLength and maxQueueLength to the same value. + **/ +@property (assign, atomic) NSUInteger maxQueueLength; + +/** + * Sometimes queue labels have long names like "com.apple.main-queue", + * but you'd prefer something shorter like simply "main". + * + * This method allows you to set such preferred replacements. + * The above example is set by default. + * + * To remove/undo a previous replacement, invoke this method with nil for the 'shortLabel' parameter. + **/ +- (nullable NSString *)replacementStringForQueueLabel:(NSString *)longLabel; + +/** + * See the `replacementStringForQueueLabel:` description + */ +- (void)setReplacementString:(nullable NSString *)shortLabel forQueueLabel:(NSString *)longLabel; + +@end + +/** + * Category on `DDDispatchQueueLogFormatter` to make method declarations easier to extend/modify + **/ +@interface DDDispatchQueueLogFormatter (OverridableMethods) + +/** + * Date formatter default configuration + */ +- (void)configureDateFormatter:(NSDateFormatter *)dateFormatter; + +/** + * Formatter method to transfrom from date to string + */ +- (NSString *)stringFromDate:(NSDate *)date; + +/** + * Method to compute the queue thread label + */ +- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage; + +@end + +#pragma mark - DDAtomicCountable + +__attribute__((deprecated("DDAtomicCountable is useless since DDDispatchQueueLogFormatter is always shareable now"))) +@protocol DDAtomicCountable + +- (instancetype)initWithDefaultValue:(int32_t)defaultValue; +- (int32_t)increment; +- (int32_t)decrement; +- (int32_t)value; + +@end + +__attribute__((deprecated("DDAtomicCountable is deprecated"))) +@interface DDAtomicCounter: NSObject +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger+Buffering.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger+Buffering.h new file mode 100644 index 000000000..cbb90f082 --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger+Buffering.h @@ -0,0 +1,27 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DDFileLogger (Buffering) + +- (instancetype)wrapWithBuffer; +- (instancetype)unwrapFromBuffer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger.h b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger.h new file mode 100644 index 000000000..ffd39365c --- /dev/null +++ b/dydx/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger.h @@ -0,0 +1,571 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2024, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import + +@class DDLogFileInfo; + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class provides a logger to write log statements to a file. + **/ + + +// Default configuration and safety/sanity values. +// +// maximumFileSize -> kDDDefaultLogMaxFileSize +// rollingFrequency -> kDDDefaultLogRollingFrequency +// maximumNumberOfLogFiles -> kDDDefaultLogMaxNumLogFiles +// logFilesDiskQuota -> kDDDefaultLogFilesDiskQuota +// +// You should carefully consider the proper configuration values for your application. + +extern unsigned long long const kDDDefaultLogMaxFileSize; +extern NSTimeInterval const kDDDefaultLogRollingFrequency; +extern NSUInteger const kDDDefaultLogMaxNumLogFiles; +extern unsigned long long const kDDDefaultLogFilesDiskQuota; + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +/// The serializer is responsible for turning a log message into binary for writing into a file. +/// It allows storing log messages in a non-text format. +/// The serialier should not be used for filtering or formatting messages! +/// Also, it must be fast! +@protocol DDFileLogMessageSerializer +@required + +/// Returns the binary representation of the message. +/// - Parameter message: The formatted log message to serialize. +// + +/// Returns the binary representation of the message. +/// - Parameters: +/// - string: The string to serialize. Usually, this is the formatted message, but it can also be e.g. a log file header. +/// - message: The message which represents the `string`. This is null, if `string` is e.g. a log file header. +/// - Note: The `message` parameter should not be used for formatting! It should simply be used to extract the necessary metadata for serializing. +- (NSData *)dataForString:(NSString *)string + originatingFromMessage:(nullable DDLogMessage *)message NS_SWIFT_NAME(dataForString(_:originatingFrom:)); + +@end + +/// The (default) plain text message serializer. +@interface DDFileLogPlainTextMessageSerializer : NSObject + +- (instancetype)init; + +@end + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@class DDFileLogger; +/** + * The LogFileManager protocol is designed to allow you to control all aspects of your log files. + * + * The primary purpose of this is to allow you to do something with the log files after they have been rolled. + * Perhaps you want to compress them to save disk space. + * Perhaps you want to upload them to an FTP server. + * Perhaps you want to run some analytics on the file. + * + * A default LogFileManager is, of course, provided. + * The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property. + * + * This protocol provides various methods to fetch the list of log files. + * + * There are two variants: sorted and unsorted. + * If sorting is not necessary, the unsorted variant is obviously faster. + * The sorted variant will return an array sorted by when the log files were created, + * with the most recently created log file at index 0, and the oldest log file at the end of the array. + * + * You can fetch only the log file paths (full path including name), log file names (name only), + * or an array of `DDLogFileInfo` objects. + * The `DDLogFileInfo` class is documented below, and provides a handy wrapper that + * gives you easy access to various file attributes such as the creation date or the file size. + */ +@protocol DDLogFileManager +@required + +// Public properties + +/** + * The maximum number of archived log files to keep on disk. + * For example, if this property is set to 3, + * then the LogFileManager will only keep 3 archived log files (plus the current active log file) on disk. + * Once the active log file is rolled/archived, then the oldest of the existing 3 rolled/archived log files is deleted. + * + * You may optionally disable this option by setting it to zero. + **/ +@property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles; + +/** + * The maximum space that logs can take. On rolling logfile all old log files that exceed logFilesDiskQuota will + * be deleted. + * + * You may optionally disable this option by setting it to zero. + **/ +@property (readwrite, assign, atomic) unsigned long long logFilesDiskQuota; + +// Public methods + +/** + * Returns the logs directory (path) + */ +@property (nonatomic, readonly, copy) NSString *logsDirectory; + +/** + * Returns an array of `NSString` objects, + * each of which is the filePath to an existing log file on disk. + **/ +@property (nonatomic, readonly, strong) NSArray *unsortedLogFilePaths; + +/** + * Returns an array of `NSString` objects, + * each of which is the fileName of an existing log file on disk. + **/ +@property (nonatomic, readonly, strong) NSArray *unsortedLogFileNames; + +/** + * Returns an array of `DDLogFileInfo` objects, + * each representing an existing log file on disk, + * and containing important information about the log file such as it's modification date and size. + **/ +@property (nonatomic, readonly, strong) NSArray *unsortedLogFileInfos; + +/** + * Just like the `unsortedLogFilePaths` method, but sorts the array. + * The items in the array are sorted by creation date. + * The first item in the array will be the most recently created log file. + **/ +@property (nonatomic, readonly, strong) NSArray *sortedLogFilePaths; + +/** + * Just like the `unsortedLogFileNames` method, but sorts the array. + * The items in the array are sorted by creation date. + * The first item in the array will be the most recently created log file. + **/ +@property (nonatomic, readonly, strong) NSArray *sortedLogFileNames; + +/** + * Just like the `unsortedLogFileInfos` method, but sorts the array. + * The items in the array are sorted by creation date. + * The first item in the array will be the most recently created log file. + **/ +@property (nonatomic, readonly, strong) NSArray *sortedLogFileInfos; + +// Private methods (only to be used by DDFileLogger) + +/** + * Generates a new unique log file path, and creates the corresponding log file. + * This method is executed directly on the file logger's internal queue. + * The file has to exist by the time the method returns. + **/ +- (nullable NSString *)createNewLogFileWithError:(NSError **)error; + +@optional + +/// The log message serializer. +@property (nonatomic, readonly, strong) id logMessageSerializer; + +/// Manually perform a cleanup of the log files managed by this manager. +/// This can be called from any queue! +- (BOOL)cleanupLogFilesWithError:(NSError **)error; + +// MARK: Private methods (only to be used by DDFileLogger) + +// MARK: Notifications from DDFileLogger +/// Called when the log file manager was added to a file logger. +/// This should be used to make the manager "active" - like starting internal timers etc. +/// Executed on global queue with default priority. +/// - Parameter fileLogger: The file logger this manager was added to. +/// - Important: The manager **must not** keep a strong reference to `fileLogger` or a retain cycle will be created! +- (void)didAddToFileLogger:(DDFileLogger *)fileLogger; + +/// Called when a log file was archived. Executed on global queue with default priority. +/// @param logFilePath The path to the log file that was archived. +/// @param wasRolled Whether or not the archiving happend after rolling the log file. +- (void)didArchiveLogFile:(NSString *)logFilePath wasRolled:(BOOL)wasRolled NS_SWIFT_NAME(didArchiveLogFile(atPath:wasRolled:)); + +// MARK: Deprecated APIs +/// Creates a new log file ignoring any errors. Deprecated in favor of `-createNewLogFileWithError:`. +/// Will only be called if `-createNewLogFileWithError:` is not implemented. +- (nullable NSString *)createNewLogFile __attribute__((deprecated("Use -createNewLogFileWithError:"))) NS_SWIFT_UNAVAILABLE("Use -createNewLogFileWithError:"); + +/// Called when a log file was archived. Executed on global queue with default priority. +- (void)didArchiveLogFile:(NSString *)logFilePath NS_SWIFT_NAME(didArchiveLogFile(atPath:)) __attribute__((deprecated("Use -didArchiveLogFile:wasRolled:"))); + +/// Called when the roll action was executed and the log was archived. Executed on global queue with default priority. +- (void)didRollAndArchiveLogFile:(NSString *)logFilePath NS_SWIFT_NAME(didRollAndArchiveLogFile(atPath:)) __attribute__((deprecated("Use -didArchiveLogFile:wasRolled:"))); + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Default log file manager. + * + * All log files are placed inside the logsDirectory. + * If a specific logsDirectory isn't specified, the default directory is used. + * On Mac, this is in `~/Library/Logs/`. + * On iPhone, this is in `~/Library/Caches/Logs`. + * + * Log files are named `"