diff --git a/.gitignore b/.gitignore index 5cff804..bef2fd8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -xcuserdata/ -.build/ +Pods/ +Keys.swift +Info.plist +riddles.json +GoogleService-Info.plist .DS_Store -Riddler/App/Config/Config.swift -Riddler/App/Info.plist -Riddler/GoogleService-Info.plist diff --git a/.swiftformat b/.swiftformat deleted file mode 100644 index 7a99919..0000000 --- a/.swiftformat +++ /dev/null @@ -1,24 +0,0 @@ -# Config options - ---swiftversion 5.8 ---indent tab ---smarttabs disabled ---xcodeindentation enabled ---importgrouping testable-bottom ---commas inline ---modifierorder public,private(set),final,override,convenience,lazy,weak ---ifdef no-indent ---header ignore ---lifecycle loadView,viewDidLoad,viewWillAppear,viewDidAppear,viewWillDisappear,viewDidDisappear ---stripunusedargs closure-only ---wraparguments before-first ---wrapparameters before-first ---wrapcollections before-first ---maxwidth 180 ---typeblanklines preserve - ---enable markTypes,organizeDeclarations,blankLineAfterImports,blankLinesBetweenImports - ---disable wrapMultilineStatementBraces,wrapSingleLineComments - ---exclude **/Generated,**/*+Generated.swift diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..e18eec6 --- /dev/null +++ b/Podfile @@ -0,0 +1,21 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '14.0' + +target 'Riddler' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for Riddler + project './Riddler.xcodeproj' + pod 'Google-Mobile-Ads-SDK' + + target 'RiddlerTests' do + inherit! :search_paths + # Pods for testing + end + + target 'RiddlerUITests' do + # Pods for testing + end + +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..cd26337 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,72 @@ +PODS: + - Google-Mobile-Ads-SDK (9.1.0): + - GoogleAppMeasurement (< 9.0, >= 7.0) + - GoogleUserMessagingPlatform (>= 1.1) + - GoogleAppMeasurement (8.13.0): + - GoogleAppMeasurement/AdIdSupport (= 8.13.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.7) + - GoogleUtilities/MethodSwizzler (~> 7.7) + - GoogleUtilities/Network (~> 7.7) + - "GoogleUtilities/NSData+zlib (~> 7.7)" + - nanopb (~> 2.30908.0) + - GoogleAppMeasurement/AdIdSupport (8.13.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 8.13.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.7) + - GoogleUtilities/MethodSwizzler (~> 7.7) + - GoogleUtilities/Network (~> 7.7) + - "GoogleUtilities/NSData+zlib (~> 7.7)" + - nanopb (~> 2.30908.0) + - GoogleAppMeasurement/WithoutAdIdSupport (8.13.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.7) + - GoogleUtilities/MethodSwizzler (~> 7.7) + - GoogleUtilities/Network (~> 7.7) + - "GoogleUtilities/NSData+zlib (~> 7.7)" + - nanopb (~> 2.30908.0) + - GoogleUserMessagingPlatform (2.0.0) + - GoogleUtilities/AppDelegateSwizzler (7.7.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (7.7.0): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.7.0): + - GoogleUtilities/Environment + - GoogleUtilities/MethodSwizzler (7.7.0): + - GoogleUtilities/Logger + - GoogleUtilities/Network (7.7.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (7.7.0)" + - GoogleUtilities/Reachability (7.7.0): + - GoogleUtilities/Logger + - nanopb (2.30908.0): + - nanopb/decode (= 2.30908.0) + - nanopb/encode (= 2.30908.0) + - nanopb/decode (2.30908.0) + - nanopb/encode (2.30908.0) + - PromisesObjC (2.0.0) + +DEPENDENCIES: + - Google-Mobile-Ads-SDK + +SPEC REPOS: + trunk: + - Google-Mobile-Ads-SDK + - GoogleAppMeasurement + - GoogleUserMessagingPlatform + - GoogleUtilities + - nanopb + - PromisesObjC + +SPEC CHECKSUMS: + Google-Mobile-Ads-SDK: d6ebb0e05cc38f4fad82d77a6655552213ed5e38 + GoogleAppMeasurement: 135c2fbcf5038e0f37dbce04fa641a702fc275d1 + GoogleUserMessagingPlatform: ab890ce5f6620f293a21b6bdd82e416a2c73aeca + GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1 + nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 + PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58 + +PODFILE CHECKSUM: 974c5be137c030a2bfaf6a2bab97b681628c1fa5 + +COCOAPODS: 1.11.2 diff --git a/README.md b/README.md index 06040e3..b1a2184 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,29 @@ # Riddler for iOS -Riddler Icon +Riddler Icon Riddler is a riddle game built as a native iOS app in Swift using SwiftUI. It includes 50 challenging riddles with hints for when you get stuck. The game tracks your stats so you can compare your performance against your friends, and see who can answer all 50 riddles the quickest. Riddler was featured in the **'New games we love'** section of the App Store as well as reaching **No. 28** in the **'Word'** game chart. -[![Download on the App Store](https://developer.apple.com/assets/elements/badges/download-on-the-app-store.svg)](https://apps.apple.com/us/app/riddler-can-you-solve-it/id1615311096) +Check it out on the [App Store](https://apps.apple.com/us/app/riddler-can-you-solve-it/id1615311096). -## Before Building -> This respository will not compile out of the box. I have excluded the `Info.plist` and `Config.swift` files as they contain private API keys. +## Note +> This respository will not compile out of the box. Some files are not included due to including confidential information, such as private keys. -In order to build you'll need to use the example configuration files which by running the following command in the root folder: - -```bash -cp Riddler/App/ExampleInfo.plist Riddler/App/Info.plist && cp Riddler/App/Config/ExampleConfig.swift Riddler/App/Config/Config.swift -``` ## Features -- UI built with SwiftUI +- UI built programmatically using SwiftUI - JSON data parsing for persistant user data storage -- Achievements and leaderboards implemented using Apple's GameKit framework -- Privacy consent collection using Google's User Messaging Platform framework -- Interstitial and rewarded ads which use Google's AdMob service -- General usage analytics using PostHog +- Achievements and leaderboards implemented using Apple GameKit +- Privacy consent collection using Google User Messaging Platform +- Interstitial and rewarded ads using AdMob +- Firebase analytics and crashyltics for stability monitoring ## How it looks...

- +

diff --git a/Riddler.xcodeproj/project.pbxproj b/Riddler.xcodeproj/project.pbxproj index eb6d9b7..751e87a 100644 --- a/Riddler.xcodeproj/project.pbxproj +++ b/Riddler.xcodeproj/project.pbxproj @@ -7,156 +7,149 @@ objects = { /* Begin PBXBuildFile section */ - A77063202BB06FF700AB686D /* StartupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A770631F2BB06FF700AB686D /* StartupManager.swift */; }; - A77063232BB0747500AB686D /* UMPConsentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A77063222BB0747500AB686D /* UMPConsentView.swift */; }; - A77063252BB074A000AB686D /* UMPConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A77063242BB074A000AB686D /* UMPConsentViewController.swift */; }; - A77063272BB0781300AB686D /* AdsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A77063262BB0781300AB686D /* AdsManager.swift */; }; - A77063292BB079CB00AB686D /* UMPPrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A77063282BB079CB00AB686D /* UMPPrivacyView.swift */; }; - A770632B2BB079EB00AB686D /* UMPPrivacyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A770632A2BB079EB00AB686D /* UMPPrivacyViewController.swift */; }; - A770632D2BB0813B00AB686D /* RDInterstitialAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = A770632C2BB0813B00AB686D /* RDInterstitialAd.swift */; }; - A770632F2BB0822200AB686D /* InterstitialAdView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A770632E2BB0822200AB686D /* InterstitialAdView.swift */; }; - A77063312BB0824E00AB686D /* InterstitialAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A77063302BB0824E00AB686D /* InterstitialAdViewController.swift */; }; - A77063332BB0E47F00AB686D /* RDRewardedAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = A77063322BB0E47F00AB686D /* RDRewardedAd.swift */; }; - A77063352BB0E6A300AB686D /* RewardedAdView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A77063342BB0E6A300AB686D /* RewardedAdView.swift */; }; - A77063372BB0E6B800AB686D /* RewardedAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A77063362BB0E6B800AB686D /* RewardedAdViewController.swift */; }; - A7878B332B9133A5007D5F7A /* PlayerV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7878B322B9133A5007D5F7A /* PlayerV2.swift */; }; - A7878B352B9135DF007D5F7A /* RiddleStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7878B342B9135DF007D5F7A /* RiddleStats.swift */; }; - A7878B372B914764007D5F7A /* PlayerDataConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7878B362B914764007D5F7A /* PlayerDataConvertible.swift */; }; - A78EC72B2B967ED900DB19C3 /* DebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A78EC72A2B967ED900DB19C3 /* DebugView.swift */; }; - A78EC72D2B9682EF00DB19C3 /* DisplayStat.swift in Sources */ = {isa = PBXBuildFile; fileRef = A78EC72C2B9682EF00DB19C3 /* DisplayStat.swift */; }; - A7B72DC62B8EB9500033B615 /* StorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B72DC52B8EB9500033B615 /* StorageManager.swift */; }; - A7B8D5D82B936E900013E402 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B8D5D72B936E900013E402 /* Array+Extensions.swift */; }; - A7B8D5DA2B93B45B0013E402 /* GameCenterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B8D5D92B93B45B0013E402 /* GameCenterManager.swift */; }; - A7C3DA9D2B8BD1AA0045E2E7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C3DA9C2B8BD1AA0045E2E7 /* AppDelegate.swift */; }; - A7C3DA9F2B8BD2320045E2E7 /* AppSetupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C3DA9E2B8BD2320045E2E7 /* AppSetupManager.swift */; }; - A7C3DAA42B8BD7480045E2E7 /* GKAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C3DAA32B8BD7480045E2E7 /* GKAuthentication.swift */; }; - A7C3DAA62B8BD9A70045E2E7 /* Logger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C3DAA52B8BD9A70045E2E7 /* Logger+Extensions.swift */; }; - A7C3DAA82B8BDA910045E2E7 /* GKAuthenticationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C3DAA72B8BDA910045E2E7 /* GKAuthenticationView.swift */; }; - A7C3DAAA2B8BDAC50045E2E7 /* GKAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C3DAA92B8BDAC50045E2E7 /* GKAuthenticationViewController.swift */; }; - A7C3DAAE2B8BDB6D0045E2E7 /* UIViewController+Child.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C3DAAD2B8BDB6D0045E2E7 /* UIViewController+Child.swift */; }; - A7C3DAB32B8C0B4F0045E2E7 /* StatsMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C3DAB22B8C0B4F0045E2E7 /* StatsMapper.swift */; }; - A7CD09A62BB19E86006F71E2 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CD09A52BB19E86006F71E2 /* Date+Extensions.swift */; }; - A7CD09A82BB19E9B006F71E2 /* TimeInterval+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CD09A72BB19E9B006F71E2 /* TimeInterval+Extensions.swift */; }; - A7CD09AB2BB1B27F006F71E2 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = A7CD09AA2BB1B27F006F71E2 /* PostHog */; }; - A7CD09AE2BB1B2AD006F71E2 /* Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CD09AD2BB1B2AD006F71E2 /* Analytics.swift */; }; - A7CD09B02BB1B4A3006F71E2 /* PostHogAnalyticsConnector.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CD09AF2BB1B4A3006F71E2 /* PostHogAnalyticsConnector.swift */; }; - A7CD09B22BB1B7B0006F71E2 /* LoggingAnalyticsConnector.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CD09B12BB1B7B0006F71E2 /* LoggingAnalyticsConnector.swift */; }; - A7CD09B42BB1B8A8006F71E2 /* Analytics+Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CD09B32BB1B8A8006F71E2 /* Analytics+Events.swift */; }; - A7CD09B62BB1B989006F71E2 /* Analytics+Screens.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CD09B52BB1B989006F71E2 /* Analytics+Screens.swift */; }; - A7CD09B82BB1BA2B006F71E2 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CD09B72BB1BA2B006F71E2 /* View+Extensions.swift */; }; - A7CD09C02BB1E338006F71E2 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CD09BF2BB1E338006F71E2 /* Config.swift */; }; - A7CE6A9C2B8B776B00FA9169 /* GoogleMobileAds in Frameworks */ = {isa = PBXBuildFile; productRef = A7CE6A9B2B8B776B00FA9169 /* GoogleMobileAds */; }; - A7F017662BB24A430059EA89 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A7F017652BB24A430059EA89 /* PrivacyInfo.xcprivacy */; }; - A7F07A2D2B8D31550028269E /* Fonts+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F07A2C2B8D31550028269E /* Fonts+Generated.swift */; }; - A7F07A2F2B8D418A0028269E /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A7F07A2E2B8D418A0028269E /* Colors.xcassets */; }; - A7F07A312B8D42240028269E /* Colors+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F07A302B8D42240028269E /* Colors+Generated.swift */; }; - A7F07A332B8D497E0028269E /* RKButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F07A322B8D497E0028269E /* RKButton.swift */; }; - A7F07A352B8D50DF0028269E /* RKIconButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F07A342B8D50DF0028269E /* RKIconButton.swift */; }; - A7F07A3A2B8D61900028269E /* Pow in Frameworks */ = {isa = PBXBuildFile; productRef = A7F07A392B8D61900028269E /* Pow */; }; - A7F07A3D2B8D637A0028269E /* Vortex in Frameworks */ = {isa = PBXBuildFile; productRef = A7F07A3C2B8D637A0028269E /* Vortex */; }; - F214455927E2694000E8890F /* RKBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = F214455827E2694000E8890F /* RKBackground.swift */; }; - F214456727E4E5B400E8890F /* RKToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = F214456627E4E5B400E8890F /* RKToast.swift */; }; + 381B83B7467859628807A932 /* Pods_Riddler_RiddlerUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DAB57D67E7BF739CF9A576A /* Pods_Riddler_RiddlerUITests.framework */; }; + 8FD4D269227A3463DCB02D79 /* Pods_Riddler.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D31A8880939AE0456CD5898 /* Pods_Riddler.framework */; }; + CCD38160161EEB6A485CA7F3 /* Pods_RiddlerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A52ABE1A28C8B47711BA04C0 /* Pods_RiddlerTests.framework */; }; + DC044FB09302FCD413FDF614 /* Pods_Riddler.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B099384CB453747BB0787FE /* Pods_Riddler.framework */; }; + F214454527E009F100E8890F /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F214454427E009F100E8890F /* Keys.swift */; }; + F214455727E267C500E8890F /* BackButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F214455627E267C500E8890F /* BackButton.swift */; }; + F214455927E2694000E8890F /* Background.swift in Sources */ = {isa = PBXBuildFile; fileRef = F214455827E2694000E8890F /* Background.swift */; }; + F214455B27E276F500E8890F /* IconButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F214455A27E276F500E8890F /* IconButton.swift */; }; + F214455D27E2778600E8890F /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F214455C27E2778600E8890F /* Extensions.swift */; }; + F214455F27E27DCC00E8890F /* TextIconButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F214455E27E27DCC00E8890F /* TextIconButton.swift */; }; + F214456527E2B04800E8890F /* trophy.json in Resources */ = {isa = PBXBuildFile; fileRef = F214456427E2B04800E8890F /* trophy.json */; }; + F214456727E4E5B400E8890F /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = F214456627E4E5B400E8890F /* Toast.swift */; }; F216F61427B40F7D00DA02D0 /* RiddlerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F61327B40F7D00DA02D0 /* RiddlerApp.swift */; }; F216F61827B40F7F00DA02D0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F216F61727B40F7F00DA02D0 /* Assets.xcassets */; }; F216F61B27B40F7F00DA02D0 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F216F61A27B40F7F00DA02D0 /* Preview Assets.xcassets */; }; + F216F62527B40F7F00DA02D0 /* RiddlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F62427B40F7F00DA02D0 /* RiddlerTests.swift */; }; + F216F62F27B40F7F00DA02D0 /* RiddlerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F62E27B40F7F00DA02D0 /* RiddlerUITests.swift */; }; + F216F63127B40F7F00DA02D0 /* RiddlerUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F63027B40F7F00DA02D0 /* RiddlerUITestsLaunchTests.swift */; }; F216F64327B4161400DA02D0 /* Riddle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F64227B4161400DA02D0 /* Riddle.swift */; }; - F216F64527B4162100DA02D0 /* PlayerV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F64427B4162100DA02D0 /* PlayerV1.swift */; }; - F216F64727B4163100DA02D0 /* GameplayManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F64627B4163100DA02D0 /* GameplayManager.swift */; }; + F216F64527B4162100DA02D0 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F64427B4162100DA02D0 /* Player.swift */; }; + F216F64727B4163100DA02D0 /* GameViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F64627B4163100DA02D0 /* GameViewModel.swift */; }; F216F64B27B4166000DA02D0 /* HintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F64A27B4166000DA02D0 /* HintView.swift */; }; F216F64D27B4167100DA02D0 /* RiddleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F64C27B4167100DA02D0 /* RiddleView.swift */; }; F216F64F27B4167E00DA02D0 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F64E27B4167E00DA02D0 /* MenuView.swift */; }; F216F65127B4168800DA02D0 /* CorrectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F65027B4168800DA02D0 /* CorrectView.swift */; }; F216F65327B4168F00DA02D0 /* VictoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F65227B4168F00DA02D0 /* VictoryView.swift */; }; - F216F65527B416B100DA02D0 /* RKButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F65427B416B100DA02D0 /* RKButtonStyle.swift */; }; + F216F65527B416B100DA02D0 /* ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F65427B416B100DA02D0 /* ButtonStyles.swift */; }; + F216F65727B416B900DA02D0 /* TextStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F65627B416B900DA02D0 /* TextStyles.swift */; }; + F216F65927B416C100DA02D0 /* Animations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F65827B416C100DA02D0 /* Animations.swift */; }; + F216F65B27B416CE00DA02D0 /* LottieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F65A27B416CE00DA02D0 /* LottieView.swift */; }; F216F65D27B416E500DA02D0 /* BundleDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F216F65C27B416E500DA02D0 /* BundleDecodable.swift */; }; F21BBAFC27E7E04E001FAA31 /* GameKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F21BBAFB27E7E04E001FAA31 /* GameKit.framework */; }; - F21BBB0127E7EDA8001FAA31 /* GKLeaderboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21BBB0027E7EDA8001FAA31 /* GKLeaderboardView.swift */; }; - F21BBB0327EA457D001FAA31 /* GKAchievementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21BBB0227EA457D001FAA31 /* GKAchievementsView.swift */; }; - F21BBB0527EA59B6001FAA31 /* GKManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21BBB0427EA59B6001FAA31 /* GKManager.swift */; }; + F21BBAFF27E7E972001FAA31 /* AuthenticateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21BBAFE27E7E972001FAA31 /* AuthenticateView.swift */; }; + F21BBB0127E7EDA8001FAA31 /* LeaderboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21BBB0027E7EDA8001FAA31 /* LeaderboardView.swift */; }; + F21BBB0327EA457D001FAA31 /* AchievementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21BBB0227EA457D001FAA31 /* AchievementsView.swift */; }; + F21BBB0527EA59B6001FAA31 /* GameKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21BBB0427EA59B6001FAA31 /* GameKitManager.swift */; }; + F21BBB0727EB5919001FAA31 /* TestVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21BBB0627EB5919001FAA31 /* TestVariables.swift */; }; + F230AF51287E3F6200FF7769 /* Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = F230AF50287E3F6200FF7769 /* Analytics.swift */; }; + F23FDD7927D840CA008C20A3 /* Podfile in Resources */ = {isa = PBXBuildFile; fileRef = F23FDD7827D840CA008C20A3 /* Podfile */; }; F23FDD7B27D842B1008C20A3 /* AppTrackingTransparency.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F23FDD7A27D842B1008C20A3 /* AppTrackingTransparency.framework */; }; - F281F60127D98F5D000790B4 /* RKBottomSheetPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = F281F60027D98F5D000790B4 /* RKBottomSheetPrompt.swift */; }; + F23FDD8227D90133008C20A3 /* InterstitialAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = F23FDD8127D90133008C20A3 /* InterstitialAd.swift */; }; + F23FDD8827D9073A008C20A3 /* RewardedAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = F23FDD8727D9073A008C20A3 /* RewardedAd.swift */; }; + F23FDD8A27D90940008C20A3 /* FullscreenModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F23FDD8927D90940008C20A3 /* FullscreenModifier.swift */; }; + F274B5D127DD089600D620CA /* AdFailedDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = F274B5D027DD089600D620CA /* AdFailedDialog.swift */; }; + F281F60127D98F5D000790B4 /* BasicDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = F281F60027D98F5D000790B4 /* BasicDialog.swift */; }; + F281F60327DA7066000790B4 /* ConsentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F281F60227DA7066000790B4 /* ConsentManager.swift */; }; + F281F60527DBA27C000790B4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F281F60427DBA27C000790B4 /* GoogleService-Info.plist */; }; + F281F60827DBA40F000790B4 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = F281F60727DBA40F000790B4 /* FirebaseAnalytics */; }; + F281F60A27DBA40F000790B4 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = F281F60927DBA40F000790B4 /* FirebaseCrashlytics */; }; F29A8A2F27B460AE00EEF7A3 /* Abel-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F29A8A2D27B460AE00EEF7A3 /* Abel-Regular.ttf */; }; F29A8A3027B460AE00EEF7A3 /* Teko-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F29A8A2E27B460AE00EEF7A3 /* Teko-Regular.ttf */; }; F29A8A3327B48B0000EEF7A3 /* riddles.json in Resources */ = {isa = PBXBuildFile; fileRef = F29A8A3227B48B0000EEF7A3 /* riddles.json */; }; + F29A8A3627B6F07400EEF7A3 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = F29A8A3527B6F07400EEF7A3 /* Lottie */; }; + F29A8A3827B6F40B00EEF7A3 /* confetti.json in Resources */ = {isa = PBXBuildFile; fileRef = F29A8A3727B6F40B00EEF7A3 /* confetti.json */; }; F29A8A3A27BDBD2000EEF7A3 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29A8A3927BDBD2000EEF7A3 /* SettingsView.swift */; }; F2A398E727E60E4B002ADCF3 /* TimeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2A398E627E60E4B002ADCF3 /* TimeFormatter.swift */; }; F2BBD45A27DEAB5600B23075 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BBD45927DEAB5600B23075 /* StatsView.swift */; }; + F2BBD45C27DFFE7200B23075 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BBD45B27DFFE7200B23075 /* Stats.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + F216F62127B40F7F00DA02D0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F216F60827B40F7D00DA02D0 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F216F60F27B40F7D00DA02D0; + remoteInfo = Riddler; + }; + F216F62B27B40F7F00DA02D0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F216F60827B40F7D00DA02D0 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F216F60F27B40F7D00DA02D0; + remoteInfo = Riddler; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ - A770631F2BB06FF700AB686D /* StartupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupManager.swift; sourceTree = ""; }; - A77063222BB0747500AB686D /* UMPConsentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UMPConsentView.swift; sourceTree = ""; }; - A77063242BB074A000AB686D /* UMPConsentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UMPConsentViewController.swift; sourceTree = ""; }; - A77063262BB0781300AB686D /* AdsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsManager.swift; sourceTree = ""; }; - A77063282BB079CB00AB686D /* UMPPrivacyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UMPPrivacyView.swift; sourceTree = ""; }; - A770632A2BB079EB00AB686D /* UMPPrivacyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UMPPrivacyViewController.swift; sourceTree = ""; }; - A770632C2BB0813B00AB686D /* RDInterstitialAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RDInterstitialAd.swift; sourceTree = ""; }; - A770632E2BB0822200AB686D /* InterstitialAdView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterstitialAdView.swift; sourceTree = ""; }; - A77063302BB0824E00AB686D /* InterstitialAdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterstitialAdViewController.swift; sourceTree = ""; }; - A77063322BB0E47F00AB686D /* RDRewardedAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RDRewardedAd.swift; sourceTree = ""; }; - A77063342BB0E6A300AB686D /* RewardedAdView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RewardedAdView.swift; sourceTree = ""; }; - A77063362BB0E6B800AB686D /* RewardedAdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RewardedAdViewController.swift; sourceTree = ""; }; - A7878B322B9133A5007D5F7A /* PlayerV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerV2.swift; sourceTree = ""; }; - A7878B342B9135DF007D5F7A /* RiddleStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiddleStats.swift; sourceTree = ""; }; - A7878B362B914764007D5F7A /* PlayerDataConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerDataConvertible.swift; sourceTree = ""; }; - A78EC72A2B967ED900DB19C3 /* DebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugView.swift; sourceTree = ""; }; - A78EC72C2B9682EF00DB19C3 /* DisplayStat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayStat.swift; sourceTree = ""; }; - A7B72DC52B8EB9500033B615 /* StorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageManager.swift; sourceTree = ""; }; - A7B8D5D72B936E900013E402 /* Array+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extensions.swift"; sourceTree = ""; }; - A7B8D5D92B93B45B0013E402 /* GameCenterManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameCenterManager.swift; sourceTree = ""; }; - A7C3DA792B8B7ACE0045E2E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A7C3DA952B8B9D180045E2E7 /* Riddler.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Riddler.entitlements; sourceTree = ""; }; - A7C3DA9C2B8BD1AA0045E2E7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - A7C3DA9E2B8BD2320045E2E7 /* AppSetupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSetupManager.swift; sourceTree = ""; }; - A7C3DAA32B8BD7480045E2E7 /* GKAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GKAuthentication.swift; sourceTree = ""; }; - A7C3DAA52B8BD9A70045E2E7 /* Logger+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Extensions.swift"; sourceTree = ""; }; - A7C3DAA72B8BDA910045E2E7 /* GKAuthenticationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GKAuthenticationView.swift; sourceTree = ""; }; - A7C3DAA92B8BDAC50045E2E7 /* GKAuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GKAuthenticationViewController.swift; sourceTree = ""; }; - A7C3DAAD2B8BDB6D0045E2E7 /* UIViewController+Child.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Child.swift"; sourceTree = ""; }; - A7C3DAB22B8C0B4F0045E2E7 /* StatsMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsMapper.swift; sourceTree = ""; }; - A7CD09A52BB19E86006F71E2 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; - A7CD09A72BB19E9B006F71E2 /* TimeInterval+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Extensions.swift"; sourceTree = ""; }; - A7CD09AD2BB1B2AD006F71E2 /* Analytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Analytics.swift; sourceTree = ""; }; - A7CD09AF2BB1B4A3006F71E2 /* PostHogAnalyticsConnector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalyticsConnector.swift; sourceTree = ""; }; - A7CD09B12BB1B7B0006F71E2 /* LoggingAnalyticsConnector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingAnalyticsConnector.swift; sourceTree = ""; }; - A7CD09B32BB1B8A8006F71E2 /* Analytics+Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+Events.swift"; sourceTree = ""; }; - A7CD09B52BB1B989006F71E2 /* Analytics+Screens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+Screens.swift"; sourceTree = ""; }; - A7CD09B72BB1BA2B006F71E2 /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; - A7CD09BF2BB1E338006F71E2 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; - A7F017652BB24A430059EA89 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; - A7F07A2C2B8D31550028269E /* Fonts+Generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Fonts+Generated.swift"; sourceTree = ""; }; - A7F07A2E2B8D418A0028269E /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; - A7F07A302B8D42240028269E /* Colors+Generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Colors+Generated.swift"; sourceTree = ""; }; - A7F07A322B8D497E0028269E /* RKButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RKButton.swift; sourceTree = ""; }; - A7F07A342B8D50DF0028269E /* RKIconButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RKIconButton.swift; sourceTree = ""; }; - F214455827E2694000E8890F /* RKBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RKBackground.swift; sourceTree = ""; }; - F214456627E4E5B400E8890F /* RKToast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RKToast.swift; sourceTree = ""; }; + 006E382FF0D494352903621A /* Pods-RiddlerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiddlerTests.release.xcconfig"; path = "Target Support Files/Pods-RiddlerTests/Pods-RiddlerTests.release.xcconfig"; sourceTree = ""; }; + 0A0C4E21975D90F60862142D /* Pods-Riddler-RiddlerUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Riddler-RiddlerUITests.debug.xcconfig"; path = "Target Support Files/Pods-Riddler-RiddlerUITests/Pods-Riddler-RiddlerUITests.debug.xcconfig"; sourceTree = ""; }; + 5B099384CB453747BB0787FE /* Pods_Riddler.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Riddler.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5B3BA99AC2F43B3FE22A1713 /* Pods-Riddler.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Riddler.release.xcconfig"; path = "Target Support Files/Pods-Riddler/Pods-Riddler.release.xcconfig"; sourceTree = ""; }; + 5D31A8880939AE0456CD5898 /* Pods_Riddler.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_Riddler.framework; path = "../../../Library/Developer/Xcode/DerivedData/Riddler-bfrkrrewyoihvvhkyfksgqozdhbp/Build/Products/Debug-iphoneos/Pods_Riddler.framework"; sourceTree = SOURCE_ROOT; }; + 5DAB57D67E7BF739CF9A576A /* Pods_Riddler_RiddlerUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Riddler_RiddlerUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 699F35ED9EDD5BF816B60AE0 /* Pods-RiddlerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiddlerTests.debug.xcconfig"; path = "Target Support Files/Pods-RiddlerTests/Pods-RiddlerTests.debug.xcconfig"; sourceTree = ""; }; + A32E89ED8C85BDC56279202B /* Pods-Riddler-RiddlerUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Riddler-RiddlerUITests.release.xcconfig"; path = "Target Support Files/Pods-Riddler-RiddlerUITests/Pods-Riddler-RiddlerUITests.release.xcconfig"; sourceTree = ""; }; + A52ABE1A28C8B47711BA04C0 /* Pods_RiddlerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiddlerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F214454427E009F100E8890F /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = ""; }; + F214455627E267C500E8890F /* BackButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackButton.swift; sourceTree = ""; }; + F214455827E2694000E8890F /* Background.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Background.swift; sourceTree = ""; }; + F214455A27E276F500E8890F /* IconButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconButton.swift; sourceTree = ""; }; + F214455C27E2778600E8890F /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + F214455E27E27DCC00E8890F /* TextIconButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextIconButton.swift; sourceTree = ""; }; + F214456427E2B04800E8890F /* trophy.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = trophy.json; sourceTree = ""; }; + F214456627E4E5B400E8890F /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; F216F61027B40F7D00DA02D0 /* Riddler.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Riddler.app; sourceTree = BUILT_PRODUCTS_DIR; }; F216F61327B40F7D00DA02D0 /* RiddlerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiddlerApp.swift; sourceTree = ""; }; F216F61727B40F7F00DA02D0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; F216F61A27B40F7F00DA02D0 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + F216F62027B40F7F00DA02D0 /* RiddlerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RiddlerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + F216F62427B40F7F00DA02D0 /* RiddlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiddlerTests.swift; sourceTree = ""; }; + F216F62A27B40F7F00DA02D0 /* RiddlerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RiddlerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + F216F62E27B40F7F00DA02D0 /* RiddlerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiddlerUITests.swift; sourceTree = ""; }; + F216F63027B40F7F00DA02D0 /* RiddlerUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiddlerUITestsLaunchTests.swift; sourceTree = ""; }; F216F64227B4161400DA02D0 /* Riddle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Riddle.swift; sourceTree = ""; }; - F216F64427B4162100DA02D0 /* PlayerV1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerV1.swift; sourceTree = ""; }; - F216F64627B4163100DA02D0 /* GameplayManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameplayManager.swift; sourceTree = ""; }; + F216F64427B4162100DA02D0 /* Player.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = ""; }; + F216F64627B4163100DA02D0 /* GameViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameViewModel.swift; sourceTree = ""; }; F216F64A27B4166000DA02D0 /* HintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HintView.swift; sourceTree = ""; }; F216F64C27B4167100DA02D0 /* RiddleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiddleView.swift; sourceTree = ""; }; F216F64E27B4167E00DA02D0 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = ""; }; F216F65027B4168800DA02D0 /* CorrectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CorrectView.swift; sourceTree = ""; }; F216F65227B4168F00DA02D0 /* VictoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VictoryView.swift; sourceTree = ""; }; - F216F65427B416B100DA02D0 /* RKButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RKButtonStyle.swift; sourceTree = ""; }; + F216F65427B416B100DA02D0 /* ButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStyles.swift; sourceTree = ""; }; + F216F65627B416B900DA02D0 /* TextStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextStyles.swift; sourceTree = ""; }; + F216F65827B416C100DA02D0 /* Animations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Animations.swift; sourceTree = ""; }; + F216F65A27B416CE00DA02D0 /* LottieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieView.swift; sourceTree = ""; }; F216F65C27B416E500DA02D0 /* BundleDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleDecodable.swift; sourceTree = ""; }; F21BBAFB27E7E04E001FAA31 /* GameKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameKit.framework; path = System/Library/Frameworks/GameKit.framework; sourceTree = SDKROOT; }; - F21BBB0027E7EDA8001FAA31 /* GKLeaderboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GKLeaderboardView.swift; sourceTree = ""; }; - F21BBB0227EA457D001FAA31 /* GKAchievementsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GKAchievementsView.swift; sourceTree = ""; }; - F21BBB0427EA59B6001FAA31 /* GKManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GKManager.swift; sourceTree = ""; }; + F21BBAFE27E7E972001FAA31 /* AuthenticateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticateView.swift; sourceTree = ""; }; + F21BBB0027E7EDA8001FAA31 /* LeaderboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaderboardView.swift; sourceTree = ""; }; + F21BBB0227EA457D001FAA31 /* AchievementsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementsView.swift; sourceTree = ""; }; + F21BBB0427EA59B6001FAA31 /* GameKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameKitManager.swift; sourceTree = ""; }; + F21BBB0627EB5919001FAA31 /* TestVariables.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestVariables.swift; sourceTree = ""; }; + F230AF50287E3F6200FF7769 /* Analytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Analytics.swift; sourceTree = ""; }; + F23FDD7827D840CA008C20A3 /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; F23FDD7A27D842B1008C20A3 /* AppTrackingTransparency.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppTrackingTransparency.framework; path = System/Library/Frameworks/AppTrackingTransparency.framework; sourceTree = SDKROOT; }; - F281F60027D98F5D000790B4 /* RKBottomSheetPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RKBottomSheetPrompt.swift; sourceTree = ""; }; + F23FDD8127D90133008C20A3 /* InterstitialAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterstitialAd.swift; sourceTree = ""; }; + F23FDD8727D9073A008C20A3 /* RewardedAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RewardedAd.swift; sourceTree = ""; }; + F23FDD8927D90940008C20A3 /* FullscreenModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenModifier.swift; sourceTree = ""; }; + F259BF981E79D62D50B11517 /* Pods-Riddler.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Riddler.debug.xcconfig"; path = "Target Support Files/Pods-Riddler/Pods-Riddler.debug.xcconfig"; sourceTree = ""; }; + F274B5D027DD089600D620CA /* AdFailedDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdFailedDialog.swift; sourceTree = ""; }; + F281F60027D98F5D000790B4 /* BasicDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicDialog.swift; sourceTree = ""; }; + F281F60227DA7066000790B4 /* ConsentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentManager.swift; sourceTree = ""; }; + F281F60427DBA27C000790B4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; F29A8A2D27B460AE00EEF7A3 /* Abel-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Abel-Regular.ttf"; sourceTree = ""; }; F29A8A2E27B460AE00EEF7A3 /* Teko-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Teko-Regular.ttf"; sourceTree = ""; }; + F29A8A3127B4629800EEF7A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F29A8A3227B48B0000EEF7A3 /* riddles.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = riddles.json; sourceTree = ""; }; + F29A8A3727B6F40B00EEF7A3 /* confetti.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = confetti.json; sourceTree = ""; }; F29A8A3927BDBD2000EEF7A3 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; F2A398E627E60E4B002ADCF3 /* TimeFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeFormatter.swift; sourceTree = ""; }; F2BBD45927DEAB5600B23075 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = ""; }; + F2BBD45B27DFFE7200B23075 /* Stats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stats.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -164,105 +157,88 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A7F07A3A2B8D61900028269E /* Pow in Frameworks */, F23FDD7B27D842B1008C20A3 /* AppTrackingTransparency.framework in Frameworks */, - A7F07A3D2B8D637A0028269E /* Vortex in Frameworks */, - A7CD09AB2BB1B27F006F71E2 /* PostHog in Frameworks */, + F29A8A3627B6F07400EEF7A3 /* Lottie in Frameworks */, F21BBAFC27E7E04E001FAA31 /* GameKit.framework in Frameworks */, - A7CE6A9C2B8B776B00FA9169 /* GoogleMobileAds in Frameworks */, + 8FD4D269227A3463DCB02D79 /* Pods_Riddler.framework in Frameworks */, + F281F60A27DBA40F000790B4 /* FirebaseCrashlytics in Frameworks */, + F281F60827DBA40F000790B4 /* FirebaseAnalytics in Frameworks */, + DC044FB09302FCD413FDF614 /* Pods_Riddler.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - A77063212BB0744B00AB686D /* Ads */ = { - isa = PBXGroup; - children = ( - A77063222BB0747500AB686D /* UMPConsentView.swift */, - A77063242BB074A000AB686D /* UMPConsentViewController.swift */, - A77063262BB0781300AB686D /* AdsManager.swift */, - A77063282BB079CB00AB686D /* UMPPrivacyView.swift */, - A770632A2BB079EB00AB686D /* UMPPrivacyViewController.swift */, - A770632C2BB0813B00AB686D /* RDInterstitialAd.swift */, - A770632E2BB0822200AB686D /* InterstitialAdView.swift */, - A77063302BB0824E00AB686D /* InterstitialAdViewController.swift */, - A77063322BB0E47F00AB686D /* RDRewardedAd.swift */, - A77063342BB0E6A300AB686D /* RewardedAdView.swift */, - A77063362BB0E6B800AB686D /* RewardedAdViewController.swift */, - ); - path = Ads; - sourceTree = ""; - }; - A7C3DAA22B8BD7310045E2E7 /* GameKit */ = { - isa = PBXGroup; - children = ( - F21BBB0027E7EDA8001FAA31 /* GKLeaderboardView.swift */, - F21BBB0227EA457D001FAA31 /* GKAchievementsView.swift */, - F21BBB0427EA59B6001FAA31 /* GKManager.swift */, - A7C3DAA32B8BD7480045E2E7 /* GKAuthentication.swift */, - A7C3DAA72B8BDA910045E2E7 /* GKAuthenticationView.swift */, - A7C3DAA92B8BDAC50045E2E7 /* GKAuthenticationViewController.swift */, - A7C3DAAD2B8BDB6D0045E2E7 /* UIViewController+Child.swift */, + F216F61D27B40F7F00DA02D0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CCD38160161EEB6A485CA7F3 /* Pods_RiddlerTests.framework in Frameworks */, ); - path = GameKit; - sourceTree = ""; + runOnlyForDeploymentPostprocessing = 0; }; - A7C3DAB12B8C0B390045E2E7 /* Gameplay */ = { - isa = PBXGroup; - children = ( - F216F64627B4163100DA02D0 /* GameplayManager.swift */, - A7C3DAB22B8C0B4F0045E2E7 /* StatsMapper.swift */, - A7B72DC52B8EB9500033B615 /* StorageManager.swift */, - A7B8D5D92B93B45B0013E402 /* GameCenterManager.swift */, - A770631F2BB06FF700AB686D /* StartupManager.swift */, + F216F62727B40F7F00DA02D0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 381B83B7467859628807A932 /* Pods_Riddler_RiddlerUITests.framework in Frameworks */, ); - path = Gameplay; - sourceTree = ""; + runOnlyForDeploymentPostprocessing = 0; }; - A7CD09AC2BB1B28D006F71E2 /* Analytics */ = { +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 6F8D80E1D205462D74465C06 /* Pods */ = { isa = PBXGroup; children = ( - A7CD09AD2BB1B2AD006F71E2 /* Analytics.swift */, - A7CD09AF2BB1B4A3006F71E2 /* PostHogAnalyticsConnector.swift */, - A7CD09B12BB1B7B0006F71E2 /* LoggingAnalyticsConnector.swift */, - A7CD09B32BB1B8A8006F71E2 /* Analytics+Events.swift */, - A7CD09B52BB1B989006F71E2 /* Analytics+Screens.swift */, + F259BF981E79D62D50B11517 /* Pods-Riddler.debug.xcconfig */, + 5B3BA99AC2F43B3FE22A1713 /* Pods-Riddler.release.xcconfig */, + 0A0C4E21975D90F60862142D /* Pods-Riddler-RiddlerUITests.debug.xcconfig */, + A32E89ED8C85BDC56279202B /* Pods-Riddler-RiddlerUITests.release.xcconfig */, + 699F35ED9EDD5BF816B60AE0 /* Pods-RiddlerTests.debug.xcconfig */, + 006E382FF0D494352903621A /* Pods-RiddlerTests.release.xcconfig */, ); - path = Analytics; + path = Pods; sourceTree = ""; }; - A7CD09BA2BB1E1D9006F71E2 /* Config */ = { + B35DF931D4893002C1A30F22 /* Frameworks */ = { isa = PBXGroup; children = ( - A7CD09BF2BB1E338006F71E2 /* Config.swift */, + F21BBAFB27E7E04E001FAA31 /* GameKit.framework */, + F23FDD7A27D842B1008C20A3 /* AppTrackingTransparency.framework */, + 5D31A8880939AE0456CD5898 /* Pods_Riddler.framework */, + 5DAB57D67E7BF739CF9A576A /* Pods_Riddler_RiddlerUITests.framework */, + A52ABE1A28C8B47711BA04C0 /* Pods_RiddlerTests.framework */, + 5B099384CB453747BB0787FE /* Pods_Riddler.framework */, ); - path = Config; + name = Frameworks; sourceTree = ""; }; - A7F07A2B2B8D313D0028269E /* Generated */ = { + F214455427E2670500E8890F /* Dialogs */ = { isa = PBXGroup; children = ( - A7F07A302B8D42240028269E /* Colors+Generated.swift */, - A7F07A2C2B8D31550028269E /* Fonts+Generated.swift */, + F274B5D027DD089600D620CA /* AdFailedDialog.swift */, + F281F60027D98F5D000790B4 /* BasicDialog.swift */, ); - path = Generated; + path = Dialogs; sourceTree = ""; }; - B35DF931D4893002C1A30F22 /* Frameworks */ = { + F214455527E267A500E8890F /* Styles */ = { isa = PBXGroup; children = ( - F21BBAFB27E7E04E001FAA31 /* GameKit.framework */, - F23FDD7A27D842B1008C20A3 /* AppTrackingTransparency.framework */, + F216F65627B416B900DA02D0 /* TextStyles.swift */, + F216F65427B416B100DA02D0 /* ButtonStyles.swift */, + F216F65827B416C100DA02D0 /* Animations.swift */, ); - name = Frameworks; + path = Styles; sourceTree = ""; }; F216F60727B40F7D00DA02D0 = { isa = PBXGroup; children = ( F216F61227B40F7D00DA02D0 /* Riddler */, + F216F62327B40F7F00DA02D0 /* RiddlerTests */, + F216F62D27B40F7F00DA02D0 /* RiddlerUITests */, F216F61127B40F7D00DA02D0 /* Products */, + 6F8D80E1D205462D74465C06 /* Pods */, B35DF931D4893002C1A30F22 /* Frameworks */, ); sourceTree = ""; @@ -271,6 +247,8 @@ isa = PBXGroup; children = ( F216F61027B40F7D00DA02D0 /* Riddler.app */, + F216F62027B40F7F00DA02D0 /* RiddlerTests.xctest */, + F216F62A27B40F7F00DA02D0 /* RiddlerUITests.xctest */, ); name = Products; sourceTree = ""; @@ -278,15 +256,10 @@ F216F61227B40F7D00DA02D0 /* Riddler */ = { isa = PBXGroup; children = ( + F281F60427DBA27C000790B4 /* GoogleService-Info.plist */, F216F63D27B414CD00DA02D0 /* App */, - A77063212BB0744B00AB686D /* Ads */, - A7CD09AC2BB1B28D006F71E2 /* Analytics */, - A7C3DAA22B8BD7310045E2E7 /* GameKit */, - A7C3DAB12B8C0B390045E2E7 /* Gameplay */, - A7F07A2B2B8D313D0028269E /* Generated */, F216F64127B414F000DA02D0 /* Models */, F216F63F27B414DF00DA02D0 /* Resources */, - F216F64927B4164500DA02D0 /* Screens */, F216F64027B414E900DA02D0 /* UI */, F216F63E27B414D400DA02D0 /* Utilities */, ); @@ -301,16 +274,29 @@ path = "Preview Content"; sourceTree = ""; }; + F216F62327B40F7F00DA02D0 /* RiddlerTests */ = { + isa = PBXGroup; + children = ( + F216F62427B40F7F00DA02D0 /* RiddlerTests.swift */, + ); + path = RiddlerTests; + sourceTree = ""; + }; + F216F62D27B40F7F00DA02D0 /* RiddlerUITests */ = { + isa = PBXGroup; + children = ( + F216F62E27B40F7F00DA02D0 /* RiddlerUITests.swift */, + F216F63027B40F7F00DA02D0 /* RiddlerUITestsLaunchTests.swift */, + ); + path = RiddlerUITests; + sourceTree = ""; + }; F216F63D27B414CD00DA02D0 /* App */ = { isa = PBXGroup; children = ( - A7CD09BA2BB1E1D9006F71E2 /* Config */, - A7C3DA952B8B9D180045E2E7 /* Riddler.entitlements */, - A7C3DA9C2B8BD1AA0045E2E7 /* AppDelegate.swift */, - A7C3DA9E2B8BD2320045E2E7 /* AppSetupManager.swift */, + F29A8A3127B4629800EEF7A3 /* Info.plist */, F216F61327B40F7D00DA02D0 /* RiddlerApp.swift */, - A7C3DA792B8B7ACE0045E2E7 /* Info.plist */, - A7F017652BB24A430059EA89 /* PrivacyInfo.xcprivacy */, + F23FDD7827D840CA008C20A3 /* Podfile */, ); path = App; sourceTree = ""; @@ -320,11 +306,8 @@ children = ( F216F65C27B416E500DA02D0 /* BundleDecodable.swift */, F2A398E627E60E4B002ADCF3 /* TimeFormatter.swift */, - A7C3DAA52B8BD9A70045E2E7 /* Logger+Extensions.swift */, - A7B8D5D72B936E900013E402 /* Array+Extensions.swift */, - A7CD09A52BB19E86006F71E2 /* Date+Extensions.swift */, - A7CD09A72BB19E9B006F71E2 /* TimeInterval+Extensions.swift */, - A7CD09B72BB1BA2B006F71E2 /* View+Extensions.swift */, + F21BBB0627EB5919001FAA31 /* TestVariables.swift */, + F230AF50287E3F6200FF7769 /* Analytics.swift */, ); path = Utilities; sourceTree = ""; @@ -333,8 +316,8 @@ isa = PBXGroup; children = ( F216F61727B40F7F00DA02D0 /* Assets.xcassets */, - A7F07A2E2B8D418A0028269E /* Colors.xcassets */, F216F65F27B4170300DA02D0 /* Fonts */, + F216F65E27B416FC00DA02D0 /* Lottie */, F216F61927B40F7F00DA02D0 /* Preview Content */, F29A8A3227B48B0000EEF7A3 /* riddles.json */, ); @@ -344,12 +327,12 @@ F216F64027B414E900DA02D0 /* UI */ = { isa = PBXGroup; children = ( - F214455827E2694000E8890F /* RKBackground.swift */, - F214456627E4E5B400E8890F /* RKToast.swift */, - A7F07A322B8D497E0028269E /* RKButton.swift */, - A7F07A342B8D50DF0028269E /* RKIconButton.swift */, - F216F65427B416B100DA02D0 /* RKButtonStyle.swift */, - F281F60027D98F5D000790B4 /* RKBottomSheetPrompt.swift */, + F23FDD8027D90121008C20A3 /* Ads */, + F216F64827B4163E00DA02D0 /* Components */, + F214455427E2670500E8890F /* Dialogs */, + F21BBAFD27E7E947001FAA31 /* GameKit */, + F216F64927B4164500DA02D0 /* Screens */, + F214455527E267A500E8890F /* Styles */, ); path = UI; sourceTree = ""; @@ -357,31 +340,51 @@ F216F64127B414F000DA02D0 /* Models */ = { isa = PBXGroup; children = ( - A7878B362B914764007D5F7A /* PlayerDataConvertible.swift */, - F216F64427B4162100DA02D0 /* PlayerV1.swift */, - A7878B322B9133A5007D5F7A /* PlayerV2.swift */, - A7878B342B9135DF007D5F7A /* RiddleStats.swift */, + F216F64427B4162100DA02D0 /* Player.swift */, + F216F64627B4163100DA02D0 /* GameViewModel.swift */, F216F64227B4161400DA02D0 /* Riddle.swift */, - A78EC72C2B9682EF00DB19C3 /* DisplayStat.swift */, + F2BBD45B27DFFE7200B23075 /* Stats.swift */, ); path = Models; sourceTree = ""; }; + F216F64827B4163E00DA02D0 /* Components */ = { + isa = PBXGroup; + children = ( + F216F65A27B416CE00DA02D0 /* LottieView.swift */, + F214455627E267C500E8890F /* BackButton.swift */, + F214455827E2694000E8890F /* Background.swift */, + F214455A27E276F500E8890F /* IconButton.swift */, + F214455C27E2778600E8890F /* Extensions.swift */, + F214455E27E27DCC00E8890F /* TextIconButton.swift */, + F214456627E4E5B400E8890F /* Toast.swift */, + ); + path = Components; + sourceTree = ""; + }; F216F64927B4164500DA02D0 /* Screens */ = { isa = PBXGroup; children = ( - F216F64E27B4167E00DA02D0 /* MenuView.swift */, - F216F64C27B4167100DA02D0 /* RiddleView.swift */, F216F64A27B4166000DA02D0 /* HintView.swift */, + F216F64C27B4167100DA02D0 /* RiddleView.swift */, + F216F64E27B4167E00DA02D0 /* MenuView.swift */, F216F65027B4168800DA02D0 /* CorrectView.swift */, F216F65227B4168F00DA02D0 /* VictoryView.swift */, F29A8A3927BDBD2000EEF7A3 /* SettingsView.swift */, F2BBD45927DEAB5600B23075 /* StatsView.swift */, - A78EC72A2B967ED900DB19C3 /* DebugView.swift */, ); path = Screens; sourceTree = ""; }; + F216F65E27B416FC00DA02D0 /* Lottie */ = { + isa = PBXGroup; + children = ( + F214456427E2B04800E8890F /* trophy.json */, + F29A8A3727B6F40B00EEF7A3 /* confetti.json */, + ); + path = Lottie; + sourceTree = ""; + }; F216F65F27B4170300DA02D0 /* Fonts */ = { isa = PBXGroup; children = ( @@ -391,6 +394,29 @@ path = Fonts; sourceTree = ""; }; + F21BBAFD27E7E947001FAA31 /* GameKit */ = { + isa = PBXGroup; + children = ( + F21BBAFE27E7E972001FAA31 /* AuthenticateView.swift */, + F21BBB0027E7EDA8001FAA31 /* LeaderboardView.swift */, + F21BBB0227EA457D001FAA31 /* AchievementsView.swift */, + F21BBB0427EA59B6001FAA31 /* GameKitManager.swift */, + ); + path = GameKit; + sourceTree = ""; + }; + F23FDD8027D90121008C20A3 /* Ads */ = { + isa = PBXGroup; + children = ( + F23FDD8127D90133008C20A3 /* InterstitialAd.swift */, + F23FDD8727D9073A008C20A3 /* RewardedAd.swift */, + F23FDD8927D90940008C20A3 /* FullscreenModifier.swift */, + F281F60227DA7066000790B4 /* ConsentManager.swift */, + F214454427E009F100E8890F /* Keys.swift */, + ); + path = Ads; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -398,9 +424,12 @@ isa = PBXNativeTarget; buildConfigurationList = F216F63427B40F7F00DA02D0 /* Build configuration list for PBXNativeTarget "Riddler" */; buildPhases = ( + 3A79C29EEDBAB4A8A5C625A6 /* [CP] Check Pods Manifest.lock */, F216F60C27B40F7D00DA02D0 /* Sources */, F216F60D27B40F7D00DA02D0 /* Frameworks */, F216F60E27B40F7D00DA02D0 /* Resources */, + 2C784A00B11D9636F7CA75F1 /* [CP] Embed Pods Frameworks */, + F281F60C27DBA657000790B4 /* ShellScript */, ); buildRules = ( ); @@ -408,15 +437,53 @@ ); name = Riddler; packageProductDependencies = ( - A7CE6A9B2B8B776B00FA9169 /* GoogleMobileAds */, - A7F07A392B8D61900028269E /* Pow */, - A7F07A3C2B8D637A0028269E /* Vortex */, - A7CD09AA2BB1B27F006F71E2 /* PostHog */, + F29A8A3527B6F07400EEF7A3 /* Lottie */, + F281F60727DBA40F000790B4 /* FirebaseAnalytics */, + F281F60927DBA40F000790B4 /* FirebaseCrashlytics */, ); productName = Riddler; productReference = F216F61027B40F7D00DA02D0 /* Riddler.app */; productType = "com.apple.product-type.application"; }; + F216F61F27B40F7F00DA02D0 /* RiddlerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = F216F63727B40F7F00DA02D0 /* Build configuration list for PBXNativeTarget "RiddlerTests" */; + buildPhases = ( + 3F2238B8C692A170CD01EA87 /* [CP] Check Pods Manifest.lock */, + F216F61C27B40F7F00DA02D0 /* Sources */, + F216F61D27B40F7F00DA02D0 /* Frameworks */, + F216F61E27B40F7F00DA02D0 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + F216F62227B40F7F00DA02D0 /* PBXTargetDependency */, + ); + name = RiddlerTests; + productName = RiddlerTests; + productReference = F216F62027B40F7F00DA02D0 /* RiddlerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + F216F62927B40F7F00DA02D0 /* RiddlerUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = F216F63A27B40F7F00DA02D0 /* Build configuration list for PBXNativeTarget "RiddlerUITests" */; + buildPhases = ( + 43C0F016E7F52B103328395D /* [CP] Check Pods Manifest.lock */, + F216F62627B40F7F00DA02D0 /* Sources */, + F216F62727B40F7F00DA02D0 /* Frameworks */, + F216F62827B40F7F00DA02D0 /* Resources */, + B1F755CC0335410621784198 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + F216F62C27B40F7F00DA02D0 /* PBXTargetDependency */, + ); + name = RiddlerUITests; + productName = RiddlerUITests; + productReference = F216F62A27B40F7F00DA02D0 /* RiddlerUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -425,11 +492,19 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1320; - LastUpgradeCheck = 1630; + LastUpgradeCheck = 1320; TargetAttributes = { F216F60F27B40F7D00DA02D0 = { CreatedOnToolsVersion = 13.2.1; }; + F216F61F27B40F7F00DA02D0 = { + CreatedOnToolsVersion = 13.2.1; + TestTargetID = F216F60F27B40F7D00DA02D0; + }; + F216F62927B40F7F00DA02D0 = { + CreatedOnToolsVersion = 13.2.1; + TestTargetID = F216F60F27B40F7D00DA02D0; + }; }; }; buildConfigurationList = F216F60B27B40F7D00DA02D0 /* Build configuration list for PBXProject "Riddler" */; @@ -442,16 +517,16 @@ ); mainGroup = F216F60727B40F7D00DA02D0; packageReferences = ( - A7CE6A9A2B8B776B00FA9169 /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */, - A7F07A382B8D61900028269E /* XCRemoteSwiftPackageReference "Pow" */, - A7F07A3B2B8D637A0028269E /* XCRemoteSwiftPackageReference "Vortex" */, - A7CD09A92BB1B27F006F71E2 /* XCRemoteSwiftPackageReference "posthog-ios" */, + F29A8A3427B6F07400EEF7A3 /* XCRemoteSwiftPackageReference "lottie-ios" */, + F281F60627DBA40E000790B4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, ); productRefGroup = F216F61127B40F7D00DA02D0 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F216F60F27B40F7D00DA02D0 /* Riddler */, + F216F61F27B40F7F00DA02D0 /* RiddlerTests */, + F216F62927B40F7F00DA02D0 /* RiddlerUITests */, ); }; /* End PBXProject section */ @@ -461,95 +536,238 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - A7F07A2F2B8D418A0028269E /* Colors.xcassets in Resources */, - A7F017662BB24A430059EA89 /* PrivacyInfo.xcprivacy in Resources */, F29A8A2F27B460AE00EEF7A3 /* Abel-Regular.ttf in Resources */, + F281F60527DBA27C000790B4 /* GoogleService-Info.plist in Resources */, F216F61B27B40F7F00DA02D0 /* Preview Assets.xcassets in Resources */, F29A8A3327B48B0000EEF7A3 /* riddles.json in Resources */, + F29A8A3827B6F40B00EEF7A3 /* confetti.json in Resources */, F29A8A3027B460AE00EEF7A3 /* Teko-Regular.ttf in Resources */, + F23FDD7927D840CA008C20A3 /* Podfile in Resources */, F216F61827B40F7F00DA02D0 /* Assets.xcassets in Resources */, + F214456527E2B04800E8890F /* trophy.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F216F61E27B40F7F00DA02D0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F216F62827B40F7F00DA02D0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 2C784A00B11D9636F7CA75F1 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Riddler/Pods-Riddler-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Riddler/Pods-Riddler-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Riddler/Pods-Riddler-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3A79C29EEDBAB4A8A5C625A6 /* [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-Riddler-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; + }; + 3F2238B8C692A170CD01EA87 /* [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-RiddlerTests-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; + }; + 43C0F016E7F52B103328395D /* [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-Riddler-RiddlerUITests-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; + }; + B1F755CC0335410621784198 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Riddler-RiddlerUITests/Pods-Riddler-RiddlerUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Riddler-RiddlerUITests/Pods-Riddler-RiddlerUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Riddler-RiddlerUITests/Pods-Riddler-RiddlerUITests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F281F60C27DBA657000790B4 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", + "$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ F216F60C27B40F7D00DA02D0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A7C3DAB32B8C0B4F0045E2E7 /* StatsMapper.swift in Sources */, + F23FDD8827D9073A008C20A3 /* RewardedAd.swift in Sources */, F2A398E727E60E4B002ADCF3 /* TimeFormatter.swift in Sources */, - A7B8D5DA2B93B45B0013E402 /* GameCenterManager.swift in Sources */, F29A8A3A27BDBD2000EEF7A3 /* SettingsView.swift in Sources */, - A7B72DC62B8EB9500033B615 /* StorageManager.swift in Sources */, + F21BBB0727EB5919001FAA31 /* TestVariables.swift in Sources */, F216F65327B4168F00DA02D0 /* VictoryView.swift in Sources */, - A7C3DA9F2B8BD2320045E2E7 /* AppSetupManager.swift in Sources */, F216F65D27B416E500DA02D0 /* BundleDecodable.swift in Sources */, + F216F65727B416B900DA02D0 /* TextStyles.swift in Sources */, + F216F65927B416C100DA02D0 /* Animations.swift in Sources */, F216F64F27B4167E00DA02D0 /* MenuView.swift in Sources */, - F21BBB0527EA59B6001FAA31 /* GKManager.swift in Sources */, - F214456727E4E5B400E8890F /* RKToast.swift in Sources */, - A7B8D5D82B936E900013E402 /* Array+Extensions.swift in Sources */, - F21BBB0327EA457D001FAA31 /* GKAchievementsView.swift in Sources */, - A770632D2BB0813B00AB686D /* RDInterstitialAd.swift in Sources */, + F21BBB0527EA59B6001FAA31 /* GameKitManager.swift in Sources */, + F214456727E4E5B400E8890F /* Toast.swift in Sources */, + F21BBB0327EA457D001FAA31 /* AchievementsView.swift in Sources */, F216F65127B4168800DA02D0 /* CorrectView.swift in Sources */, - A7C3DA9D2B8BD1AA0045E2E7 /* AppDelegate.swift in Sources */, - A7F07A312B8D42240028269E /* Colors+Generated.swift in Sources */, - A7878B372B914764007D5F7A /* PlayerDataConvertible.swift in Sources */, - F21BBB0127E7EDA8001FAA31 /* GKLeaderboardView.swift in Sources */, - A7F07A352B8D50DF0028269E /* RKIconButton.swift in Sources */, - A7C3DAAE2B8BDB6D0045E2E7 /* UIViewController+Child.swift in Sources */, - F216F65527B416B100DA02D0 /* RKButtonStyle.swift in Sources */, - A7F07A332B8D497E0028269E /* RKButton.swift in Sources */, - A7CD09A62BB19E86006F71E2 /* Date+Extensions.swift in Sources */, - A77063272BB0781300AB686D /* AdsManager.swift in Sources */, - A7CD09A82BB19E9B006F71E2 /* TimeInterval+Extensions.swift in Sources */, - A7C3DAA62B8BD9A70045E2E7 /* Logger+Extensions.swift in Sources */, - A7CD09B62BB1B989006F71E2 /* Analytics+Screens.swift in Sources */, - A78EC72B2B967ED900DB19C3 /* DebugView.swift in Sources */, + F214455F27E27DCC00E8890F /* TextIconButton.swift in Sources */, + F274B5D127DD089600D620CA /* AdFailedDialog.swift in Sources */, + F214455B27E276F500E8890F /* IconButton.swift in Sources */, + F21BBB0127E7EDA8001FAA31 /* LeaderboardView.swift in Sources */, + F281F60327DA7066000790B4 /* ConsentManager.swift in Sources */, + F216F65527B416B100DA02D0 /* ButtonStyles.swift in Sources */, F216F61427B40F7D00DA02D0 /* RiddlerApp.swift in Sources */, F216F64B27B4166000DA02D0 /* HintView.swift in Sources */, - A77063352BB0E6A300AB686D /* RewardedAdView.swift in Sources */, - A7CD09C02BB1E338006F71E2 /* Config.swift in Sources */, - A77063332BB0E47F00AB686D /* RDRewardedAd.swift in Sources */, - A7C3DAA82B8BDA910045E2E7 /* GKAuthenticationView.swift in Sources */, - A77063372BB0E6B800AB686D /* RewardedAdViewController.swift in Sources */, - A77063312BB0824E00AB686D /* InterstitialAdViewController.swift in Sources */, - A78EC72D2B9682EF00DB19C3 /* DisplayStat.swift in Sources */, - A77063252BB074A000AB686D /* UMPConsentViewController.swift in Sources */, - A770632F2BB0822200AB686D /* InterstitialAdView.swift in Sources */, - A7878B332B9133A5007D5F7A /* PlayerV2.swift in Sources */, - A7C3DAA42B8BD7480045E2E7 /* GKAuthentication.swift in Sources */, - A77063292BB079CB00AB686D /* UMPPrivacyView.swift in Sources */, + F21BBAFF27E7E972001FAA31 /* AuthenticateView.swift in Sources */, + F2BBD45C27DFFE7200B23075 /* Stats.swift in Sources */, + F216F65B27B416CE00DA02D0 /* LottieView.swift in Sources */, F216F64D27B4167100DA02D0 /* RiddleView.swift in Sources */, - F216F64527B4162100DA02D0 /* PlayerV1.swift in Sources */, - A7CD09B42BB1B8A8006F71E2 /* Analytics+Events.swift in Sources */, - A7CD09B22BB1B7B0006F71E2 /* LoggingAnalyticsConnector.swift in Sources */, - F281F60127D98F5D000790B4 /* RKBottomSheetPrompt.swift in Sources */, - A7F07A2D2B8D31550028269E /* Fonts+Generated.swift in Sources */, - A77063202BB06FF700AB686D /* StartupManager.swift in Sources */, + F216F64527B4162100DA02D0 /* Player.swift in Sources */, + F281F60127D98F5D000790B4 /* BasicDialog.swift in Sources */, + F23FDD8227D90133008C20A3 /* InterstitialAd.swift in Sources */, + F230AF51287E3F6200FF7769 /* Analytics.swift in Sources */, + F23FDD8A27D90940008C20A3 /* FullscreenModifier.swift in Sources */, F2BBD45A27DEAB5600B23075 /* StatsView.swift in Sources */, - A77063232BB0747500AB686D /* UMPConsentView.swift in Sources */, - A7CD09AE2BB1B2AD006F71E2 /* Analytics.swift in Sources */, - A770632B2BB079EB00AB686D /* UMPPrivacyViewController.swift in Sources */, - F216F64727B4163100DA02D0 /* GameplayManager.swift in Sources */, - A7C3DAAA2B8BDAC50045E2E7 /* GKAuthenticationViewController.swift in Sources */, - A7878B352B9135DF007D5F7A /* RiddleStats.swift in Sources */, - A7CD09B02BB1B4A3006F71E2 /* PostHogAnalyticsConnector.swift in Sources */, - F214455927E2694000E8890F /* RKBackground.swift in Sources */, - A7CD09B82BB1BA2B006F71E2 /* View+Extensions.swift in Sources */, + F216F64727B4163100DA02D0 /* GameViewModel.swift in Sources */, + F214455927E2694000E8890F /* Background.swift in Sources */, F216F64327B4161400DA02D0 /* Riddle.swift in Sources */, + F214455727E267C500E8890F /* BackButton.swift in Sources */, + F214454527E009F100E8890F /* Keys.swift in Sources */, + F214455D27E2778600E8890F /* Extensions.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F216F61C27B40F7F00DA02D0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F216F62527B40F7F00DA02D0 /* RiddlerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F216F62627B40F7F00DA02D0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F216F62F27B40F7F00DA02D0 /* RiddlerUITests.swift in Sources */, + F216F63127B40F7F00DA02D0 /* RiddlerUITestsLaunchTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + F216F62227B40F7F00DA02D0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F216F60F27B40F7D00DA02D0 /* Riddler */; + targetProxy = F216F62127B40F7F00DA02D0 /* PBXContainerItemProxy */; + }; + F216F62C27B40F7F00DA02D0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F216F60F27B40F7D00DA02D0 /* Riddler */; + targetProxy = F216F62B27B40F7F00DA02D0 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ F216F63227B40F7F00DA02D0 /* 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++17"; @@ -581,10 +799,8 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = 36LYYU944F; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -599,7 +815,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -613,7 +829,6 @@ 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++17"; @@ -645,10 +860,8 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = 36LYYU944F; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -657,7 +870,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -669,21 +882,18 @@ }; F216F63527B40F7F00DA02D0 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = F259BF981E79D62D50B11517 /* Pods-Riddler.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = accent; - CODE_SIGN_ENTITLEMENTS = Riddler/App/Riddler.entitlements; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Riddler/Resources/Preview Content\""; + DEVELOPMENT_TEAM = 36LYYU944F; ENABLE_PREVIEWS = YES; - GAD_APP_ID = "$(GAD_APP_ID_ENV)"; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Riddler/App/Info.plist; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.puzzle-games"; INFOPLIST_KEY_NSUserTrackingUsageDescription = "This identifier will be used to deliver personalized ads to you."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -691,12 +901,12 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.5.0; + MARKETING_VERSION = 1.02; PRODUCT_BUNDLE_IDENTIFIER = me.rddle.riddler; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -707,20 +917,17 @@ }; F216F63627B40F7F00DA02D0 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 5B3BA99AC2F43B3FE22A1713 /* Pods-Riddler.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = accent; - CODE_SIGN_ENTITLEMENTS = Riddler/App/Riddler.entitlements; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_ASSET_PATHS = "\"Riddler/Resources/Preview Content\""; + DEVELOPMENT_TEAM = 36LYYU944F; ENABLE_PREVIEWS = YES; - GAD_APP_ID = "$(GAD_APP_ID_ENV)"; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Riddler/App/Info.plist; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.puzzle-games"; INFOPLIST_KEY_NSUserTrackingUsageDescription = "This identifier will be used to deliver personalized ads to you."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -728,12 +935,12 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.5.0; + MARKETING_VERSION = 1.02; PRODUCT_BUNDLE_IDENTIFIER = me.rddle.riddler; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -742,6 +949,88 @@ }; name = Release; }; + F216F63827B40F7F00DA02D0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 699F35ED9EDD5BF816B60AE0 /* Pods-RiddlerTests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 36LYYU944F; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.stenning.RiddlerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Riddler.app/Riddler"; + }; + name = Debug; + }; + F216F63927B40F7F00DA02D0 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 006E382FF0D494352903621A /* Pods-RiddlerTests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 36LYYU944F; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.stenning.RiddlerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Riddler.app/Riddler"; + }; + name = Release; + }; + F216F63B27B40F7F00DA02D0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0A0C4E21975D90F60862142D /* Pods-Riddler-RiddlerUITests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 36LYYU944F; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.stenning.RiddlerUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Riddler; + }; + name = Debug; + }; + F216F63C27B40F7F00DA02D0 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A32E89ED8C85BDC56279202B /* Pods-Riddler-RiddlerUITests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 36LYYU944F; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.stenning.RiddlerUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Riddler; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -763,63 +1052,60 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F216F63727B40F7F00DA02D0 /* Build configuration list for PBXNativeTarget "RiddlerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F216F63827B40F7F00DA02D0 /* Debug */, + F216F63927B40F7F00DA02D0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F216F63A27B40F7F00DA02D0 /* Build configuration list for PBXNativeTarget "RiddlerUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F216F63B27B40F7F00DA02D0 /* Debug */, + F216F63C27B40F7F00DA02D0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - A7CD09A92BB1B27F006F71E2 /* XCRemoteSwiftPackageReference "posthog-ios" */ = { + F281F60627DBA40E000790B4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/PostHog/posthog-ios.git"; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { - kind = exactVersion; - version = 3.25.0; + kind = upToNextMajorVersion; + minimumVersion = 8.0.0; }; }; - A7CE6A9A2B8B776B00FA9169 /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */ = { + F29A8A3427B6F07400EEF7A3 /* XCRemoteSwiftPackageReference "lottie-ios" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/googleads/swift-package-manager-google-mobile-ads.git"; + repositoryURL = "https://github.com/airbnb/lottie-ios"; requirement = { - kind = exactVersion; - version = 12.4.0; - }; - }; - A7F07A382B8D61900028269E /* XCRemoteSwiftPackageReference "Pow" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/EmergeTools/Pow.git"; - requirement = { - kind = exactVersion; - version = 1.0.5; - }; - }; - A7F07A3B2B8D637A0028269E /* XCRemoteSwiftPackageReference "Vortex" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/twostraws/Vortex"; - requirement = { - kind = exactVersion; - version = 1.0.3; + kind = upToNextMajorVersion; + minimumVersion = 3.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - A7CD09AA2BB1B27F006F71E2 /* PostHog */ = { - isa = XCSwiftPackageProductDependency; - package = A7CD09A92BB1B27F006F71E2 /* XCRemoteSwiftPackageReference "posthog-ios" */; - productName = PostHog; - }; - A7CE6A9B2B8B776B00FA9169 /* GoogleMobileAds */ = { + F281F60727DBA40F000790B4 /* FirebaseAnalytics */ = { isa = XCSwiftPackageProductDependency; - package = A7CE6A9A2B8B776B00FA9169 /* XCRemoteSwiftPackageReference "swift-package-manager-google-mobile-ads" */; - productName = GoogleMobileAds; + package = F281F60627DBA40E000790B4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAnalytics; }; - A7F07A392B8D61900028269E /* Pow */ = { + F281F60927DBA40F000790B4 /* FirebaseCrashlytics */ = { isa = XCSwiftPackageProductDependency; - package = A7F07A382B8D61900028269E /* XCRemoteSwiftPackageReference "Pow" */; - productName = Pow; + package = F281F60627DBA40E000790B4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseCrashlytics; }; - A7F07A3C2B8D637A0028269E /* Vortex */ = { + F29A8A3527B6F07400EEF7A3 /* Lottie */ = { isa = XCSwiftPackageProductDependency; - package = A7F07A3B2B8D637A0028269E /* XCRemoteSwiftPackageReference "Vortex" */; - productName = Vortex; + package = F29A8A3427B6F07400EEF7A3 /* XCRemoteSwiftPackageReference "lottie-ios" */; + productName = Lottie; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/Riddler.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riddler.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3a1e49d..131bb30 100644 --- a/Riddler.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riddler.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,51 +1,122 @@ { - "originHash" : "301a474074258cbab0b953a530de18ff63be603c4ed988bf8bc0e58558b1f5cc", "pins" : [ { - "identity" : "posthog-ios", + "identity" : "abseil-cpp-swiftpm", "kind" : "remoteSourceControl", - "location" : "https://github.com/PostHog/posthog-ios.git", + "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", "state" : { - "revision" : "d640df892fcb3bbeadf02b9ec28eeb682df53d24", - "version" : "3.25.0" + "revision" : "fffc3c2729be5747390ad02d5100291a0d9ad26a", + "version" : "0.20200225.4" } }, { - "identity" : "pow", + "identity" : "boringssl-swiftpm", "kind" : "remoteSourceControl", - "location" : "https://github.com/EmergeTools/Pow.git", + "location" : "https://github.com/firebase/boringssl-SwiftPM.git", "state" : { - "revision" : "a504eb6d144bcf49f4f33029a2795345cb39e6b4", - "version" : "1.0.5" + "revision" : "734a8247442fde37df4364c21f6a0085b6a36728", + "version" : "0.7.2" } }, { - "identity" : "swift-package-manager-google-mobile-ads", + "identity" : "firebase-ios-sdk", "kind" : "remoteSourceControl", - "location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads.git", + "location" : "https://github.com/firebase/firebase-ios-sdk", "state" : { - "revision" : "1f4e2201d986617a834326ad481be747ab481570", - "version" : "12.4.0" + "revision" : "d4c4e8c7b8898ea771e90beab71112ea279d73a5", + "version" : "8.13.0" } }, { - "identity" : "swift-package-manager-google-user-messaging-platform", + "identity" : "googleappmeasurement", "kind" : "remoteSourceControl", - "location" : "https://github.com/googleads/swift-package-manager-google-user-messaging-platform.git", + "location" : "https://github.com/google/GoogleAppMeasurement.git", "state" : { - "revision" : "668673ea23b3e71b9f2540d8fb9464c4dc32ecc0", - "version" : "2.2.0" + "revision" : "6cc2991c11872510a5314bc112cc7558dd9d046a", + "version" : "8.12.0" } }, { - "identity" : "vortex", + "identity" : "googledatatransport", "kind" : "remoteSourceControl", - "location" : "https://github.com/twostraws/Vortex", + "location" : "https://github.com/google/GoogleDataTransport.git", "state" : { - "revision" : "26e036c4f04efcc019c95d43bfeffeaefbef3c99", - "version" : "1.0.3" + "revision" : "15ccdfd25ac55b9239b82809531ff26605e7556e", + "version" : "9.1.2" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "b3bb0c5551fb3f80ca939829639ab5b093edd14f", + "version" : "7.7.0" + } + }, + { + "identity" : "grpc-swiftpm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/grpc-SwiftPM.git", + "state" : { + "revision" : "fb405dd2c7901485f7e158b24e3a0a47e4efd8b5", + "version" : "1.28.4" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "bc6a19702ac76ac4e488b68148710eb815f9bc56", + "version" : "1.7.0" + } + }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", + "version" : "1.22.2" + } + }, + { + "identity" : "lottie-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/airbnb/lottie-ios", + "state" : { + "revision" : "4a6058cbbdfe4f74aeae92c8bd51ad3b0de2a1ee", + "version" : "3.3.0" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "7ee9ef9f627d85cbe1b8c4f49a3ed26eed216c77", + "version" : "2.30908.0" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "611337c330350c9c1823ad6d671e7f936af5ee13", + "version" : "2.0.0" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "e1499bc69b9040b29184f7f2996f7bab467c1639", + "version" : "1.19.0" } } ], - "version" : 3 + "version" : 2 } diff --git a/Riddler.xcodeproj/xcshareddata/xcschemes/Riddler.xcscheme b/Riddler.xcodeproj/xcshareddata/xcschemes/Riddler.xcscheme index 7e0f08a..c09758f 100644 --- a/Riddler.xcodeproj/xcshareddata/xcschemes/Riddler.xcscheme +++ b/Riddler.xcodeproj/xcshareddata/xcschemes/Riddler.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + diff --git a/Riddler/App/Riddler.entitlements b/Riddler.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 81% rename from Riddler/App/Riddler.entitlements rename to Riddler.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index c74150d..18d9810 100644 --- a/Riddler/App/Riddler.entitlements +++ b/Riddler.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -2,7 +2,7 @@ - com.apple.developer.game-center + IDEDidComputeMac32BitWarning diff --git a/Riddler.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Riddler.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/Riddler.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/Riddler.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riddler.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..131bb30 --- /dev/null +++ b/Riddler.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,122 @@ +{ + "pins" : [ + { + "identity" : "abseil-cpp-swiftpm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", + "state" : { + "revision" : "fffc3c2729be5747390ad02d5100291a0d9ad26a", + "version" : "0.20200225.4" + } + }, + { + "identity" : "boringssl-swiftpm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/boringssl-SwiftPM.git", + "state" : { + "revision" : "734a8247442fde37df4364c21f6a0085b6a36728", + "version" : "0.7.2" + } + }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk", + "state" : { + "revision" : "d4c4e8c7b8898ea771e90beab71112ea279d73a5", + "version" : "8.13.0" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "6cc2991c11872510a5314bc112cc7558dd9d046a", + "version" : "8.12.0" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "15ccdfd25ac55b9239b82809531ff26605e7556e", + "version" : "9.1.2" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "b3bb0c5551fb3f80ca939829639ab5b093edd14f", + "version" : "7.7.0" + } + }, + { + "identity" : "grpc-swiftpm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/grpc-SwiftPM.git", + "state" : { + "revision" : "fb405dd2c7901485f7e158b24e3a0a47e4efd8b5", + "version" : "1.28.4" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "bc6a19702ac76ac4e488b68148710eb815f9bc56", + "version" : "1.7.0" + } + }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", + "version" : "1.22.2" + } + }, + { + "identity" : "lottie-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/airbnb/lottie-ios", + "state" : { + "revision" : "4a6058cbbdfe4f74aeae92c8bd51ad3b0de2a1ee", + "version" : "3.3.0" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "7ee9ef9f627d85cbe1b8c4f49a3ed26eed216c77", + "version" : "2.30908.0" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "611337c330350c9c1823ad6d671e7f936af5ee13", + "version" : "2.0.0" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "e1499bc69b9040b29184f7f2996f7bab467c1639", + "version" : "1.19.0" + } + } + ], + "version" : 2 +} diff --git a/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/IDEFindNavigatorScopes.plist b/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/UserInterfaceState.xcuserstate b/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..f069053 Binary files /dev/null and b/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/WorkspaceSettings.xcsettings b/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..379adbe --- /dev/null +++ b/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,18 @@ + + + + + BuildLocationStyle + UseAppPreferences + CustomBuildLocationType + RelativeToDerivedData + DerivedDataLocationStyle + Default + IssueFilterStyle + ShowActiveSchemeOnly + LiveSourceIssuesEnabled + + ShowSharedSchemesAutomaticallyEnabled + + + diff --git a/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..233bd9b --- /dev/null +++ b/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/xcschemes/xcschememanagement.plist b/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..db223d7 --- /dev/null +++ b/Riddler.xcworkspace/xcuserdata/oliverstenning.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,72 @@ + + + + + SchemeUserState + + Promises (Playground) 1.xcscheme + + isShown + + orderHint + 2 + + Promises (Playground) 2.xcscheme + + isShown + + orderHint + 3 + + Promises (Playground) 3.xcscheme + + isShown + + orderHint + 4 + + Promises (Playground) 4.xcscheme + + isShown + + orderHint + 5 + + Promises (Playground) 5.xcscheme + + isShown + + orderHint + 6 + + Promises (Playground) 6.xcscheme + + isShown + + orderHint + 16 + + Promises (Playground) 7.xcscheme + + isShown + + orderHint + 17 + + Promises (Playground) 8.xcscheme + + isShown + + orderHint + 18 + + Promises (Playground).xcscheme + + isShown + + orderHint + 1 + + + + diff --git a/Riddler/Ads/AdsManager.swift b/Riddler/Ads/AdsManager.swift deleted file mode 100644 index 093ddee..0000000 --- a/Riddler/Ads/AdsManager.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation -import GoogleMobileAds -import OSLog - -// MARK: - AdsManager - -class AdsManager { - - // MARK: Lifecycle - - private init() {} - - // MARK: Internal - - static let shared = AdsManager() - - var hasConsented: Bool = false { - didSet { - if hasConsented { - setupAdsSDK() - } - } - } - - // MARK: Private - - private var isSDKSetup: Bool = false - - private func setupAdsSDK() { - guard !isSDKSetup, Config.admobEnabled else { return } - - Logger.consent.info("Starting Mobile Ads SDK") - MobileAds.shared.start { [weak self] _ in - guard let self else { return } - isSDKSetup = true - MobileAds.shared.requestConfiguration.testDeviceIdentifiers = Config.adMobTestDevices - - RDInterstitialAd.shared.loadAd() - RDRewardedAd.shared.loadAd() - } - } -} diff --git a/Riddler/Ads/InterstitialAdView.swift b/Riddler/Ads/InterstitialAdView.swift deleted file mode 100644 index 1b5a24e..0000000 --- a/Riddler/Ads/InterstitialAdView.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation -import GoogleMobileAds -import SwiftUI - -struct InterstitialAdView: UIViewControllerRepresentable { - - // MARK: Lifecycle - - public init( - completion: @escaping (() -> Void) - ) { - self.completion = completion - } - - // MARK: Internal - - func makeUIViewController(context: UIViewControllerRepresentableContext) -> InterstitialAdViewController { - let adViewController = InterstitialAdViewController(completion: completion) - return adViewController - } - - func updateUIViewController(_ uiViewController: InterstitialAdViewController, context: UIViewControllerRepresentableContext) {} - - // MARK: Private - - private let completion: () -> Void - -} diff --git a/Riddler/Ads/InterstitialAdViewController.swift b/Riddler/Ads/InterstitialAdViewController.swift deleted file mode 100644 index d8d2203..0000000 --- a/Riddler/Ads/InterstitialAdViewController.swift +++ /dev/null @@ -1,66 +0,0 @@ -import Foundation -import GoogleMobileAds -import OSLog -import SwiftUI - -// MARK: - InterstitialAdViewController - -class InterstitialAdViewController: UIViewController { - - // MARK: Lifecycle - - init(completion: @escaping () -> Void) { - interstitialAd = RDInterstitialAd.shared.interstitialAd - self.completion = completion - super.init(nibName: nil, bundle: nil) - interstitialAd?.fullScreenContentDelegate = self - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - guard !hasShownAd else { return } - - if let interstitialAd { - Logger.ads.info("Presenting interstitial ad") - Analytics.shared.screen(.interstitialAd()) - hasShownAd = true - interstitialAd.present(from: self) - } else { - Logger.ads.error("Tried to show interstitial ad when it wasn't loaded") - completion() - } - } - - // MARK: Private - - private let interstitialAd: InterstitialAd? - - private let completion: () -> Void - - private var hasShownAd: Bool = false - -} - -// MARK: GADFullScreenContentDelegate - -extension InterstitialAdViewController: FullScreenContentDelegate { - - func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) { - Logger.ads.info("Add finished displaying") - RDInterstitialAd.shared.loadAd() - completion() - } - - func ad(_ ad: FullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) { - Logger.ads.error("Ad failed to present with error: \(error)") - RDInterstitialAd.shared.loadAd() - completion() - } - -} diff --git a/Riddler/Ads/RDInterstitialAd.swift b/Riddler/Ads/RDInterstitialAd.swift deleted file mode 100644 index d4a4019..0000000 --- a/Riddler/Ads/RDInterstitialAd.swift +++ /dev/null @@ -1,33 +0,0 @@ -import GoogleMobileAds -import OSLog -import SwiftUI - -class RDInterstitialAd: NSObject { - - // MARK: Internal - - static let shared = RDInterstitialAd() - - var interstitialAd: InterstitialAd? - - func loadAd() { - let request = Request() - InterstitialAd.load(with: adUnitId, request: request) { interstitialAd, error in - if let error { - Logger.ads.error("Failed to load ad with error: \(error)") - return - } - Logger.ads.info("Loaded interstitial ad") - self.interstitialAd = interstitialAd - } - } - - func clearAd() { - interstitialAd = nil - } - - // MARK: Private - - private let adUnitId: String = Config.interstitialAdUnitId - -} diff --git a/Riddler/Ads/RDRewardedAd.swift b/Riddler/Ads/RDRewardedAd.swift deleted file mode 100644 index 973041b..0000000 --- a/Riddler/Ads/RDRewardedAd.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Combine -import GoogleMobileAds -import OSLog -import SwiftUI - -class RDRewardedAd: NSObject { - - // MARK: Internal - - static let shared = RDRewardedAd() - - var rewardedAd: RewardedAd? - - var adLoadedPublisher = PassthroughSubject() - - func loadAd() { - let request = Request() - RewardedAd.load(with: adUnitId, request: request) { rewardedAd, error in - if let error { - Logger.ads.error("Failed to load rewarded ad with error: \(error)") - return - } - - Logger.ads.info("Loaded rewarded ad") - self.rewardedAd = rewardedAd - self.adLoadedPublisher.send() - } - } - - func clearAd() { - rewardedAd = nil - } - - // MARK: Private - - private let adUnitId: String = Config.rewardedAdUnitId - -} diff --git a/Riddler/Ads/RewardedAdView.swift b/Riddler/Ads/RewardedAdView.swift deleted file mode 100644 index 6f27c85..0000000 --- a/Riddler/Ads/RewardedAdView.swift +++ /dev/null @@ -1,31 +0,0 @@ -import Foundation -import GoogleMobileAds -import SwiftUI - -struct RewardedAdView: UIViewControllerRepresentable { - - // MARK: Lifecycle - - public init( - rewardedHandler: @escaping () -> Void, - completion: @escaping () -> Void - ) { - rewardHandler = rewardedHandler - self.completion = completion - } - - // MARK: Internal - - func makeUIViewController(context: UIViewControllerRepresentableContext) -> RewardedAdViewController { - let adViewController = RewardedAdViewController(rewardHandler: rewardHandler, completion: completion) - return adViewController - } - - func updateUIViewController(_ uiViewController: RewardedAdViewController, context: UIViewControllerRepresentableContext) {} - - // MARK: Private - - private let rewardHandler: () -> Void - private let completion: () -> Void - -} diff --git a/Riddler/Ads/RewardedAdViewController.swift b/Riddler/Ads/RewardedAdViewController.swift deleted file mode 100644 index 8a844ae..0000000 --- a/Riddler/Ads/RewardedAdViewController.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Foundation -import GoogleMobileAds -import OSLog -import SwiftUI - -// MARK: - RewardedAdViewController - -class RewardedAdViewController: UIViewController { - - // MARK: Lifecycle - - init(rewardHandler: @escaping () -> Void, completion: @escaping () -> Void) { - rewardedAd = RDRewardedAd.shared.rewardedAd - self.rewardHandler = rewardHandler - self.completion = completion - super.init(nibName: nil, bundle: nil) - rewardedAd?.fullScreenContentDelegate = self - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - guard !hasShownAd else { return } - - if let rewardedAd { - Logger.ads.info("Presenting rewarded ad") - Analytics.shared.screen(.rewardedAd()) - hasShownAd = true - rewardedAd.present(from: self, userDidEarnRewardHandler: rewardHandler) - } else { - Logger.ads.error("Tried to show rewarded ad when it wasn't loaded") - completion() - } - } - - // MARK: Private - - private let rewardedAd: RewardedAd? - - private let rewardHandler: () -> Void - private let completion: () -> Void - - private var hasShownAd: Bool = false - -} - -// MARK: GADFullScreenContentDelegate - -extension RewardedAdViewController: FullScreenContentDelegate { - - func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) { - Logger.ads.info("Rewarded ad finished displaying") - RDRewardedAd.shared.loadAd() - completion() - } - - func ad(_ ad: FullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) { - Logger.ads.error("Rewarded ad failed to present with error: \(error)") - RDRewardedAd.shared.loadAd() - completion() - } - -} diff --git a/Riddler/Ads/UMPConsentView.swift b/Riddler/Ads/UMPConsentView.swift deleted file mode 100644 index 269e43c..0000000 --- a/Riddler/Ads/UMPConsentView.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation -import GameKit -import SwiftUI - -struct UMPConsentView: UIViewControllerRepresentable { - - // MARK: Lifecycle - - public init(failure: @escaping ((Error) -> Void), completion: @escaping (() -> Void)) { - self.failure = failure - self.completion = completion - } - - // MARK: Internal - - func makeUIViewController(context: UIViewControllerRepresentableContext) -> UMPConsentViewController { - let consentViewController = UMPConsentViewController(failure: failure, completion: completion) - return consentViewController - } - - func updateUIViewController(_ uiViewController: UMPConsentViewController, context: UIViewControllerRepresentableContext) {} - - // MARK: Private - - private let failure: (Error) -> Void - private let completion: () -> Void -} diff --git a/Riddler/Ads/UMPConsentViewController.swift b/Riddler/Ads/UMPConsentViewController.swift deleted file mode 100644 index cedb7fe..0000000 --- a/Riddler/Ads/UMPConsentViewController.swift +++ /dev/null @@ -1,62 +0,0 @@ -import GoogleMobileAds -import OSLog -import SwiftUI -import UserMessagingPlatform - -class UMPConsentViewController: UIViewController { - - // MARK: Lifecycle - - init(failure: @escaping (Error) -> Void, completion: @escaping () -> Void) { - self.failure = failure - self.completion = completion - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - let requestParameters = UMPRequestParameters() - let debugSettings = UMPDebugSettings() - debugSettings.geography = .EEA - debugSettings.testDeviceIdentifiers = ["12E8FAFC-172A-45C0-B1BF-A7A2C8413E22"] - requestParameters.debugSettings = debugSettings - - UMPConsentInformation.sharedInstance.requestConsentInfoUpdate(with: requestParameters) { [weak self] error in - guard let self else { return } - - if let error { - Logger.consent.error("Failed to request consent with error: \(error)") - } - - Logger.consent.info("Loading and presenting consent form if required.") - UMPConsentForm.loadAndPresentIfRequired(from: self) { [weak self] error in - guard let self else { return } - - if let error { - Logger.consent.error("Failed to present consent form with error: \(error)") - failure(error) - } else { - AdsManager.shared.hasConsented = UMPConsentInformation.sharedInstance.canRequestAds - Logger.consent.info("Consent collection completed with hasConsented: \(AdsManager.shared.hasConsented)") - completion() - } - } - } - - let hasPreviouslyConsented = UMPConsentInformation.sharedInstance.canRequestAds - Logger.ads.info("Using consent information from previous session, hasConsent: \(hasPreviouslyConsented)") - AdsManager.shared.hasConsented = hasPreviouslyConsented - } - - // MARK: Private - - private let failure: (Error) -> Void - private let completion: () -> Void - -} diff --git a/Riddler/Ads/UMPPrivacyView.swift b/Riddler/Ads/UMPPrivacyView.swift deleted file mode 100644 index 703f1a8..0000000 --- a/Riddler/Ads/UMPPrivacyView.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation -import GameKit -import SwiftUI - -struct UMPPrivacyView: UIViewControllerRepresentable { - - // MARK: Lifecycle - - public init(failure: @escaping ((Error) -> Void), completion: @escaping (() -> Void)) { - self.failure = failure - self.completion = completion - } - - // MARK: Internal - - func makeUIViewController(context: UIViewControllerRepresentableContext) -> UMPPrivacyViewController { - let consentViewController = UMPPrivacyViewController(failure: failure, completion: completion) - return consentViewController - } - - func updateUIViewController(_ uiViewController: UMPPrivacyViewController, context: UIViewControllerRepresentableContext) {} - - // MARK: Private - - private let failure: (Error) -> Void - private let completion: () -> Void -} diff --git a/Riddler/Ads/UMPPrivacyViewController.swift b/Riddler/Ads/UMPPrivacyViewController.swift deleted file mode 100644 index 77849e6..0000000 --- a/Riddler/Ads/UMPPrivacyViewController.swift +++ /dev/null @@ -1,44 +0,0 @@ -import Foundation -import GameKit -import OSLog -import SwiftUI -import UserMessagingPlatform - -class UMPPrivacyViewController: UIViewController { - - // MARK: Lifecycle - - init(failure: @escaping (Error) -> Void, completion: @escaping () -> Void) { - self.failure = failure - self.completion = completion - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - UMPConsentForm.presentPrivacyOptionsForm(from: self) { [weak self] error in - guard let self else { return } - - if let error { - Logger.consent.error("Failed to present privacy form from settings with error: \(error)") - failure(error) - } else { - AdsManager.shared.hasConsented = UMPConsentInformation.sharedInstance.canRequestAds - Logger.consent.info("Updated consent status to hasConsented: \(AdsManager.shared.hasConsented)") - Logger.consent.info("Updated consent status to: \(UMPConsentInformation.sharedInstance.consentStatus.rawValue)") - completion() - } - } - } - - // MARK: Internal - - let failure: (Error) -> Void - let completion: () -> Void - -} diff --git a/Riddler/Analytics/Analytics+Events.swift b/Riddler/Analytics/Analytics+Events.swift deleted file mode 100644 index 1e900f6..0000000 --- a/Riddler/Analytics/Analytics+Events.swift +++ /dev/null @@ -1,114 +0,0 @@ -import Foundation - -extension AnalyticsEvent { - - // MARK: - Menu - - static func tapStart() -> Self { - .init(name: "Tap start") - } - - static func tapAchievements() -> Self { - .init(name: "Tap achievements") - } - - static func tapLeaderboard() -> Self { - .init(name: "Tap leaderboard") - } - - static func tapSettings() -> Self { - .init(name: "Tap settings") - } - - // MARK: - Settings - - static func tapPrivacyPolicy() -> Self { - .init(name: "Tap privacy policy") - } - - static func tapUpdatePrivacy() -> Self { - .init(name: "Tap update privacy settings") - } - - static func tapReportBug() -> Self { - .init(name: "Tap report bug") - } - - // MARK: - Riddle - - static func tapOpenHints() -> Self { - .init(name: "Tap open hints") - } - - static func tapEnterAnswer() -> Self { - .init(name: "Tap enter answer") - } - - static func incorrectGuess(_ guess: String, riddleNumber: Int) -> Self { - .init(name: "Incorrect guess", properties: ["guess": guess, "riddleNumber": riddleNumber]) - } - - static func correctGuess(_ guess: String, riddleNumber: Int) -> Self { - .init(name: "Correct guess", properties: ["guess": guess, "riddleNumber": riddleNumber]) - } - - // MARK: - Hint - - static func tapUseHint(numberOfHints: Int) -> Self { - .init(name: "Tap use hint", properties: ["numberOfHints": numberOfHints]) - } - - static func tapGetMoreHints(numberOfHints: Int) -> Self { - .init(name: "Tap get more hints", properties: ["numberOfHints": numberOfHints]) - } - - static func tapCloseGetMoreHintsDialog(numberOfHints: Int) -> Self { - .init(name: "Tap close get more hints dialog", properties: ["numberOfHints": numberOfHints]) - } - - static func tapWatchRewardedAd(numberOfHints: Int) -> Self { - .init(name: "Tap watch rewarded ad", properties: ["numberOfHints": numberOfHints]) - } - - // MARK: - Correct - - static func tapOpenSuggestDialog() -> Self { - .init(name: "Tap open suggest answer dialog") - } - - static func tapSuggestAnswer() -> Self { - .init(name: "Tap suggest answer") - } - - static func tapCloseSuggestDialog() -> Self { - .init(name: "Tap close suggest answer dialog") - } - - static func tapShareCorrect() -> Self { - .init(name: "Tap share correct") - } - - // MARK: - Victory - - static func tapShareVictory() -> Self { - .init(name: "Tap share victory") - } - - static func tapOpenStats() -> Self { - .init(name: "Tap open stats") - } - - // MARK: - Stats - - static func stats(_ stats: [DisplayStat]) -> Self { - let properties = Dictionary(uniqueKeysWithValues: stats.map { ($0.name, $0.value) }) - return .init(name: "Stats", properties: properties) - } - - // MARK: - Ads - - static func hintsRewarded() -> Self { - .init(name: "Hints rewarded") - } - -} diff --git a/Riddler/Analytics/Analytics+Screens.swift b/Riddler/Analytics/Analytics+Screens.swift deleted file mode 100644 index 584ca17..0000000 --- a/Riddler/Analytics/Analytics+Screens.swift +++ /dev/null @@ -1,41 +0,0 @@ -import Foundation - -extension AnalyticsScreen { - - static func menu() -> Self { - .init(name: "Menu") - } - - static func settings() -> Self { - .init(name: "Settings") - } - - static func riddle() -> Self { - .init(name: "Riddle") - } - - static func hint() -> Self { - .init(name: "Hint") - } - - static func correct() -> Self { - .init(name: "Correct") - } - - static func victory() -> Self { - .init(name: "Victory") - } - - static func stats() -> Self { - .init(name: "Stats") - } - - static func interstitialAd() -> Self { - .init(name: "Interstitial ad") - } - - static func rewardedAd() -> Self { - .init(name: "Rewarded ad") - } - -} diff --git a/Riddler/Analytics/Analytics.swift b/Riddler/Analytics/Analytics.swift deleted file mode 100644 index d3addd7..0000000 --- a/Riddler/Analytics/Analytics.swift +++ /dev/null @@ -1,83 +0,0 @@ -import Foundation - -// MARK: - AnalyticsScreen - -struct AnalyticsScreen { - - // MARK: Lifecycle - - init(name: String, properties: [String: Any] = [:]) { - self.name = name - self.properties = properties - } - - // MARK: Internal - - let name: String - let properties: [String: Any] - -} - -// MARK: - AnalyticsEvent - -struct AnalyticsEvent { - - // MARK: Lifecycle - - init(name: String, properties: [String: Any] = [:]) { - self.name = name - self.properties = properties - } - - // MARK: Internal - - let name: String - let properties: [String: Any] - -} - -// MARK: - AnalyticsConnectable - -protocol AnalyticsConnectable { - func screen(_ screen: AnalyticsScreen) - func event(_ event: AnalyticsEvent) -} - -// MARK: - AnalyticsProtocol - -protocol AnalyticsProtocol { - func screen(_ screen: AnalyticsScreen) - func event(_ event: AnalyticsEvent) -} - -// MARK: - Analytics - -final class Analytics: AnalyticsProtocol { - - // MARK: Lifecycle - - init() {} - - // MARK: Public - - public func addConnector(_ connector: AnalyticsConnectable) { - connectors.append(connector) - } - - // MARK: Internal - - static let shared = Analytics() - - func screen(_ screen: AnalyticsScreen) { - connectors.forEach { $0.screen(screen) } - } - - func event(_ event: AnalyticsEvent) { - connectors.forEach { $0.event(event) } - } - - // MARK: Private - - private var connectors: [AnalyticsConnectable] = [] - -} diff --git a/Riddler/Analytics/LoggingAnalyticsConnector.swift b/Riddler/Analytics/LoggingAnalyticsConnector.swift deleted file mode 100644 index be05d04..0000000 --- a/Riddler/Analytics/LoggingAnalyticsConnector.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation -import OSLog - -struct LoggingAnalyticsConnector: AnalyticsConnectable { - - func screen(_ screen: AnalyticsScreen) { - Logger.analytics.info("Screen: \(screen.name)\nProperties: \(screen.properties)") - } - - func event(_ event: AnalyticsEvent) { - Logger.analytics.info("Event: \(event.name)\nProperties: \(event.properties)") - } - -} diff --git a/Riddler/Analytics/PostHogAnalyticsConnector.swift b/Riddler/Analytics/PostHogAnalyticsConnector.swift deleted file mode 100644 index 070cbf6..0000000 --- a/Riddler/Analytics/PostHogAnalyticsConnector.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation -import PostHog - -struct PostHogAnalyticsConnector: AnalyticsConnectable { - - func screen(_ screen: AnalyticsScreen) { - PostHogSDK.shared.screen(screen.name, properties: screen.properties) - } - - func event(_ event: AnalyticsEvent) { - PostHogSDK.shared.capture(event.name, properties: event.properties) - } - -} diff --git a/Riddler/App/AppDelegate.swift b/Riddler/App/AppDelegate.swift deleted file mode 100644 index ae570b2..0000000 --- a/Riddler/App/AppDelegate.swift +++ /dev/null @@ -1,10 +0,0 @@ -import UIKit - -class AppDelegate: NSObject, UIApplicationDelegate { - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { - AppSetupManager.setup() - return true - } - -} diff --git a/Riddler/App/AppSetupManager.swift b/Riddler/App/AppSetupManager.swift deleted file mode 100644 index c97b6bd..0000000 --- a/Riddler/App/AppSetupManager.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Foundation -import PostHog -import UIKit - -enum AppSetupManager { - - // MARK: Internal - - static func setup() { - setupPostHog() - setupAnalyticsLogging() - setupAppearance() - } - - // MARK: Private - - private static func setupPostHog() { - // Omitted from public repo - guard let postHogApiKey = Config.postHogApiKey, let postHogHost = Config.postHogHost else { return } - let config = PostHogConfig(apiKey: postHogApiKey, host: postHogHost) - PostHogSDK.shared.setup(config) - Analytics.shared.addConnector(PostHogAnalyticsConnector()) - } - - private static func setupAnalyticsLogging() { - Analytics.shared.addConnector(LoggingAnalyticsConnector()) - } - - private static func setupAppearance() { - UIBarButtonItem.appearance().setTitleTextAttributes([ - .font: RKFonts.Teko.regular.font(size: 24), - .foregroundColor: RKColors.accent.color - ], for: .normal) - - UIBarButtonItem.appearance().setTitleTextAttributes([ - .font: RKFonts.Teko.regular.font(size: 24), - .foregroundColor: RKColors.accentDark.color - ], for: .highlighted) - - UIBarButtonItem.appearance().tintColor = RKColors.accent.color - } - -} diff --git a/Riddler/App/Config/ExampleConfig.swift b/Riddler/App/Config/ExampleConfig.swift deleted file mode 100644 index 126e13d..0000000 --- a/Riddler/App/Config/ExampleConfig.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation - -typealias Config = ExampleConfig - -// MARK: - ExampleConfig - -enum ExampleConfig { - - // PostHog analytics are disabled when the property is nil - static let postHogApiKey: String? = nil - static let postHogHost: String? = nil - - // AdMob requires updating app ID in Info.plist - static let admobEnabled: Bool = false - - static let interstitialAdUnitId: String = "ca-app-pub-3940256099942544/4411468910" - static let rewardedAdUnitId: String = "ca-app-pub-3940256099942544/1712485313" - - static let adMobTestDevices: [String] = [] - -} diff --git a/Riddler/App/ExampleInfo.plist b/Riddler/App/ExampleInfo.plist deleted file mode 100644 index 6890261..0000000 --- a/Riddler/App/ExampleInfo.plist +++ /dev/null @@ -1,221 +0,0 @@ - - - - - UILaunchScreen - - UIImageName - primaryBackground - UIColorName - primary - UIImageRespectsSafeAreaInsets - - - UIAppFonts - - Abel-Regular.ttf - Teko-Regular.ttf - - GADApplicationIdentifier - ca-app-pub-3940256099942544~3347511713 - SKAdNetworkItems - - - SKAdNetworkIdentifier - cstr6suwn9.skadnetwork - - - SKAdNetworkIdentifier - 4fzdc2evr5.skadnetwork - - - SKAdNetworkIdentifier - 4pfyvq9l8r.skadnetwork - - - SKAdNetworkIdentifier - 2fnua5tdw4.skadnetwork - - - SKAdNetworkIdentifier - ydx93a7ass.skadnetwork - - - SKAdNetworkIdentifier - 5a6flpkh64.skadnetwork - - - SKAdNetworkIdentifier - p78axxw29g.skadnetwork - - - SKAdNetworkIdentifier - v72qych5uu.skadnetwork - - - SKAdNetworkIdentifier - ludvb6z3bs.skadnetwork - - - SKAdNetworkIdentifier - cp8zw746q7.skadnetwork - - - SKAdNetworkIdentifier - 3sh42y64q3.skadnetwork - - - SKAdNetworkIdentifier - c6k4g5qg8m.skadnetwork - - - SKAdNetworkIdentifier - s39g8k73mm.skadnetwork - - - SKAdNetworkIdentifier - 3qy4746246.skadnetwork - - - SKAdNetworkIdentifier - f38h382jlk.skadnetwork - - - SKAdNetworkIdentifier - hs6bdukanm.skadnetwork - - - SKAdNetworkIdentifier - v4nxqhlyqp.skadnetwork - - - SKAdNetworkIdentifier - wzmmz9fp6w.skadnetwork - - - SKAdNetworkIdentifier - yclnxrl5pm.skadnetwork - - - SKAdNetworkIdentifier - t38b2kh725.skadnetwork - - - SKAdNetworkIdentifier - 7ug5zh24hu.skadnetwork - - - SKAdNetworkIdentifier - gta9lk7p23.skadnetwork - - - SKAdNetworkIdentifier - vutu7akeur.skadnetwork - - - SKAdNetworkIdentifier - y5ghdn5j9k.skadnetwork - - - SKAdNetworkIdentifier - n6fk4nfna4.skadnetwork - - - SKAdNetworkIdentifier - v9wttpbfk9.skadnetwork - - - SKAdNetworkIdentifier - n38lu8286q.skadnetwork - - - SKAdNetworkIdentifier - 47vhws6wlr.skadnetwork - - - SKAdNetworkIdentifier - kbd757ywx3.skadnetwork - - - SKAdNetworkIdentifier - 9t245vhmpl.skadnetwork - - - SKAdNetworkIdentifier - eh6m2bh4zr.skadnetwork - - - SKAdNetworkIdentifier - a2p9lx4jpn.skadnetwork - - - SKAdNetworkIdentifier - 22mmun2rn5.skadnetwork - - - SKAdNetworkIdentifier - 4468km3ulz.skadnetwork - - - SKAdNetworkIdentifier - 2u9pt9hc89.skadnetwork - - - SKAdNetworkIdentifier - 8s468mfl3y.skadnetwork - - - SKAdNetworkIdentifier - klf5c3l5u5.skadnetwork - - - SKAdNetworkIdentifier - ppxm28t8ap.skadnetwork - - - SKAdNetworkIdentifier - ecpz2srf59.skadnetwork - - - SKAdNetworkIdentifier - uw77j35x4d.skadnetwork - - - SKAdNetworkIdentifier - pwa73g5rt2.skadnetwork - - - SKAdNetworkIdentifier - mlmmfzh3r3.skadnetwork - - - SKAdNetworkIdentifier - 578prtvx9j.skadnetwork - - - SKAdNetworkIdentifier - 4dzt52r2t5.skadnetwork - - - SKAdNetworkIdentifier - e5fvkxwrpn.skadnetwork - - - SKAdNetworkIdentifier - 8c4e2ghe7u.skadnetwork - - - SKAdNetworkIdentifier - zq492l623r.skadnetwork - - - SKAdNetworkIdentifier - 3rd42ekr43.skadnetwork - - - SKAdNetworkIdentifier - 3qcr597p9d.skadnetwork - - - - diff --git a/Riddler/App/PrivacyInfo.xcprivacy b/Riddler/App/PrivacyInfo.xcprivacy deleted file mode 100644 index c966b47..0000000 --- a/Riddler/App/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,44 +0,0 @@ - - - - - NSPrivacyCollectedDataTypes - - - NSPrivacyCollectedDataType - NSPrivacyCollectedDataTypeProductInteraction - NSPrivacyCollectedDataTypeLinked - - NSPrivacyCollectedDataTypeTracking - - NSPrivacyCollectedDataTypePurposes - - NSPrivacyCollectedDataTypePurposeAnalytics - - - - NSPrivacyCollectedDataType - NSPrivacyCollectedDataTypeAdvertisingData - NSPrivacyCollectedDataTypeLinked - - NSPrivacyCollectedDataTypeTracking - - NSPrivacyCollectedDataTypePurposes - - NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising - - - - NSPrivacyAccessedAPITypes - - - NSPrivacyAccessedAPITypeReasons - - CA92.1 - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryUserDefaults - - - - diff --git a/Riddler/App/RiddlerApp.swift b/Riddler/App/RiddlerApp.swift index 53b34fd..5860964 100644 --- a/Riddler/App/RiddlerApp.swift +++ b/Riddler/App/RiddlerApp.swift @@ -1,15 +1,44 @@ import SwiftUI +import Firebase +import GameKit @main struct RiddlerApp: App { + + @State var showGameCenter: Bool = false + @State var showAchievements: Bool = false + @State var showLeaderboard: Bool = false - @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate - - var body: some Scene { - WindowGroup { - MenuView(startupManager: StartupManager(), game: GameplayManager()) - .preferredColorScheme(.dark) - } - } - + var body: some Scene { + WindowGroup { + ZStack { + MenuView(showGameCenter: $showGameCenter, + showLeaderboard: $showLeaderboard, + showAchievements: $showAchievements) + + .presentAuthenticate(isPresented: $showGameCenter) + .presentAchievements(isPresented: $showAchievements) + .presentLeaderboard(isPresented: $showLeaderboard) + } + .onAppear(perform: appStart) + .preferredColorScheme(.dark) + } + } + + func appStart() { + if !ConsentManager.shared.ConsentConfigured { + if !TestVariables.DISABLE_CONSENT_FORM { + ConsentManager.shared.startupConsent(completion: startGameCenter) + } + } + } + + func startGameCenter() { + if !TestVariables.DISABLE_GAMECENTER && GameKitManager.shared.gameKitStarted == false { + print("Start: Game Center") + GameKitManager.shared.gameKitStarted = true + showGameCenter = true + } + } + } diff --git a/Riddler/GameKit/GKAchievementsView.swift b/Riddler/GameKit/GKAchievementsView.swift deleted file mode 100644 index 23f1143..0000000 --- a/Riddler/GameKit/GKAchievementsView.swift +++ /dev/null @@ -1,36 +0,0 @@ -import GameKit -import SwiftUI -import UIKit - -struct GKAchievementsView: UIViewControllerRepresentable { - - class Coordinator: NSObject, GKGameCenterControllerDelegate { - - // MARK: Lifecycle - - init(_ parent: GKAchievementsView) { - self.parent = parent - } - - // MARK: Internal - - var parent: GKAchievementsView - - func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) { - gameCenterViewController.dismiss(animated: true) - } - - } - - func makeUIViewController(context: Context) -> some UIViewController { - let viewController = GKGameCenterViewController(state: .achievements) - viewController.gameCenterDelegate = context.coordinator - return viewController - } - - func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {} - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } -} diff --git a/Riddler/GameKit/GKAuthentication.swift b/Riddler/GameKit/GKAuthentication.swift deleted file mode 100644 index 5fbc7b8..0000000 --- a/Riddler/GameKit/GKAuthentication.swift +++ /dev/null @@ -1,76 +0,0 @@ -import Combine -import Foundation -import GameKit -import os.log - -public final class GKAuthentication: NSObject, GKLocalPlayerListener { - - // MARK: Lifecycle - - private override init() { - isAuthenticated.value = GKLocalPlayer.local.isAuthenticated - super.init() - // Setup internal observer for GameKit authentication changes - NotificationCenter.default.addObserver( - self, - selector: #selector(GKAuthentication.authenticationChanged), - name: Notification.Name.GKPlayerAuthenticationDidChangeNotificationName, - object: nil - ) - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - // MARK: Public - - public static let shared = GKAuthentication() - - public private(set) var isAuthenticated = CurrentValueSubject(false) - - public func authenticate( - authenticationViewController: @escaping (UIViewController) -> Void, - failed: @escaping (Error) -> Void, - authenticated: @escaping (GKLocalPlayer) -> Void - ) { - if GKLocalPlayer.local.isAuthenticated { - authenticated(GKLocalPlayer.local) - return - } - - if let authenticationError { - failed(authenticationError) - return - } - - GKLocalPlayer.local.authenticateHandler = { viewController, error in - if GKLocalPlayer.local.isAuthenticated { - authenticated(GKLocalPlayer.local) - return - } - - if let viewController { - authenticationViewController(viewController) - return - } - - if let error { - Logger.gameCenter.error("Authentication failed with error: \(error.localizedDescription)") - self.authenticationError = error - failed(error) - return - } - } - } - - // MARK: Internal - - private(set) var authenticationError: Error? - - // MARK: Fileprivate - - @objc fileprivate func authenticationChanged() { - isAuthenticated.value = GKLocalPlayer.local.isAuthenticated - } -} diff --git a/Riddler/GameKit/GKAuthenticationView.swift b/Riddler/GameKit/GKAuthenticationView.swift deleted file mode 100644 index e5029b1..0000000 --- a/Riddler/GameKit/GKAuthenticationView.swift +++ /dev/null @@ -1,31 +0,0 @@ -import Foundation -import GameKit -import SwiftUI - -struct GKAuthenticationView: UIViewControllerRepresentable { - - // MARK: Lifecycle - - public init(failed: @escaping ((Error) -> Void), authenticated: @escaping ((GKPlayer) -> Void)) { - self.failed = failed - self.authenticated = authenticated - } - - // MARK: Internal - - func makeUIViewController(context: UIViewControllerRepresentableContext) -> GKAuthenticationViewController { - let authenticationViewController = GKAuthenticationViewController { failed in - self.failed(failed) - } authenticated: { player in - authenticated(player) - } - return authenticationViewController - } - - func updateUIViewController(_ uiViewController: GKAuthenticationViewController, context: UIViewControllerRepresentableContext) {} - - // MARK: Private - - private let failed: (Error) -> Void - private let authenticated: (GKPlayer) -> Void -} diff --git a/Riddler/GameKit/GKAuthenticationViewController.swift b/Riddler/GameKit/GKAuthenticationViewController.swift deleted file mode 100644 index 9ef6006..0000000 --- a/Riddler/GameKit/GKAuthenticationViewController.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Foundation -import GameKit -import OSLog -import SwiftUI - -class GKAuthenticationViewController: UIViewController { - - // MARK: Lifecycle - - init(failed: @escaping (Error) -> Void, authenticated: @escaping (GKLocalPlayer) -> Void) { - self.failed = failed - self.authenticated = authenticated - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - GKAuthentication.shared.authenticate { authenticationViewController in - self.add(authenticationViewController) - } failed: { error in - Logger.gameCenter.error("Authentication failed with error: \(error.localizedDescription)") - self.failed(error) - } authenticated: { player in - Logger.gameCenter.info("Played authenticated \(player.displayName)") - self.authenticated(player) - } - } - - override func viewWillDisappear(_ animated: Bool) { - removeAll() - } - - // MARK: Internal - - let failed: (Error) -> Void - let authenticated: (GKLocalPlayer) -> Void - -} diff --git a/Riddler/GameKit/GKLeaderboardView.swift b/Riddler/GameKit/GKLeaderboardView.swift deleted file mode 100644 index 5fc8169..0000000 --- a/Riddler/GameKit/GKLeaderboardView.swift +++ /dev/null @@ -1,36 +0,0 @@ -import GameKit -import SwiftUI -import UIKit - -struct GKLeaderboardView: UIViewControllerRepresentable { - - class Coordinator: NSObject, GKGameCenterControllerDelegate { - - // MARK: Lifecycle - - init(_ parent: GKLeaderboardView) { - self.parent = parent - } - - // MARK: Internal - - var parent: GKLeaderboardView - - func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) { - gameCenterViewController.dismiss(animated: true) - } - - } - - func makeUIViewController(context: Context) -> some UIViewController { - let viewController = GKGameCenterViewController(leaderboardID: "riddle_progress", playerScope: .friendsOnly, timeScope: .allTime) - viewController.gameCenterDelegate = context.coordinator - return viewController - } - - func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {} - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } -} diff --git a/Riddler/GameKit/GKManager.swift b/Riddler/GameKit/GKManager.swift deleted file mode 100644 index a8dcc13..0000000 --- a/Riddler/GameKit/GKManager.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation -import GameKit - -// MARK: - GKManager - -class GKManager { - - // MARK: Lifecycle - - private init() {} - - // MARK: Internal - - static let shared = GKManager() - - var isAuthenticated: Bool = false - -} diff --git a/Riddler/GameKit/UIViewController+Child.swift b/Riddler/GameKit/UIViewController+Child.swift deleted file mode 100644 index 8662a91..0000000 --- a/Riddler/GameKit/UIViewController+Child.swift +++ /dev/null @@ -1,33 +0,0 @@ -import UIKit - -@nonobjc extension UIViewController { - - func add(_ child: UIViewController) { - addChild(child) - child.view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(child.view) - NSLayoutConstraint.activate([ - child.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0), - child.view.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0), - child.view.topAnchor.constraint(equalTo: view.topAnchor, constant: 0), - child.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0) - ]) - child.didMove(toParent: self) - } - - func remove() { - guard parent != nil else { - return - } - - willMove(toParent: nil) - view.removeFromSuperview() - removeFromParent() - } - - func removeAll() { - children.forEach { child in - child.remove() - } - } -} diff --git a/Riddler/Gameplay/GameCenterManager.swift b/Riddler/Gameplay/GameCenterManager.swift deleted file mode 100644 index 88895c7..0000000 --- a/Riddler/Gameplay/GameCenterManager.swift +++ /dev/null @@ -1,117 +0,0 @@ -import Foundation -import GameKit -import OSLog - -struct GameCenterManager { - - // MARK: Internal - - func updateLeaderboard(newScore: Int) { - guard GKManager.shared.isAuthenticated else { return } - GKLeaderboard.submitScore(newScore, context: -1, player: GKLocalPlayer.local, leaderboardIDs: ["riddle_progress"]) { error in - if let error { - Logger.gameCenter.error("Failed to submit score with error: \(error)") - return - } - Logger.gameCenter.info("Submitted new score of \(newScore) to Leaderboard") - } - } - - func handleAchiementUpdates(currentAchievements: [String: Double], currentRiddleIndex: Int, lastRiddleStats: RiddleStats) -> [String: Double] { - var updatedAchievements = [String: Double]() - - let totalRiddlesForAchievement: [AchievementKey: Int] = [ - .riddles50: 50, - .riddles40: 40, - .riddles30: 30, - .riddles20: 20, - .riddles10: 10, - .riddles5: 5, - .riddlesFirst: 1 - ] - - // Update riddle-related achievements based on progress - for (achievement, totalRiddlesRequired) in totalRiddlesForAchievement { - let progress = (Double(currentRiddleIndex) / Double(totalRiddlesRequired)) * 100.0 - updatedAchievements[achievement.rawValue] = min(progress, 100.0) - } - - // For achievements based on no help or incorrect guesses, set to 100 if criteria met, else 0 - updatedAchievements[AchievementKey.under5Minutes.rawValue] = lastRiddleStats.timeTaken < 300 ? 100.0 : 0.0 - updatedAchievements[AchievementKey.noHelp.rawValue] = lastRiddleStats.hintsUsed == 0 ? 100.0 : 0.0 - updatedAchievements[AchievementKey.noHelp.rawValue] = lastRiddleStats.incorrectGuesses == 0 ? 100.0 : 0.0 - - handleNewAchievements(currentAchievements: currentAchievements, updatedAchievements: updatedAchievements) - reportAchievements(updatedAchievements) - return updatedAchievements - } - - func setCurrentAchievements(_ currentAchievements: [String: Double]) { - reportAchievements(currentAchievements) - } - - // MARK: Private - - private enum AchievementKey: String { - case riddles50 = "riddles_50" - case riddles40 = "riddles_40" - case riddles30 = "riddles_30" - case riddles20 = "riddles_20" - case riddles10 = "riddles_10" - case riddles5 = "riddles_5" - case riddlesFirst = "riddle_first" - case under5Minutes = "under_5_minutes" - case noHelp = "no_help" - case firstTry = "first_try" - } - - private func handleNewAchievements(currentAchievements: [String: Double], updatedAchievements: [String: Double]) { - var newlyCompletedAchievements: [String] = [] - for (achievementId, newProgress) in updatedAchievements { - let oldProgress = currentAchievements[achievementId] ?? 0.0 - if newProgress == 100.0, oldProgress < 100.0 { - newlyCompletedAchievements.append(achievementId) - } - } - - guard !newlyCompletedAchievements.isEmpty else { return } - Logger.gameplay.info("New achievements unlocked: \(newlyCompletedAchievements)") - } - - private func reportAchievements(_ storedAchievements: [String: Double]) { - guard GKManager.shared.isAuthenticated else { return } - GKAchievement.loadAchievements { remoteAchievements, error in - if let error { - Logger.gameCenter.error("Failed to fetch remote achievements with error: \(error)") - return - } - - guard let remoteAchievements else { return } - let remoteAchievementsDict = Dictionary(uniqueKeysWithValues: remoteAchievements.map { ($0.identifier, $0.percentComplete) }) - - let achievementsToReport = storedAchievements.compactMap { achievementId, percentComplete -> GKAchievement? in - guard percentComplete != 0 else { return nil } - - // Only create a GKAchievement object if the local and remote achievements don't match - if let remotePercentComplete = remoteAchievementsDict[achievementId], remotePercentComplete == percentComplete { - return nil // This achievement matches the remote one; no need to report it - } - - let achievementToUpdate = GKAchievement(identifier: achievementId) - achievementToUpdate.percentComplete = percentComplete - Logger.gameCenter.info("Reporting \(achievementId) with \(percentComplete)% completion") - return achievementToUpdate - } - - guard !achievementsToReport.isEmpty else { return } - GKAchievement.report(achievementsToReport) { error in - if let error { - Logger.gameCenter.error("Failed to report achievements with error: \(error)") - } else { - Logger.gameCenter.info("Successfully reported achievements.") - } - } - } - } - -} diff --git a/Riddler/Gameplay/GameplayManager.swift b/Riddler/Gameplay/GameplayManager.swift deleted file mode 100644 index ed27c94..0000000 --- a/Riddler/Gameplay/GameplayManager.swift +++ /dev/null @@ -1,298 +0,0 @@ -import Combine -import Foundation -import GameKit -import OSLog -import SwiftUI - -// MARK: - NavigationRoute - -enum NavigationRoute { - case riddle - case hint - case victory - case settings -} - -// MARK: - GameplayManager - -final class GameplayManager: ObservableObject { - - // MARK: Lifecycle - - init( - storageManager: StorageManaging = StorageManager(), - gameCenterManager: GameCenterManager = GameCenterManager(), - statsMapper: StatsMapper = StatsMapper() - ) { - self.storageManager = storageManager - self.gameCenterManager = gameCenterManager - self.statsMapper = statsMapper - player = storageManager.loadPlayer() - } - - // MARK: Internal - - let riddles: [Riddle] = Bundle.main.decode("riddles.json") - - @Published var navigationPath = NavigationPath() - @Published var player: Player - - @Published var showCorrect = false - @Published var showVictoryStats = false - - @Published var showAchievementToast = false - @Published var showNoMoreHintsToast = false - @Published var showShareCorrectToast = false - @Published var showShareVictoryToast = false - - @Published var showInterstitialAd = false - @Published var showRewardedAd = false - - @Published var showWatchAdDialog = false - @Published var showPrivacySettingsDialog = false - @Published var showHintsRewardedToast = false - - @Published var showAdPrivacyForm = false - @Published var showAdPrivacyFormError = false - - @Published var showAppStoreReviewPrompt = false - - @Published var answerString = "" - - var currentRiddle: Riddle? { - riddles[safeIndex: player.currentRiddleIndex] - } - - var riddleNumberString: String { - "\(player.currentRiddleIndex + 1)/\(riddles.count)" - } - - var hintString: String { - let hintsToShow = riddles[player.currentRiddleIndex].hints[0 ..< player.currentRiddleHintsUsed] - guard !hintsToShow.isEmpty else { return "No hints used." } - return hintsToShow.joined(separator: "\n\n") - } - - var canRequestMoreHints: Bool { - AdsManager.shared.hasConsented && RDRewardedAd.shared.rewardedAd != nil - } - - var victoryStats: [DisplayStat] { - let stats = statsMapper.mapVictoryStats(for: player) - Analytics.shared.event(.stats(stats)) - return stats - } - - // MARK: - Menu - - func startTapped() { - if player.currentRiddleIndex >= riddles.count { - navigationPath.append(NavigationRoute.victory) - } else { - navigationPath.append(NavigationRoute.riddle) - } - } - - func handleGameCenterAuthenticated() { - gameCenterManager.setCurrentAchievements(player.achievements) - gameCenterManager.updateLeaderboard(newScore: riddleLeaderboardScore) - } - - // MARK: - Riddle - - func checkAnswer() { - Analytics.shared.event(.tapEnterAnswer()) - let answer = answerString.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) - let isCorrectAnswer = riddles[player.currentRiddleIndex].answers.contains(answer) - Logger.gameplay.info("Entered: \(answer), Is correct: \(isCorrectAnswer)") - isCorrectAnswer ? handleCorrectGuess(answer) : handleIncorrectGuess(answer) - storageManager.savePlayer(player) - } - - func correctAppeared() { - answerString = "" - - player.riddleStats.append(.init( - startTime: player.currentRiddleStartTime, - incorrectGuesses: player.currentRiddleIncorrectGuesses, - hintsUsed: player.currentRiddleHintsUsed - )) - - withAnimation(.easeInOut(duration: 1.0)) { - player.currentRiddleIndex += 1 - player.currentRiddleStartTime = Date() - player.currentRiddleIncorrectGuesses = 0 - player.currentRiddleHintsUsed = 0 - } - - - gameCenterManager.updateLeaderboard(newScore: riddleLeaderboardScore) - if let lastRiddleStats = player.riddleStats.last { - let updatedAchievements = gameCenterManager.handleAchiementUpdates( - currentAchievements: player.achievements, - currentRiddleIndex: player.currentRiddleIndex, - lastRiddleStats: lastRiddleStats - ) - player.achievements = updatedAchievements - } - - storageManager.savePlayer(player) - } - - func correctDismissed() { - guard player.currentRiddleIndex < riddles.count else { return } - - let showedAppStoreReview = handleShowingAppStoreReviewPrompt() - guard !showedAppStoreReview else { return } - - handleShowingInterstitialAd() - } - - // MARK: - Hints - - func openHints() { - Analytics.shared.event(.tapOpenHints()) - navigationPath.append(NavigationRoute.hint) - } - - func useHint() { - Analytics.shared.event(.tapUseHint(numberOfHints: player.hintsAvailable)) - Logger.gameplay.info("Player tried to use hint") - let riddleHasUnusedHints = player.currentRiddleHintsUsed < riddles[player.currentRiddleIndex].hints.count - guard riddleHasUnusedHints else { - withAnimation { - showNoMoreHintsToast = true - } - Logger.gameplay.info("Unable to use hint as riddle has no more hints") - return - } - - let playerHasHints = player.hintsAvailable > 0 - if playerHasHints { - player.currentRiddleHintsUsed += 1 - player.hintsAvailable -= 1 - Logger.gameplay.info("Player used a hint") - } - } - - func requestHints() { - showRewardedAd = true - } - - func handleHintReward() { - Analytics.shared.event(.hintsRewarded()) - player.hintsAvailable += 3 - storageManager.savePlayer(player) - withAnimation { - showHintsRewardedToast = true - } - } - - // MARK: Victory - - func victoryStatsTapped() { - Analytics.shared.event(.tapOpenStats()) - showVictoryStats = true - } - - func shareVictory() { - Analytics.shared.event(.tapShareVictory()) - UIPasteboard.general.string = player.victoryShareString - withAnimation { - showShareVictoryToast = true - } - } - - func shareCorrect() { - UIPasteboard.general.string = player.correctShareString - withAnimation { - showShareCorrectToast = true - } - } - - func debugResetTapped() { - player = Player() - storageManager.savePlayer(player) - } - - // MARK: - Ads - - func interstitialAdDidFinish() { - showInterstitialAd = false - lastAdWatchDate = Date() - } - - func rewardedAdDidFinish() { - showRewardedAd = false - } - - // MARK: - App Store Review - - func promptedForAppStoreReview() { - showAppStoreReviewPrompt = false - lastAppStoreReviewPromptDate = Date() - } - - // MARK: Private - - private let storageManager: StorageManaging - private let gameCenterManager: GameCenterManager - private let statsMapper: StatsMapper - private let userDefaults: UserDefaults = .standard - private var cancellables = Set() - - private var lastAdWatchDate: Date? - - private let lastAppStoreReviewPromptKey = "LastAppStoreReviewPrompt" - - private var lastAppStoreReviewPromptDate: Date? { - get { - let timeInterval = userDefaults.double(forKey: lastAppStoreReviewPromptKey) - guard timeInterval > 0 else { return nil } - return Date(timeIntervalSinceReferenceDate: timeInterval) - } - set { - let timeInterval = newValue?.timeIntervalSinceReferenceDate - guard let timeInterval else { return } - userDefaults.set(timeInterval, forKey: lastAppStoreReviewPromptKey) - } - } - - private var riddleLeaderboardScore: Int { - min(riddles.count, player.currentRiddleIndex + 1) - } - - private func handleCorrectGuess(_ guess: String) { - Analytics.shared.event(.correctGuess(guess, riddleNumber: player.currentRiddleIndex + 1)) - showCorrect = true - } - - private func handleIncorrectGuess(_ guess: String) { - Analytics.shared.event(.incorrectGuess(guess, riddleNumber: player.currentRiddleIndex + 1)) - withAnimation { - player.currentRiddleIncorrectGuesses += 1 - } - answerString = "" - storageManager.savePlayer(player) - } - - private func handleShowingAppStoreReviewPrompt() -> Bool { - guard player.currentRiddleIndex > 10 else { return false } - - let lastShownLongEnoughAgo = lastAppStoreReviewPromptDate.map { $0.isBefore(Date(timeIntervalSinceNow: .weeks(-20))) } ?? true - guard lastShownLongEnoughAgo else { return false } - - showAppStoreReviewPrompt = true - return true - } - - @discardableResult - private func handleShowingInterstitialAd() -> Bool { - if let lastAdWatchDate, lastAdWatchDate.isAfter(Date(timeIntervalSinceNow: .minutes(-3))) { - return false - } - showInterstitialAd = true - return true - } - -} diff --git a/Riddler/Gameplay/StartupManager.swift b/Riddler/Gameplay/StartupManager.swift deleted file mode 100644 index bd5bce2..0000000 --- a/Riddler/Gameplay/StartupManager.swift +++ /dev/null @@ -1,33 +0,0 @@ -import SwiftUI - -// MARK: - StartupStep - -enum StartupStep { - case gameCenter - case adsConsent - case complete -} - -// MARK: - StartupManager - -final class StartupManager: ObservableObject { - - // MARK: Lifecycle - - init(startupStep: StartupStep = .gameCenter) { - self.startupStep = startupStep - } - - // MARK: Internal - - @Published var startupStep: StartupStep = .gameCenter - - func handleGameCenterComplete() { - startupStep = .adsConsent - } - - func handleAdsConsentcomplete() { - startupStep = .complete - } - -} diff --git a/Riddler/Gameplay/StatsMapper.swift b/Riddler/Gameplay/StatsMapper.swift deleted file mode 100644 index 70bd8f0..0000000 --- a/Riddler/Gameplay/StatsMapper.swift +++ /dev/null @@ -1,62 +0,0 @@ -import Foundation - -struct StatsMapper { - - // MARK: Internal - - func mapVictoryStats(for player: Player) -> [DisplayStat] { - var victoryStats: [DisplayStat?] = [] - victoryStats.append(totalTime(for: player)) - victoryStats.append(quickestTime(for: player)) - victoryStats.append(slowestTime(for: player)) - victoryStats.append(totalGuesses(for: player)) - victoryStats.append(correctFirstGuesses(for: player)) - victoryStats.append(mostIncorrectGuesses(for: player)) - victoryStats.append(hintsUsed(for: player)) - victoryStats.append(correctWithoutHints(for: player)) - return victoryStats.compactMap { $0 } - } - - // MARK: Private - - private func totalTime(for player: Player) -> DisplayStat? { - guard let formattedTimeTaken = player.formattedTimeTaken else { return nil } - return DisplayStat(name: "Total time:", value: formattedTimeTaken) - } - - private func quickestTime(for player: Player) -> DisplayStat { - let quickestTime = player.riddleStats.max(by: { $0.timeTaken > $1.timeTaken })?.formattedTimeTaken ?? "0 seconds" - return DisplayStat(name: "Quickest time:", value: quickestTime) - } - - private func slowestTime(for player: Player) -> DisplayStat { - let slowestTime = player.riddleStats.max(by: { $0.timeTaken < $1.timeTaken })?.formattedTimeTaken ?? "0 seconds" - return DisplayStat(name: "Slowest time:", value: slowestTime) - } - - private func totalGuesses(for player: Player) -> DisplayStat { - let totalGuesses = player.totalGuesses - return DisplayStat(name: "Total guesses:", value: String(totalGuesses)) - } - - private func correctFirstGuesses(for player: Player) -> DisplayStat { - let correctFirstGuesses = player.riddleStats.filter { $0.incorrectGuesses == 0 }.count - return DisplayStat(name: "Correct first guesses:", value: String(correctFirstGuesses)) - } - - private func mostIncorrectGuesses(for player: Player) -> DisplayStat { - let mostIncorrectGuesses = player.riddleStats.map(\.incorrectGuesses).max() ?? 0 - return DisplayStat(name: "Most incorrect guesses:", value: String(mostIncorrectGuesses)) - } - - private func hintsUsed(for player: Player) -> DisplayStat { - let hintsUsed = player.riddleStats.reduce(0) { $0 + $1.hintsUsed } - return DisplayStat(name: "Hints used:", value: String(hintsUsed)) - } - - private func correctWithoutHints(for player: Player) -> DisplayStat { - let correctWithoutHints = player.riddleStats.filter { $0.hintsUsed == 0 }.count - return DisplayStat(name: "Answered without hints:", value: String(correctWithoutHints)) - } - -} diff --git a/Riddler/Gameplay/StorageManager.swift b/Riddler/Gameplay/StorageManager.swift deleted file mode 100644 index 3e1a47b..0000000 --- a/Riddler/Gameplay/StorageManager.swift +++ /dev/null @@ -1,109 +0,0 @@ -import Foundation -import OSLog - -// MARK: - StorageManaging - -protocol StorageManaging { - func savePlayer(_ player: Player) - func loadPlayer() -> Player -} - -// MARK: - PreviewStorageManager - -struct PreviewStorageManager: StorageManaging { - func savePlayer(_ player: Player) {} - func loadPlayer() -> Player { - Player() - } -} - -// MARK: - StorageManager - -struct StorageManager: StorageManaging { - - // MARK: Internal - - func savePlayer(_ player: Player) { - Logger.dataStorage.info("Attempting to save player data") - guard let encoded = try? JSONEncoder().encode(player) else { - Logger.dataStorage.error("Failed to encode player data") - return - } - - let playerVersion = PlayerVersion.latest - UserDefaults.standard.set(playerVersion.rawValue, forKey: playerVersionKey) - UserDefaults.standard.set(encoded, forKey: playerVersion.storageKey) - } - - func loadPlayer() -> Player { - Logger.dataStorage.info("Attempting to load player data") - let playerVersion = getPlayerVersion() - guard let data = readPlayerData(for: playerVersion), let playerData = decodePlayerData(playerVersion.decodableType, from: data) else { - Logger.dataStorage.info("Creating a new player") - return Player() - } - return playerData.upgraded() - } - - // MARK: Private - - private enum PlayerVersion: String { - case v1 - case v2 - - // MARK: Lifecycle - - init(rawValue: String?) { - switch rawValue { - case "v2": self = .v2 - default: self = .v1 - } - } - - // MARK: Internal - - static var latest: Self { - .v2 - } - - var storageKey: String { - switch self { - case .v1: "Player" - case .v2: "PlayerV2" - } - } - - var decodableType: PlayerDataConvertible.Type { - switch self { - case .v1: return PlayerV1.self - case .v2: return PlayerV2.self - } - } - - } - - private let playerVersionKey: String = "PlayerVersion" - - private func getPlayerVersion() -> PlayerVersion { - let playerVersion = PlayerVersion(rawValue: UserDefaults.standard.string(forKey: playerVersionKey)) - Logger.dataStorage.info("Current player version: \(playerVersion.rawValue)") - return playerVersion - } - - private func readPlayerData(for playerVersion: PlayerVersion) -> Data? { - guard let data = UserDefaults.standard.data(forKey: playerVersion.storageKey) else { - Logger.dataStorage.info("No player data in UserDefaults for \(playerVersion.rawValue)") - return nil - } - return data - } - - private func decodePlayerData(_ type: T.Type, from data: Data) -> T? where T: Decodable { - guard let decodedData = try? JSONDecoder().decode(type, from: data) else { - Logger.dataStorage.error("Failed to decode player data for \(type).") - return nil - } - return decodedData - } - -} diff --git a/Riddler/Generated/Colors+Generated.swift b/Riddler/Generated/Colors+Generated.swift deleted file mode 100644 index 723417f..0000000 --- a/Riddler/Generated/Colors+Generated.swift +++ /dev/null @@ -1,111 +0,0 @@ -// swiftlint:disable all -// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen - -#if os(macOS) - import AppKit -#elseif os(iOS) - import UIKit -#elseif os(tvOS) || os(watchOS) - import UIKit -#endif -#if canImport(SwiftUI) - import SwiftUI -#endif - -// Deprecated typealiases -@available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0") -public typealias AssetColorTypeAlias = ColorAsset.Color - -// swiftlint:disable superfluous_disable_command file_length implicit_return - -// MARK: - Asset Catalogs - -// swiftlint:disable identifier_name line_length nesting type_body_length type_name -public enum RKColors { - public static let error = ColorAsset(name: "Error") - public static let accent = ColorAsset(name: "accent") - public static let accentDark = ColorAsset(name: "accentDark") - public static let grey = ColorAsset(name: "grey") - public static let grey2 = ColorAsset(name: "grey2") - public static let primary = ColorAsset(name: "primary") - public static let primaryDark = ColorAsset(name: "primaryDark") -} -// swiftlint:enable identifier_name line_length nesting type_body_length type_name - -// MARK: - Implementation Details - -public final class ColorAsset { - public fileprivate(set) var name: String - - #if os(macOS) - public typealias Color = NSColor - #elseif os(iOS) || os(tvOS) || os(watchOS) - public typealias Color = UIColor - #endif - - @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) - public private(set) lazy var color: Color = { - guard let color = Color(asset: self) else { - fatalError("Unable to load color asset named \(name).") - } - return color - }() - - #if os(iOS) || os(tvOS) - @available(iOS 11.0, tvOS 11.0, *) - public func color(compatibleWith traitCollection: UITraitCollection) -> Color { - let bundle = BundleToken.bundle - guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else { - fatalError("Unable to load color asset named \(name).") - } - return color - } - #endif - - #if canImport(SwiftUI) - @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) - public private(set) lazy var swiftUIColor: SwiftUI.Color = { - SwiftUI.Color(asset: self) - }() - #endif - - fileprivate init(name: String) { - self.name = name - } -} - -public extension ColorAsset.Color { - @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) - convenience init?(asset: ColorAsset) { - let bundle = BundleToken.bundle - #if os(iOS) || os(tvOS) - self.init(named: asset.name, in: bundle, compatibleWith: nil) - #elseif os(macOS) - self.init(named: NSColor.Name(asset.name), bundle: bundle) - #elseif os(watchOS) - self.init(named: asset.name) - #endif - } -} - -#if canImport(SwiftUI) -@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) -public extension SwiftUI.Color { - init(asset: ColorAsset) { - let bundle = BundleToken.bundle - self.init(asset.name, bundle: bundle) - } -} -#endif - -// swiftlint:disable convenience_type -private final class BundleToken { - static let bundle: Bundle = { - #if SWIFT_PACKAGE - return Bundle.module - #else - return Bundle(for: BundleToken.self) - #endif - }() -} -// swiftlint:enable convenience_type diff --git a/Riddler/Generated/Fonts+Generated.swift b/Riddler/Generated/Fonts+Generated.swift deleted file mode 100644 index a7bc09d..0000000 --- a/Riddler/Generated/Fonts+Generated.swift +++ /dev/null @@ -1,143 +0,0 @@ -// swiftlint:disable all -// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen - -#if os(macOS) - import AppKit.NSFont -#elseif os(iOS) || os(tvOS) || os(watchOS) - import UIKit.UIFont -#endif -#if canImport(SwiftUI) - import SwiftUI -#endif - -// Deprecated typealiases -@available(*, deprecated, renamed: "FontConvertible.Font", message: "This typealias will be removed in SwiftGen 7.0") -public typealias Font = FontConvertible.Font - -// swiftlint:disable superfluous_disable_command file_length implicit_return - -// MARK: - Fonts - -// swiftlint:disable identifier_name line_length type_body_length -public enum RKFonts { - public enum Abel { - public static let regular = FontConvertible(name: "Abel-Regular", family: "Abel", path: "Abel-Regular.ttf") - public static let all: [FontConvertible] = [regular] - } - public enum Teko { - public static let regular = FontConvertible(name: "Teko-Regular", family: "Teko", path: "Teko-Regular.ttf") - public static let all: [FontConvertible] = [regular] - } - public static let allCustomFonts: [FontConvertible] = [Abel.all, Teko.all].flatMap { $0 } - public static func registerAllCustomFonts() { - allCustomFonts.forEach { $0.register() } - } -} -// swiftlint:enable identifier_name line_length type_body_length - -// MARK: - Implementation Details - -public struct FontConvertible { - public let name: String - public let family: String - public let path: String - - #if os(macOS) - public typealias Font = NSFont - #elseif os(iOS) || os(tvOS) || os(watchOS) - public typealias Font = UIFont - #endif - - public func font(size: CGFloat) -> Font { - guard let font = Font(font: self, size: size) else { - fatalError("Unable to initialize font '\(name)' (\(family))") - } - return font - } - - #if canImport(SwiftUI) - @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) - public func swiftUIFont(size: CGFloat) -> SwiftUI.Font { - return SwiftUI.Font.custom(self, size: size) - } - - @available(iOS 14.0, tvOS 14.0, watchOS 7.0, macOS 11.0, *) - public func swiftUIFont(fixedSize: CGFloat) -> SwiftUI.Font { - return SwiftUI.Font.custom(self, fixedSize: fixedSize) - } - - @available(iOS 14.0, tvOS 14.0, watchOS 7.0, macOS 11.0, *) - public func swiftUIFont(size: CGFloat, relativeTo textStyle: SwiftUI.Font.TextStyle) -> SwiftUI.Font { - return SwiftUI.Font.custom(self, size: size, relativeTo: textStyle) - } - #endif - - public func register() { - // swiftlint:disable:next conditional_returns_on_newline - guard let url = url else { return } - CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil) - } - - fileprivate func registerIfNeeded() { - #if os(iOS) || os(tvOS) || os(watchOS) - if !UIFont.fontNames(forFamilyName: family).contains(name) { - register() - } - #elseif os(macOS) - if let url = url, CTFontManagerGetScopeForURL(url as CFURL) == .none { - register() - } - #endif - } - - fileprivate var url: URL? { - // swiftlint:disable:next implicit_return - return BundleToken.bundle.url(/service/forresource: path, withExtension: nil) - } -} - -public extension FontConvertible.Font { - convenience init?(font: FontConvertible, size: CGFloat) { - font.registerIfNeeded() - self.init(name: font.name, size: size) - } -} - -#if canImport(SwiftUI) -@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) -public extension SwiftUI.Font { - static func custom(_ font: FontConvertible, size: CGFloat) -> SwiftUI.Font { - font.registerIfNeeded() - return custom(font.name, size: size) - } -} - -@available(iOS 14.0, tvOS 14.0, watchOS 7.0, macOS 11.0, *) -public extension SwiftUI.Font { - static func custom(_ font: FontConvertible, fixedSize: CGFloat) -> SwiftUI.Font { - font.registerIfNeeded() - return custom(font.name, fixedSize: fixedSize) - } - - static func custom( - _ font: FontConvertible, - size: CGFloat, - relativeTo textStyle: SwiftUI.Font.TextStyle - ) -> SwiftUI.Font { - font.registerIfNeeded() - return custom(font.name, size: size, relativeTo: textStyle) - } -} -#endif - -// swiftlint:disable convenience_type -private final class BundleToken { - static let bundle: Bundle = { - #if SWIFT_PACKAGE - return Bundle.module - #else - return Bundle(for: BundleToken.self) - #endif - }() -} -// swiftlint:enable convenience_type diff --git a/Riddler/Models/DisplayStat.swift b/Riddler/Models/DisplayStat.swift deleted file mode 100644 index 3415b77..0000000 --- a/Riddler/Models/DisplayStat.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -struct DisplayStat { - let name: String - let value: String -} diff --git a/Riddler/Models/GameViewModel.swift b/Riddler/Models/GameViewModel.swift new file mode 100644 index 0000000..799370b --- /dev/null +++ b/Riddler/Models/GameViewModel.swift @@ -0,0 +1,192 @@ +import Foundation +import Firebase +import FirebaseAnalytics +import GameKit + +class GameViewModel: ObservableObject { + + @Published var player: Player = Player() + let riddles: [Riddle] = Bundle.main.decode("riddles.json") + + init() { + loadPlayer() + } + + // MARK: - UI computed variables + var riddleNumString: String { + "\(player.riddleDisplay)/\(riddles.count)" + } + + func hintString() -> String { + var hintString: String = "" + var hintArray: [String] = [] + + for x in 0.. (Bool, Bool) { + let answer = inputAnswer.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) + + if answerContained(answer: answer) { + var newAchievements: Bool = false + newAchievements = player.correctGuess() + + savePlayer() + + checkAchievements() + updateLeaderboard() + + // Log event to firebase + guessEvent(guess: answer, correct: true, player: self.player) + + return (true, newAchievements) + } else { + player.incorrectGuess() + savePlayer() + + // Log event to firebase + guessEvent(guess: answer, correct: false, player: player) + + return (false, false) + } + } + + func answerContained(answer: String) -> Bool { + for correctAnswer in riddles[player.riddle].answers { + if answer == correctAnswer { + return true + } + } + return false + } + + func useHint() { + // Change player stats to show hints used + player.useHint(riddleHints: riddles[player.riddle].hints.count) + objectWillChange.send() + savePlayer() + + // Log event to firebase + useHintEvent(player: player) + } + + func hintUsable() -> Bool { + return player.hintUsable(riddleHints: riddles[player.riddle].hints.count) + } + + func hintReward() { + // Increase player hints + player.addHints() + objectWillChange.send() + savePlayer() + + // Log event to firebase + unlockHintsEvent(player: player) + } + + func setRatingAsk() { + player.ratingAsked = true + savePlayer() + } + + func updateRiddleStart() { + player.updateRiddleStart() + objectWillChange.send() + savePlayer() + } + +// func showAd() { +// player.timeOfLastAd = Date() +// objectWillChange.send() +// savePlayer() +// } + + // MARK: - Achievements + func checkAchievements() { + if GameKitManager.shared.gameKitEnabled { + GKAchievement.loadAchievements(completionHandler: { (achievements: [GKAchievement]?, error: Error? ) in + + var achievementsToReport: [GKAchievement] = [] + + for (key, value) in self.player.achievements { + if value == true { + let achievementID = key + var achievement: GKAchievement? = nil + + // Check if achievement with that ID already exists + achievement = achievements?.first(where: { $0.identifier == achievementID }) + + // If it doesn't add it to list of achievements to report + if achievement == nil { + let completedAchievement = GKAchievement(identifier: key) + completedAchievement.percentComplete = 100 + achievementsToReport.append(completedAchievement) + print("GC Achievement Added: \(key) -> \(value)") + } + } + } + + if achievementsToReport.count > 0 { + GKAchievement.report(achievementsToReport, withCompletionHandler: {(error: Error?) in + if error != nil { + print("GC Error: \(String(describing: error))") + } + }) + } + + }) + } + } + + // MARK: - Leaderboard + func updateLeaderboard() { + if GameKitManager.shared.gameKitEnabled { + var score: Int = player.riddle + + if player.completed { + score = 50 + } + + GKLeaderboard.submitScore(score, context: -1, player: GKLocalPlayer.local, leaderboardIDs: ["riddle_progress"], completionHandler: { (error: Error?) in + if error != nil { + print("GC Error: \(String(describing: error?.localizedDescription))") + } else { + print("GC Score Updated") + } + }) + } + } + + // MARK: - Player data handling + func savePlayer() { + if let encoded = try? JSONEncoder().encode(player) { + UserDefaults.standard.set(encoded, forKey: "Player") + } + } + + func loadPlayer() { + if TestVariables.RESET_DATA { + player = Player() + } else { + if let loaded = UserDefaults.standard.data(forKey: "Player") { + if let decoded = try? JSONDecoder().decode(Player.self, from: loaded) { + player = decoded + } + } + } + } + +} diff --git a/Riddler/Models/Player.swift b/Riddler/Models/Player.swift new file mode 100644 index 0000000..bfb0f3c --- /dev/null +++ b/Riddler/Models/Player.swift @@ -0,0 +1,353 @@ +import Foundation + +class Player: Codable, ObservableObject { + + // MARK: - Variables + @Published var riddle: Int + @Published var hints: Int + @Published var completed: Bool + var ratingAsked: Bool + + var startTime: Date + var endTime: Date + var incorrectGuesses: Int + @Published var hintsUsed: Int + @Published var riddleStats: [Stats] +// var timeOfLastAd: Date + + var achievements: [String: Bool] + + // MARK: - computed variables + var riddleDisplay: Int { + if completed { + return 51 + } else { + return riddle + 1 + } + } + + var guesses: Int { + if completed { + return incorrectGuesses + 50 + } else { + return incorrectGuesses + riddle + } + } + + var time: Int { + timeDifference(startTime: startTime, endTime: endTime) + } + var timeString: String { + timeDifferenceString(startTime: startTime, endTime: endTime) + } + + var timeSoFar: Int { + timeDifference(startTime: startTime, endTime: Date()) + } + + var timeSoFarString: String { + timeDifferenceString(startTime: startTime, endTime: Date()) + } + + var currentRiddleStats: Stats { + riddleStats[riddle] + } + + var previousRiddleStats: Stats { + riddleStats[riddle - 1] + } + + init() { + self.riddle = TestVariables.START_RIDDLE + self.hints = ConfigVariables.HINT_START_AMOUNT + self.completed = TestVariables.COMPLETED + self.ratingAsked = false + self.startTime = Date() + self.endTime = Date() + self.incorrectGuesses = 0 + self.hintsUsed = 0 + self.riddleStats = [Stats]() +// self.timeOfLastAd = Date() + self.achievements = [:] + + for _ in 1...50 { + riddleStats.append(Stats()) + } + } + + // MARK: - encoding + enum CodingKeys: CodingKey { + case riddle, hints, completed, ratingAsked, startTime, endTime, incorrectGuesses, hintsUsed, riddleStats, timeOfLastAd, achievements + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + riddle = try container.decode(Int.self, forKey: .riddle) + hints = try container.decode(Int.self, forKey: .hints) + completed = try container.decode(Bool.self, forKey: .completed) + ratingAsked = try container.decode(Bool.self, forKey: .ratingAsked) + startTime = try container.decode(Date.self, forKey: .startTime) + endTime = try container.decode(Date.self, forKey: .endTime) + incorrectGuesses = try container.decode(Int.self, forKey: .incorrectGuesses) + hintsUsed = try container.decode(Int.self, forKey: .hintsUsed) + riddleStats = try container.decode([Stats].self, forKey: .riddleStats) +// timeOfLastAd = try container.decode(Date.self, forKey: .timeOfLastAd) + achievements = try container.decode([String: Bool].self, forKey: .achievements) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(riddle, forKey: .riddle) + try container.encode(hints, forKey: .hints) + try container.encode(completed, forKey: .completed) + try container.encode(ratingAsked, forKey: .ratingAsked) + try container.encode(startTime, forKey: .startTime) + try container.encode(endTime, forKey: .endTime) + try container.encode(incorrectGuesses, forKey: .incorrectGuesses) + try container.encode(hintsUsed, forKey: .hintsUsed) + try container.encode(riddleStats, forKey: .riddleStats) +// try container.encode(timeOfLastAd, forKey: .timeOfLastAd) + try container.encode(achievements, forKey: .achievements) + } + + // MARK: - Gameplay functions + func start() { + startTime = Date() + } + + func correctGuess() -> Bool { + // Check if that was last riddle + if riddle == 49 { + if !completed { + endTime = Date() + riddleStats[riddle].endTime = Date() + completed = true + return updateAchievements() + } + } else { + riddleStats[riddle].endTime = Date() + riddle += 1 + riddleStats[riddle].startTime = Date() + return updateAchievements() + } + return false + } + + func incorrectGuess() { + incorrectGuesses += 1 + riddleStats[riddle].incorrectGuesses += 1 + } + + func useHint(riddleHints: Int) { + if hints > 0 && riddleStats[riddle].hintsUsed < riddleHints { + hintsUsed += 1 + riddleStats[riddle].hintsUsed += 1 + hints -= 1 + } + } + + func hintUsable(riddleHints: Int) -> Bool { + return (riddleStats[riddle].hintsUsed < riddleHints) + } + + func addHints() { + hints += ConfigVariables.HINT_UNLOCK_AMOUNT + } + + func askRating() -> Bool { + if !ratingAsked && riddle > 5 { + return true + } + return false + } + + func updateRiddleStart() { + riddleStats[riddle].startTime = Date() + } + + private struct AchievementKeys { + static let riddles50: String = "riddles_50" + static let riddles40: String = "riddles_40" + static let riddles30: String = "riddles_30" + static let riddles20: String = "riddles_20" + static let riddles10: String = "riddles_10" + static let riddles5: String = "riddles_5" + static let riddlesFirst: String = "riddles_first" + static let under5Minutes: String = "under_5_minutes" + static let noHelp: String = "no_help" + static let firstTry: String = "first_try" + } + + private func updateAchievements() -> Bool { + + var newAchievement: Bool = false + + // 50 riddles + if completed && achievements[AchievementKeys.riddles50] == nil { + achievements.updateValue(true, forKey: AchievementKeys.riddles50) + newAchievement = true + } + + // 40 riddles + if riddle >= 40 && achievements[AchievementKeys.riddles40] == nil { + achievements.updateValue(true, forKey: AchievementKeys.riddles40) + newAchievement = true + } + + // 30 riddles + if riddle >= 30 && achievements[AchievementKeys.riddles30] == nil { + achievements.updateValue(true, forKey: AchievementKeys.riddles30) + newAchievement = true + } + + // 20 riddles + if riddle >= 20 && achievements[AchievementKeys.riddles20] == nil { + achievements.updateValue(true, forKey: AchievementKeys.riddles20) + newAchievement = true + } + + // 10 riddles + if riddle >= 10 && achievements[AchievementKeys.riddles10] == nil { + achievements.updateValue(true, forKey: AchievementKeys.riddles10) + newAchievement = true + } + + // 5 riddles + if riddle >= 5 && achievements[AchievementKeys.riddles5] == nil { + achievements.updateValue(true, forKey: AchievementKeys.riddles5) + newAchievement = true + } + + // First riddle + if riddle >= 1 && achievements[AchievementKeys.riddlesFirst] == nil { + achievements.updateValue(true, forKey: AchievementKeys.riddlesFirst) + newAchievement = true + } + + // Under 5 minutes + if riddleStats[riddle - 1].time < 300 && achievements[AchievementKeys.under5Minutes] == nil { + achievements.updateValue(true, forKey: AchievementKeys.under5Minutes) + newAchievement = true + } + + // No hints used + if riddleStats[riddle - 1].hintsUsed == 0 && achievements[AchievementKeys.noHelp] == nil { + achievements.updateValue(true, forKey: AchievementKeys.noHelp) + newAchievement = true + } + + // First try + if riddleStats[riddle - 1].incorrectGuesses == 0 && achievements[AchievementKeys.firstTry] == nil { + achievements.updateValue(true, forKey: AchievementKeys.firstTry) + newAchievement = true + } + + return newAchievement + + } + +// func shouldShowAd() -> Bool { +// // Show ad if longer than 3 mins since last ad +// return timeDifference(startTime: timeOfLastAd, endTime: Date()) > ConfigVariables.TIME_BETWEEN_ADS +// } + + // MARK: - Stats functions + func victoryStats() -> [String] { + var victoryStats: [String] = [] + + victoryStats.append(timeString) + victoryStats.append(quickestTime()) + victoryStats.append(slowestTime()) + victoryStats.append(String(guesses)) + victoryStats.append(String(correctFirstGuesses())) + victoryStats.append(String(mostIncorrectGuesses())) + victoryStats.append(String(hintsUsed)) + victoryStats.append(String(riddlesWithoutHints())) + + return victoryStats + } + + func quickestTime() -> String { + var quickestTime: Int = riddleStats[0].time + var quickestRiddle: Int = 0 + + for x in 1.. String { + var slowestTime: Int = riddleStats[0].time + var slowestRiddle: Int = 0 + + for x in 1.. slowestTime { + slowestTime = time + slowestRiddle = x + } + } + return riddleStats[slowestRiddle].timeString + } + + func correctFirstGuesses() -> Int { + var correctFirsts: Int = 0 + + for x in 0.. Int { + var mostIncorrect: Int = riddleStats[0].incorrectGuesses + + for x in 1.. mostIncorrect { + mostIncorrect = riddleStats[x].incorrectGuesses + } + } + return mostIncorrect + } + + func riddlesWithoutHints() -> Int { + var noHints: Int = 0 + + for x in 0.. String { + return """ + Riddler #\(riddleDisplay - 1) + \(riddleStats[riddle - 1].timeString) + \(riddleStats[riddle - 1].guessesString) + rddle.me/this + """ + } + + func playerResults() -> String { + return """ + Riddler - Defeated + \(timeString) + \(guesses) guesses + rddle.me/this + """ + } + +} diff --git a/Riddler/Models/PlayerDataConvertible.swift b/Riddler/Models/PlayerDataConvertible.swift deleted file mode 100644 index 0c23e9f..0000000 --- a/Riddler/Models/PlayerDataConvertible.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -protocol PlayerDataConvertible: Codable { - func upgraded() -> Player -} diff --git a/Riddler/Models/PlayerV1.swift b/Riddler/Models/PlayerV1.swift deleted file mode 100644 index 498c6e6..0000000 --- a/Riddler/Models/PlayerV1.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Combine -import Foundation - -// MARK: - PlayerV1 - -struct PlayerV1 { - - // MARK: Lifecycle - - @available(*, deprecated, message: "Creating new PlayerV1 is no longer supported") - init() { - riddle = 0 - hints = 3 - completed = false - startTime = Date() - endTime = Date() - incorrectGuesses = 0 - hintsUsed = 0 - riddleStats = [RiddleStats](repeating: .init(), count: 50) - achievements = [:] - } - - // MARK: Internal - - var riddle: Int - var hints: Int - var completed: Bool - var startTime: Date - var endTime: Date - var incorrectGuesses: Int - var hintsUsed: Int - var riddleStats: [RiddleStats] - var achievements: [String: Bool] - -} - -// MARK: PlayerDataConvertible - -extension PlayerV1: PlayerDataConvertible { - func upgraded() -> Player { - Player(playerV1: self) - } -} diff --git a/Riddler/Models/PlayerV2.swift b/Riddler/Models/PlayerV2.swift deleted file mode 100644 index 7c846c5..0000000 --- a/Riddler/Models/PlayerV2.swift +++ /dev/null @@ -1,95 +0,0 @@ -import Foundation - -typealias Player = PlayerV2 - -// MARK: - PlayerV2 - -struct PlayerV2: Codable { - - // MARK: Lifecycle - - init() { - currentRiddleIndex = 0 - currentRiddleStartTime = Date() - currentRiddleHintsUsed = 0 - currentRiddleIncorrectGuesses = 0 - hintsAvailable = 3 - riddleStats = [RiddleStats]() - achievements = [String: Double]() - } - - init(playerV1: PlayerV1) { - currentRiddleIndex = playerV1.riddle - hintsAvailable = playerV1.hints - - var editedRiddleStats = playerV1.riddleStats.map { RiddleStats(legacyRiddleStats: $0) }.compactMap { $0 } - if let lastRiddleStat = editedRiddleStats.popLast() { - currentRiddleStartTime = lastRiddleStat.startTime - currentRiddleHintsUsed = lastRiddleStat.hintsUsed - currentRiddleIncorrectGuesses = lastRiddleStat.incorrectGuesses - } else { - currentRiddleStartTime = Date() - currentRiddleHintsUsed = 0 - currentRiddleIncorrectGuesses = 0 - } - - riddleStats = editedRiddleStats - achievements = playerV1.achievements.mapValues { isAchieved in - isAchieved ? 100.0 : 0.0 - } - - let brokenAchievementKey = "riddles_first" - let correctAchievementKey = "riddle_first" - - achievements[correctAchievementKey] = achievements[brokenAchievementKey] - achievements.removeValue(forKey: brokenAchievementKey) - } - - // MARK: Internal - - var currentRiddleIndex: Int - var currentRiddleStartTime: Date - var currentRiddleHintsUsed: Int - var currentRiddleIncorrectGuesses: Int - var hintsAvailable: Int - var riddleStats: [RiddleStats] - var achievements: [String: Double] - - var totalGuesses: Int { - riddleStats.reduce(0) { $0 + $1.totalGuesses } - } - - var formattedTimeTaken: String? { - guard let startTime = riddleStats.first?.startTime, let endTime = riddleStats.last?.endTime else { return nil } - return TimeFormatter.timeDifference(from: startTime, to: endTime) - } - - var victoryShareString: String { - var shareString = "Riddler - Defeated\n" - if let formattedTimeTaken { - shareString += "\(formattedTimeTaken)\n" - } - shareString += "\(totalGuesses) guesses\n" - shareString += "rddle.me/this" - return shareString - } - - var correctShareString: String { - var shareString = "Riddler #\(currentRiddleIndex)\n" - if let lastRiddleStats = riddleStats.last { - shareString += "\(lastRiddleStats.formattedTimeTaken)\n" - shareString += "\(lastRiddleStats.totalGuesses) guess\(lastRiddleStats.totalGuesses == 1 ? "" : "es")\n" // TODO: Localise - } - shareString += "rddle.me/this" - return shareString - } - -} - -// MARK: PlayerDataConvertible - -extension PlayerV2: PlayerDataConvertible { - func upgraded() -> Player { - self - } -} diff --git a/Riddler/Models/Riddle.swift b/Riddler/Models/Riddle.swift index 6e99dc7..b2870fc 100644 --- a/Riddler/Models/Riddle.swift +++ b/Riddler/Models/Riddle.swift @@ -1,8 +1,16 @@ import Foundation struct Riddle: Codable, Identifiable { - let id: Int - let question: String - let answers: [String] - let hints: [String] + var id: Int + var question: String + var answers: [String] + var hints: [String] + + init() { + self.id = 0 + self.question = "Question" + self.answers = ["Answer"] + self.hints = ["Hint"] + } + } diff --git a/Riddler/Models/RiddleStats.swift b/Riddler/Models/RiddleStats.swift deleted file mode 100644 index 9aafd12..0000000 --- a/Riddler/Models/RiddleStats.swift +++ /dev/null @@ -1,50 +0,0 @@ -import Foundation - -struct RiddleStats: Codable { - - // MARK: Lifecycle - - @available(*, deprecated, message: "This initializer is no longer used during player creation") - init() { - startTime = Date() - endTime = Date() - incorrectGuesses = 0 - hintsUsed = 0 - } - - init(startTime: Date, endTime: Date = Date(), incorrectGuesses: Int, hintsUsed: Int) { - self.startTime = startTime - self.endTime = endTime - self.incorrectGuesses = incorrectGuesses - self.hintsUsed = hintsUsed - } - - init?(legacyRiddleStats: RiddleStats) { - let timeDiff = legacyRiddleStats.endTime.timeIntervalSinceReferenceDate - legacyRiddleStats.startTime.timeIntervalSinceReferenceDate - guard timeDiff.magnitude > 1 else { return nil } - startTime = legacyRiddleStats.startTime - endTime = legacyRiddleStats.endTime - incorrectGuesses = legacyRiddleStats.incorrectGuesses - hintsUsed = legacyRiddleStats.hintsUsed - } - - // MARK: Internal - - let startTime: Date - let endTime: Date - let incorrectGuesses: Int - let hintsUsed: Int - - var timeTaken: Double { - endTime.timeIntervalSince(startTime) - } - - var formattedTimeTaken: String { - TimeFormatter.timeDifference(from: startTime, to: endTime) - } - - var totalGuesses: Int { - incorrectGuesses + 1 - } - -} diff --git a/Riddler/Models/Stats.swift b/Riddler/Models/Stats.swift new file mode 100644 index 0000000..e1e076e --- /dev/null +++ b/Riddler/Models/Stats.swift @@ -0,0 +1,37 @@ +import Foundation + +struct Stats: Codable { + var startTime: Date + var endTime: Date + + var incorrectGuesses: Int + var hintsUsed: Int + + init() { + self.startTime = Date() + self.endTime = Date() + self.incorrectGuesses = 0 + self.hintsUsed = 0 + } + + var time: Int { + timeDifference(startTime: startTime, endTime: endTime) + } + + var timeString: String { + timeDifferenceString(startTime: startTime, endTime: endTime) + } + + var guesses: Int { + incorrectGuesses + 1 + } + + var guessesString: String { + if incorrectGuesses == 0 { + return "1 guess" + } else { + return "\(incorrectGuesses + 1) guesses" + } + } + +} diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/1024.png b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..47ba459 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/114.png b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..1b8068c Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/120.png b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..bdb9a34 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/180.png b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..9b081c6 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/29.png b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..86abfc4 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/40.png b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..29ca776 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/57.png b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..f9beb71 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/58.png b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..0179a25 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/60.png b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..39833e5 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/80.png b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..b7d1ce3 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/87.png b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..7df369b Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/App Icon.png b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/App Icon.png deleted file mode 100644 index 9f0e23d..0000000 Binary files a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/App Icon.png and /dev/null differ diff --git a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index 1e3ebc4..af727e0 100644 --- a/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Riddler/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,9 +1,75 @@ { "images" : [ { - "filename" : "App Icon.png", - "idiom" : "universal", - "platform" : "ios", + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", "size" : "1024x1024" } ], diff --git a/Riddler/Resources/Colors.xcassets/Contents.json b/Riddler/Resources/Assets.xcassets/Backgrounds/Contents.json similarity index 100% rename from Riddler/Resources/Colors.xcassets/Contents.json rename to Riddler/Resources/Assets.xcassets/Backgrounds/Contents.json diff --git a/Riddler/Resources/Assets.xcassets/Backgrounds/bg_accent.imageset/Background_Accent.png b/Riddler/Resources/Assets.xcassets/Backgrounds/bg_accent.imageset/Background_Accent.png new file mode 100644 index 0000000..a81c5eb Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Backgrounds/bg_accent.imageset/Background_Accent.png differ diff --git a/Riddler/Resources/Assets.xcassets/Backgrounds/bg_accent.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Backgrounds/bg_accent.imageset/Contents.json new file mode 100644 index 0000000..0ae2316 --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Backgrounds/bg_accent.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Background_Accent.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riddler/Resources/Assets.xcassets/Backgrounds/bg_primary.imageset/Background_1.png b/Riddler/Resources/Assets.xcassets/Backgrounds/bg_primary.imageset/Background_1.png new file mode 100644 index 0000000..233d17e Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Backgrounds/bg_primary.imageset/Background_1.png differ diff --git a/Riddler/Resources/Assets.xcassets/Backgrounds/bg_primary.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Backgrounds/bg_primary.imageset/Contents.json new file mode 100644 index 0000000..f8c1b64 --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Backgrounds/bg_primary.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Background_1.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riddler/Resources/Colors.xcassets/accent.colorset/Contents.json b/Riddler/Resources/Assets.xcassets/Colors/Accent.colorset/Contents.json similarity index 75% rename from Riddler/Resources/Colors.xcassets/accent.colorset/Contents.json rename to Riddler/Resources/Assets.xcassets/Colors/Accent.colorset/Contents.json index cb63ed3..07623a3 100644 --- a/Riddler/Resources/Colors.xcassets/accent.colorset/Contents.json +++ b/Riddler/Resources/Assets.xcassets/Colors/Accent.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "display-p3", "components" : { "alpha" : "1.000", - "blue" : "0xA4", - "green" : "0xDF", - "red" : "0x0B" + "blue" : "0x7B", + "green" : "0xA9", + "red" : "0x49" } }, "idiom" : "universal" diff --git a/Riddler/Resources/Colors.xcassets/accentDark.colorset/Contents.json b/Riddler/Resources/Assets.xcassets/Colors/AccentDark.colorset/Contents.json similarity index 75% rename from Riddler/Resources/Colors.xcassets/accentDark.colorset/Contents.json rename to Riddler/Resources/Assets.xcassets/Colors/AccentDark.colorset/Contents.json index b72ef9a..90d408a 100644 --- a/Riddler/Resources/Colors.xcassets/accentDark.colorset/Contents.json +++ b/Riddler/Resources/Assets.xcassets/Colors/AccentDark.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "display-p3", "components" : { "alpha" : "1.000", - "blue" : "0x8F", - "green" : "0xC2", - "red" : "0x0A" + "blue" : "0x6C", + "green" : "0x93", + "red" : "0x3F" } }, "idiom" : "universal" diff --git a/Riddler/Resources/Assets.xcassets/Colors/Contents.json b/Riddler/Resources/Assets.xcassets/Colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riddler/Resources/Colors.xcassets/grey.colorset/Contents.json b/Riddler/Resources/Assets.xcassets/Colors/Disabled.colorset/Contents.json similarity index 75% rename from Riddler/Resources/Colors.xcassets/grey.colorset/Contents.json rename to Riddler/Resources/Assets.xcassets/Colors/Disabled.colorset/Contents.json index f9f4859..89a0900 100644 --- a/Riddler/Resources/Colors.xcassets/grey.colorset/Contents.json +++ b/Riddler/Resources/Assets.xcassets/Colors/Disabled.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xD0", - "green" : "0xDE", - "red" : "0xAD" + "blue" : "0x5E", + "green" : "0x5E", + "red" : "0x5E" } }, "idiom" : "universal" diff --git a/Riddler/Resources/Colors.xcassets/Error.colorset/Contents.json b/Riddler/Resources/Assets.xcassets/Colors/Error.colorset/Contents.json similarity index 100% rename from Riddler/Resources/Colors.xcassets/Error.colorset/Contents.json rename to Riddler/Resources/Assets.xcassets/Colors/Error.colorset/Contents.json diff --git a/Riddler/Resources/Colors.xcassets/primary.colorset/Contents.json b/Riddler/Resources/Assets.xcassets/Colors/Primary.colorset/Contents.json similarity index 100% rename from Riddler/Resources/Colors.xcassets/primary.colorset/Contents.json rename to Riddler/Resources/Assets.xcassets/Colors/Primary.colorset/Contents.json diff --git a/Riddler/Resources/Colors.xcassets/primaryDark.colorset/Contents.json b/Riddler/Resources/Assets.xcassets/Colors/PrimaryDark.colorset/Contents.json similarity index 100% rename from Riddler/Resources/Colors.xcassets/primaryDark.colorset/Contents.json rename to Riddler/Resources/Assets.xcassets/Colors/PrimaryDark.colorset/Contents.json diff --git a/Riddler/Resources/Colors.xcassets/grey2.colorset/Contents.json b/Riddler/Resources/Assets.xcassets/Colors/Secondary.colorset/Contents.json similarity index 75% rename from Riddler/Resources/Colors.xcassets/grey2.colorset/Contents.json rename to Riddler/Resources/Assets.xcassets/Colors/Secondary.colorset/Contents.json index 830e60c..c41ca8a 100644 --- a/Riddler/Resources/Colors.xcassets/grey2.colorset/Contents.json +++ b/Riddler/Resources/Assets.xcassets/Colors/Secondary.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xB2", - "green" : "0xBE", - "red" : "0x93" + "blue" : "0x8A", + "green" : "0x8A", + "red" : "0x8A" } }, "idiom" : "universal" diff --git a/Riddler/Resources/Assets.xcassets/Colors/SecondaryDark.colorset/Contents.json b/Riddler/Resources/Assets.xcassets/Colors/SecondaryDark.colorset/Contents.json new file mode 100644 index 0000000..5f6e59b --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Colors/SecondaryDark.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x73", + "green" : "0x73", + "red" : "0x73" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riddler/Resources/Assets.xcassets/Icons/Contents.json b/Riddler/Resources/Assets.xcassets/Icons/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Icons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riddler/Resources/Assets.xcassets/primaryBackground.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Icons/add.imageset/Contents.json similarity index 54% rename from Riddler/Resources/Assets.xcassets/primaryBackground.imageset/Contents.json rename to Riddler/Resources/Assets.xcassets/Icons/add.imageset/Contents.json index 670fddf..ecac245 100644 --- a/Riddler/Resources/Assets.xcassets/primaryBackground.imageset/Contents.json +++ b/Riddler/Resources/Assets.xcassets/Icons/add.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "primaryBackground.png", + "filename" : "outline_add_black_48pt_1x.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "primaryBackground@2x.png", + "filename" : "outline_add_black_48pt_2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "primaryBackground@3x.png", + "filename" : "outline_add_black_48pt_3x.png", "idiom" : "universal", "scale" : "3x" } @@ -19,5 +19,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" } } diff --git a/Riddler/Resources/Assets.xcassets/Icons/add.imageset/outline_add_black_48pt_1x.png b/Riddler/Resources/Assets.xcassets/Icons/add.imageset/outline_add_black_48pt_1x.png new file mode 100644 index 0000000..e00d6d1 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/add.imageset/outline_add_black_48pt_1x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/add.imageset/outline_add_black_48pt_2x.png b/Riddler/Resources/Assets.xcassets/Icons/add.imageset/outline_add_black_48pt_2x.png new file mode 100644 index 0000000..3cb1092 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/add.imageset/outline_add_black_48pt_2x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/add.imageset/outline_add_black_48pt_3x.png b/Riddler/Resources/Assets.xcassets/Icons/add.imageset/outline_add_black_48pt_3x.png new file mode 100644 index 0000000..60cbd9c Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/add.imageset/outline_add_black_48pt_3x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/back.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Icons/back.imageset/Contents.json new file mode 100644 index 0000000..be7ebd8 --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Icons/back.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "outline_arrow_back_ios_new_black_48pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "outline_arrow_back_ios_new_black_48pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "outline_arrow_back_ios_new_black_48pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riddler/Resources/Assets.xcassets/Icons/back.imageset/outline_arrow_back_ios_new_black_48pt_1x.png b/Riddler/Resources/Assets.xcassets/Icons/back.imageset/outline_arrow_back_ios_new_black_48pt_1x.png new file mode 100644 index 0000000..879206e Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/back.imageset/outline_arrow_back_ios_new_black_48pt_1x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/back.imageset/outline_arrow_back_ios_new_black_48pt_2x.png b/Riddler/Resources/Assets.xcassets/Icons/back.imageset/outline_arrow_back_ios_new_black_48pt_2x.png new file mode 100644 index 0000000..5751aa6 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/back.imageset/outline_arrow_back_ios_new_black_48pt_2x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/back.imageset/outline_arrow_back_ios_new_black_48pt_3x.png b/Riddler/Resources/Assets.xcassets/Icons/back.imageset/outline_arrow_back_ios_new_black_48pt_3x.png new file mode 100644 index 0000000..a69c43e Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/back.imageset/outline_arrow_back_ios_new_black_48pt_3x.png differ diff --git a/Riddler/Resources/Assets.xcassets/accentBackground.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Icons/check_large.imageset/Contents.json similarity index 66% rename from Riddler/Resources/Assets.xcassets/accentBackground.imageset/Contents.json rename to Riddler/Resources/Assets.xcassets/Icons/check_large.imageset/Contents.json index 1b7961c..09224a4 100644 --- a/Riddler/Resources/Assets.xcassets/accentBackground.imageset/Contents.json +++ b/Riddler/Resources/Assets.xcassets/Icons/check_large.imageset/Contents.json @@ -1,17 +1,15 @@ { "images" : [ { - "filename" : "accentBackground.png", + "filename" : "check_black_large.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "accentBackground@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "accentBackground@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Riddler/Resources/Assets.xcassets/Icons/check_large.imageset/check_black_large.png b/Riddler/Resources/Assets.xcassets/Icons/check_large.imageset/check_black_large.png new file mode 100644 index 0000000..e57aeb8 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/check_large.imageset/check_black_large.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/close.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Icons/close.imageset/Contents.json new file mode 100644 index 0000000..e8b942e --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Icons/close.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "outline_close_black_48pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "outline_close_black_48pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "outline_close_black_48pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riddler/Resources/Assets.xcassets/Icons/close.imageset/outline_close_black_48pt_1x.png b/Riddler/Resources/Assets.xcassets/Icons/close.imageset/outline_close_black_48pt_1x.png new file mode 100644 index 0000000..8320bab Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/close.imageset/outline_close_black_48pt_1x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/close.imageset/outline_close_black_48pt_2x.png b/Riddler/Resources/Assets.xcassets/Icons/close.imageset/outline_close_black_48pt_2x.png new file mode 100644 index 0000000..51e1362 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/close.imageset/outline_close_black_48pt_2x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/close.imageset/outline_close_black_48pt_3x.png b/Riddler/Resources/Assets.xcassets/Icons/close.imageset/outline_close_black_48pt_3x.png new file mode 100644 index 0000000..8d88f15 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/close.imageset/outline_close_black_48pt_3x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/feedback.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Icons/feedback.imageset/Contents.json new file mode 100644 index 0000000..7157135 --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Icons/feedback.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "outline_feedback_black_48pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "outline_feedback_black_48pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "outline_feedback_black_48pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riddler/Resources/Assets.xcassets/Icons/feedback.imageset/outline_feedback_black_48pt_1x.png b/Riddler/Resources/Assets.xcassets/Icons/feedback.imageset/outline_feedback_black_48pt_1x.png new file mode 100644 index 0000000..970b84e Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/feedback.imageset/outline_feedback_black_48pt_1x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/feedback.imageset/outline_feedback_black_48pt_2x.png b/Riddler/Resources/Assets.xcassets/Icons/feedback.imageset/outline_feedback_black_48pt_2x.png new file mode 100644 index 0000000..e0f5f7a Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/feedback.imageset/outline_feedback_black_48pt_2x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/feedback.imageset/outline_feedback_black_48pt_3x.png b/Riddler/Resources/Assets.xcassets/Icons/feedback.imageset/outline_feedback_black_48pt_3x.png new file mode 100644 index 0000000..876afeb Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/feedback.imageset/outline_feedback_black_48pt_3x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/forward.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Icons/forward.imageset/Contents.json new file mode 100644 index 0000000..e614de3 --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Icons/forward.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "outline_arrow_forward_ios_black_48pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "outline_arrow_forward_ios_black_48pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "outline_arrow_forward_ios_black_48pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riddler/Resources/Assets.xcassets/Icons/forward.imageset/outline_arrow_forward_ios_black_48pt_1x.png b/Riddler/Resources/Assets.xcassets/Icons/forward.imageset/outline_arrow_forward_ios_black_48pt_1x.png new file mode 100644 index 0000000..7f744f6 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/forward.imageset/outline_arrow_forward_ios_black_48pt_1x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/forward.imageset/outline_arrow_forward_ios_black_48pt_2x.png b/Riddler/Resources/Assets.xcassets/Icons/forward.imageset/outline_arrow_forward_ios_black_48pt_2x.png new file mode 100644 index 0000000..0755b27 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/forward.imageset/outline_arrow_forward_ios_black_48pt_2x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/forward.imageset/outline_arrow_forward_ios_black_48pt_3x.png b/Riddler/Resources/Assets.xcassets/Icons/forward.imageset/outline_arrow_forward_ios_black_48pt_3x.png new file mode 100644 index 0000000..1faf42e Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/forward.imageset/outline_arrow_forward_ios_black_48pt_3x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/leaderboard.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Icons/leaderboard.imageset/Contents.json new file mode 100644 index 0000000..e09ab03 --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Icons/leaderboard.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "outline_leaderboard_black_48pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "outline_leaderboard_black_48pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "outline_leaderboard_black_48pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riddler/Resources/Assets.xcassets/Icons/leaderboard.imageset/outline_leaderboard_black_48pt_1x.png b/Riddler/Resources/Assets.xcassets/Icons/leaderboard.imageset/outline_leaderboard_black_48pt_1x.png new file mode 100644 index 0000000..e0b8eb7 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/leaderboard.imageset/outline_leaderboard_black_48pt_1x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/leaderboard.imageset/outline_leaderboard_black_48pt_2x.png b/Riddler/Resources/Assets.xcassets/Icons/leaderboard.imageset/outline_leaderboard_black_48pt_2x.png new file mode 100644 index 0000000..7b61b20 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/leaderboard.imageset/outline_leaderboard_black_48pt_2x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/leaderboard.imageset/outline_leaderboard_black_48pt_3x.png b/Riddler/Resources/Assets.xcassets/Icons/leaderboard.imageset/outline_leaderboard_black_48pt_3x.png new file mode 100644 index 0000000..41c04e6 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/leaderboard.imageset/outline_leaderboard_black_48pt_3x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/settings.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Icons/settings.imageset/Contents.json new file mode 100644 index 0000000..a7aba52 --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Icons/settings.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "outline_settings_black_48pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "outline_settings_black_48pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "outline_settings_black_48pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riddler/Resources/Assets.xcassets/Icons/settings.imageset/outline_settings_black_48pt_1x.png b/Riddler/Resources/Assets.xcassets/Icons/settings.imageset/outline_settings_black_48pt_1x.png new file mode 100644 index 0000000..33b34a1 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/settings.imageset/outline_settings_black_48pt_1x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/settings.imageset/outline_settings_black_48pt_2x.png b/Riddler/Resources/Assets.xcassets/Icons/settings.imageset/outline_settings_black_48pt_2x.png new file mode 100644 index 0000000..eeed38e Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/settings.imageset/outline_settings_black_48pt_2x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/settings.imageset/outline_settings_black_48pt_3x.png b/Riddler/Resources/Assets.xcassets/Icons/settings.imageset/outline_settings_black_48pt_3x.png new file mode 100644 index 0000000..5b90003 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/settings.imageset/outline_settings_black_48pt_3x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/share.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Icons/share.imageset/Contents.json new file mode 100644 index 0000000..b6a9fb8 --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Icons/share.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "outline_share_black_48pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "outline_share_black_48pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "outline_share_black_48pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riddler/Resources/Assets.xcassets/Icons/share.imageset/outline_share_black_48pt_1x.png b/Riddler/Resources/Assets.xcassets/Icons/share.imageset/outline_share_black_48pt_1x.png new file mode 100644 index 0000000..f14320a Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/share.imageset/outline_share_black_48pt_1x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/share.imageset/outline_share_black_48pt_2x.png b/Riddler/Resources/Assets.xcassets/Icons/share.imageset/outline_share_black_48pt_2x.png new file mode 100644 index 0000000..a50b19c Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/share.imageset/outline_share_black_48pt_2x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/share.imageset/outline_share_black_48pt_3x.png b/Riddler/Resources/Assets.xcassets/Icons/share.imageset/outline_share_black_48pt_3x.png new file mode 100644 index 0000000..24ebd9b Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/share.imageset/outline_share_black_48pt_3x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/stats.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Icons/stats.imageset/Contents.json new file mode 100644 index 0000000..76f83fe --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Icons/stats.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "outline_bar_chart_black_48pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "outline_bar_chart_black_48pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "outline_bar_chart_black_48pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riddler/Resources/Assets.xcassets/Icons/stats.imageset/outline_bar_chart_black_48pt_1x.png b/Riddler/Resources/Assets.xcassets/Icons/stats.imageset/outline_bar_chart_black_48pt_1x.png new file mode 100644 index 0000000..0be55b7 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/stats.imageset/outline_bar_chart_black_48pt_1x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/stats.imageset/outline_bar_chart_black_48pt_2x.png b/Riddler/Resources/Assets.xcassets/Icons/stats.imageset/outline_bar_chart_black_48pt_2x.png new file mode 100644 index 0000000..21e9ec3 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/stats.imageset/outline_bar_chart_black_48pt_2x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/stats.imageset/outline_bar_chart_black_48pt_3x.png b/Riddler/Resources/Assets.xcassets/Icons/stats.imageset/outline_bar_chart_black_48pt_3x.png new file mode 100644 index 0000000..0ea0936 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/stats.imageset/outline_bar_chart_black_48pt_3x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/trophy.imageset/Contents.json b/Riddler/Resources/Assets.xcassets/Icons/trophy.imageset/Contents.json new file mode 100644 index 0000000..ee64377 --- /dev/null +++ b/Riddler/Resources/Assets.xcassets/Icons/trophy.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "outline_emoji_events_black_48pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "outline_emoji_events_black_48pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "outline_emoji_events_black_48pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riddler/Resources/Assets.xcassets/Icons/trophy.imageset/outline_emoji_events_black_48pt_1x.png b/Riddler/Resources/Assets.xcassets/Icons/trophy.imageset/outline_emoji_events_black_48pt_1x.png new file mode 100644 index 0000000..88ed2f6 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/trophy.imageset/outline_emoji_events_black_48pt_1x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/trophy.imageset/outline_emoji_events_black_48pt_2x.png b/Riddler/Resources/Assets.xcassets/Icons/trophy.imageset/outline_emoji_events_black_48pt_2x.png new file mode 100644 index 0000000..9642894 Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/trophy.imageset/outline_emoji_events_black_48pt_2x.png differ diff --git a/Riddler/Resources/Assets.xcassets/Icons/trophy.imageset/outline_emoji_events_black_48pt_3x.png b/Riddler/Resources/Assets.xcassets/Icons/trophy.imageset/outline_emoji_events_black_48pt_3x.png new file mode 100644 index 0000000..f6784da Binary files /dev/null and b/Riddler/Resources/Assets.xcassets/Icons/trophy.imageset/outline_emoji_events_black_48pt_3x.png differ diff --git a/Riddler/Resources/Assets.xcassets/accentBackground.imageset/accentBackground.png b/Riddler/Resources/Assets.xcassets/accentBackground.imageset/accentBackground.png deleted file mode 100644 index 4a3559d..0000000 Binary files a/Riddler/Resources/Assets.xcassets/accentBackground.imageset/accentBackground.png and /dev/null differ diff --git a/Riddler/Resources/Assets.xcassets/accentBackground.imageset/accentBackground@2x.png b/Riddler/Resources/Assets.xcassets/accentBackground.imageset/accentBackground@2x.png deleted file mode 100644 index d9d56e8..0000000 Binary files a/Riddler/Resources/Assets.xcassets/accentBackground.imageset/accentBackground@2x.png and /dev/null differ diff --git a/Riddler/Resources/Assets.xcassets/accentBackground.imageset/accentBackground@3x.png b/Riddler/Resources/Assets.xcassets/accentBackground.imageset/accentBackground@3x.png deleted file mode 100644 index 7f6f5df..0000000 Binary files a/Riddler/Resources/Assets.xcassets/accentBackground.imageset/accentBackground@3x.png and /dev/null differ diff --git a/Riddler/Resources/Assets.xcassets/primaryBackground.imageset/primaryBackground.png b/Riddler/Resources/Assets.xcassets/primaryBackground.imageset/primaryBackground.png deleted file mode 100644 index 9468e96..0000000 Binary files a/Riddler/Resources/Assets.xcassets/primaryBackground.imageset/primaryBackground.png and /dev/null differ diff --git a/Riddler/Resources/Assets.xcassets/primaryBackground.imageset/primaryBackground@2x.png b/Riddler/Resources/Assets.xcassets/primaryBackground.imageset/primaryBackground@2x.png deleted file mode 100644 index 10a7ff1..0000000 Binary files a/Riddler/Resources/Assets.xcassets/primaryBackground.imageset/primaryBackground@2x.png and /dev/null differ diff --git a/Riddler/Resources/Assets.xcassets/primaryBackground.imageset/primaryBackground@3x.png b/Riddler/Resources/Assets.xcassets/primaryBackground.imageset/primaryBackground@3x.png deleted file mode 100644 index 958285b..0000000 Binary files a/Riddler/Resources/Assets.xcassets/primaryBackground.imageset/primaryBackground@3x.png and /dev/null differ diff --git a/Riddler/Resources/Lottie/confetti.json b/Riddler/Resources/Lottie/confetti.json new file mode 100644 index 0000000..cd77fa5 --- /dev/null +++ b/Riddler/Resources/Lottie/confetti.json @@ -0,0 +1 @@ +{"v":"5.5.8","fr":29.9700012207031,"ip":0,"op":300.00001221925,"w":2000,"h":2000,"nm":"Comp 1","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":0,"nm":"BlueConfetti","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1000,1000,0],"ix":2},"a":{"a":0,"k":[1000,1000,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":2000,"h":2000,"ip":150.000006109625,"op":450.000018328876,"st":150.000006109625,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"BlueConfetti","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1000,1000,0],"ix":2},"a":{"a":0,"k":[1000,1000,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":2000,"h":2000,"ip":0,"op":300.00001221925,"st":-150.000006109625,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"GreenConfetti","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1000,1000,0],"ix":2},"a":{"a":0,"k":[1000,1000,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":2000,"h":2000,"ip":150.000006109625,"op":450.000018328876,"st":150.000006109625,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"GreenConfetti","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1000,1000,0],"ix":2},"a":{"a":0,"k":[1000,1000,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":2000,"h":2000,"ip":0,"op":299.00001217852,"st":-150.000006109625,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"YellowConfetti","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1000,1000,0],"ix":2},"a":{"a":0,"k":[1000,1000,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":2000,"h":2000,"ip":150.000006109625,"op":516.000021017111,"st":150.000006109625,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"YellowConfetti","refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1000,1000,0],"ix":2},"a":{"a":0,"k":[1000,1000,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":2000,"h":2000,"ip":0,"op":300.00001221925,"st":-149.000006068894,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"RedConfetti","refId":"comp_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1000,1000,0],"ix":2},"a":{"a":0,"k":[1000,1000,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":2000,"h":2000,"ip":150.000006109625,"op":750.000030548126,"st":150.000006109625,"bm":0},{"ddd":0,"ind":8,"ty":0,"nm":"RedConfetti","refId":"comp_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1000,1000,0],"ix":2},"a":{"a":0,"k":[1000,1000,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":2000,"h":2000,"ip":0,"op":450.000018328876,"st":-150.000006109625,"bm":0}]},{"id":"comp_1","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[688,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.054901961237,0.678431391716,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":175,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":176,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":196,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":231,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":276,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":305,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":349.000014215061,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":177,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":211,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":243,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":301,"s":[100,1]},{"t":349.000014215061,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":175,"s":[0]},{"t":349.000014215061,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":175.000007127896,"op":1075.00004378565,"st":175.000007127896,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1088,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.054901961237,0.678431391716,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":225,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":226,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":246,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":281,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":326,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":355,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":399.000016251603,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":227,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":261,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":293,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":351,"s":[100,1]},{"t":399.000016251603,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":225,"s":[0]},{"t":399.000016251603,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":225.000009164438,"op":1125.00004582219,"st":225.000009164438,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1408,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.054901961237,0.678431391716,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":137,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":182,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":211,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":255.000010386363,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":83,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":117,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":149,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":207,"s":[100,1]},{"t":255.000010386363,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":81,"s":[0]},{"t":255.000010386363,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":81.0000032991976,"op":981.000039956949,"st":81.0000032991976,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1968,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.054901961237,0.678431391716,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":145,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":146,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":166,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":201,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":246,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":275,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":319.000012993136,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":147,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":181,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":213,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":271,"s":[100,1]},{"t":319.000012993136,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":145,"s":[0]},{"t":319.000012993136,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":145.000005905971,"op":1045.00004256372,"st":145.000005905971,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[848,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.054901961237,0.678431391716,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":154,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":199,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":228,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":272.000011078787,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":100,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":134,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":166,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":224,"s":[100,1]},{"t":272.000011078787,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":98,"s":[0]},{"t":272.000011078787,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":98.0000039916218,"op":369.000015029678,"st":98.0000039916218,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[368,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.054901961237,0.678431391716,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":51,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":71,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":151,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":180,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":224.000009123707,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":52,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":86,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":118,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":176,"s":[100,1]},{"t":224.000009123707,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[0]},{"t":224.000009123707,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":50.0000020365418,"op":300.00001221925,"st":50.0000020365418,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1728,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.054901961237,0.678431391716,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":35,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":115,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":144,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":188.000007657397,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":16,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":50,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":82,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":140,"s":[100,1]},{"t":188.000007657397,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":14,"s":[0]},{"t":188.000007657397,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":14.0000005702317,"op":300.00001221925,"st":14.0000005702317,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1248,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.054901961237,0.678431391716,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-1,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":55,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":173.000007046434,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":1,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":35,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":67,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":125,"s":[100,1]},{"t":173.000007046434,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1,"s":[0]},{"t":173.000007046434,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300.00001221925,"st":-1.00000004073083,"bm":0}]},{"id":"comp_2","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[368,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.905882358551,0.658823549747,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":62,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":63,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":163,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":192,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":236.000009612477,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":64,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":98,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":130,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":188,"s":[100,1]},{"t":236.000009612477,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":62,"s":[0]},{"t":236.000009612477,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":62.0000025253118,"op":962.000039183063,"st":62.0000025253118,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1328,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.905882358551,0.658823549747,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":190,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":191,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":211,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":246,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":291,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":320,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":364.000014826024,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":192,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":226,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":258,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":316,"s":[100,1]},{"t":364.000014826024,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[0]},{"t":364.000014826024,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":190.000007738859,"op":1090.00004439661,"st":190.000007738859,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[848,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.905882358551,0.658823549747,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":125,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":146,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":181,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":226,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":255,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":299.00001217852,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":127,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":161,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":193,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":251,"s":[100,1]},{"t":299.00001217852,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":125,"s":[0]},{"t":299.00001217852,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":125.000005091354,"op":1025.00004174911,"st":125.000005091354,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1328,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.905882358551,0.658823549747,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":41,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":42,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":62,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":142,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":171,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":215.00000875713,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":43,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":77,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":109,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":167,"s":[100,1]},{"t":215.00000875713,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":41,"s":[0]},{"t":215.00000875713,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":41.0000016699642,"op":941.000038327716,"st":41.0000016699642,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1088,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.905882358551,0.658823549747,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":142,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":143,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":163,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":198,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":243,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":272,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":316.000012870944,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":144,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":178,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":268,"s":[100,1]},{"t":316.000012870944,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":142,"s":[0]},{"t":316.000012870944,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":142.000005783779,"op":413.000016821835,"st":142.000005783779,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[608,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.905882358551,0.658823549747,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":212,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":213,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":233,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":268,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":313,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":342,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":386.000015722102,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":214,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":248,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":280,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":338,"s":[100,1]},{"t":386.000015722102,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":212,"s":[0]},{"t":386.000015722102,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":212.000008634937,"op":462.000018817646,"st":212.000008634937,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1088,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.905882358551,0.658823549747,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-9,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-8,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":47,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":121,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":165.000006720588,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-7,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":27,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":59,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":117,"s":[100,1]},{"t":165.000006720588,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-9,"s":[0]},{"t":165.000006720588,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-9.00000036657752,"op":277.000011282441,"st":-9.00000036657752,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1728,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.905882358551,0.658823549747,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":37,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":38,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":58,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":138,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":167,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":211.000008594206,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":39,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":73,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":105,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":163,"s":[100,1]},{"t":211.000008594206,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"t":211.000008594206,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":38.0000015477717,"op":338.000013767022,"st":37.0000015070409,"bm":0}]},{"id":"comp_3","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[288,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.803921580315,0.223529413342,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-1,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":55,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":173.000007046434,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":1,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":35,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":67,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":125,"s":[100,1]},{"t":173.000007046434,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1,"s":[0]},{"t":173.000007046434,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-1.00000004073083,"op":899.000036617021,"st":-1.00000004073083,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1424,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.803921580315,0.223529413342,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":160,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":161,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":181,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":216,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":261,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":290,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":334.000013604099,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":162,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":196,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":228,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":286,"s":[100,1]},{"t":334.000013604099,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[0]},{"t":334.000013604099,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":160.000006516934,"op":1060.00004317469,"st":160.000006516934,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[528,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.803921580315,0.223529413342,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":13,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":33,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":142,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":186.000007575935,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":14,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":48,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":80,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":138,"s":[100,1]},{"t":186.000007575935,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":12,"s":[0]},{"t":186.000007575935,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":12.00000048877,"op":912.000037146522,"st":12.00000048877,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1168,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.803921580315,0.223529413342,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":45,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":46,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":66,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":146,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":175,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":219.000008920053,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":47,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":81,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":113,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":171,"s":[100,1]},{"t":219.000008920053,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[0]},{"t":219.000008920053,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":45.0000018328876,"op":945.000038490639,"st":45.0000018328876,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[768,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.803921580315,0.223529413342,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":220,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":221,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":241,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":276,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":321,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":350,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":394.000016047949,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":222,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":256,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":288,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":346,"s":[100,1]},{"t":394.000016047949,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[0]},{"t":394.000016047949,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":220.000008960784,"op":491.00001999884,"st":220.000008960784,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[2016,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.803921580315,0.223529413342,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-1,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":55,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":129,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":173.000007046434,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":1,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":35,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":67,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":125,"s":[100,1]},{"t":173.000007046434,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1,"s":[0]},{"t":173.000007046434,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-1.00000004073083,"op":249.000010141978,"st":-1.00000004073083,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1968,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.803921580315,0.223529413342,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":178,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":179,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":199,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":234,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":279,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":308,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":352.000014337254,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":214,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":246,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":304,"s":[100,1]},{"t":352.000014337254,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":178,"s":[0]},{"t":352.000014337254,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":178.000007250089,"op":464.000018899107,"st":178.000007250089,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1568,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.803921580315,0.223529413342,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":115,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":150,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":195,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":224,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":268.000010915864,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":96,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":130,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":162,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":220,"s":[100,1]},{"t":268.000010915864,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":94,"s":[0]},{"t":268.000010915864,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":95.0000038694293,"op":395.00001608868,"st":94.0000038286985,"bm":0}]},{"id":"comp_4","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1568,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.298039227724,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":169,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":170,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":190,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":225,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":270,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":299,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":343.000013970676,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":171,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":205,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":237,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":295,"s":[100,1]},{"t":343.000013970676,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":169,"s":[0]},{"t":343.000013970676,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":169.000006883511,"op":1069.00004354126,"st":169.000006883511,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1104,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.298039227724,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":67,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":123,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":168,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":197,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":241.000009816131,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":69,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":103,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":135,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":193,"s":[100,1]},{"t":241.000009816131,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":67,"s":[0]},{"t":241.000009816131,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":67.0000027289659,"op":967.000039386717,"st":67.0000027289659,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[928,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.298039227724,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":232,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":233,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":253,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":288,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":333,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":362,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":406.000016536719,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":234,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":268,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":300,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":358,"s":[100,1]},{"t":406.000016536719,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":232,"s":[0]},{"t":406.000016536719,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":232.000009449554,"op":1132.00004610731,"st":232.000009449554,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1968,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.298039227724,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":133,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":168,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":213,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":242,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":286.000011649019,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":114,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":148,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":238,"s":[100,1]},{"t":286.000011649019,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":112,"s":[0]},{"t":286.000011649019,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":112.000004561854,"op":1012.0000412196,"st":112.000004561854,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1248,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.298039227724,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":328,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":329,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":349,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":384,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":429,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":458,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":502.000020446879,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":330,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":364,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":396,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":454,"s":[100,1]},{"t":502.000020446879,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":328,"s":[0]},{"t":502.000020446879,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":328.000013359714,"op":599.00002439777,"st":328.000013359714,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[2016,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.298039227724,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":259,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":260,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":280,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":315,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":360,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":389,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":433.000017636452,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":261,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":295,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":327,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":385,"s":[100,1]},{"t":433.000017636452,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":259,"s":[0]},{"t":433.000017636452,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":259.000010549286,"op":509.000020731995,"st":259.000010549286,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[608,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.298039227724,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":1,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":56,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":174.000007087165,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":2,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":36,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":68,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":126,"s":[100,1]},{"t":174.000007087165,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":174.000007087165,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":286.000011649019,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1808,732,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[32,32],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.298039227724,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":55,"s":[-256,-748],"to":[0,4.316],"ti":[0.521,-4.829]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":56,"s":[-256.803,-794.275],"to":[-6.566,60.819],"ti":[0,-108.389]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76,"s":[-308,-464.77],"to":[0,131.094],"ti":[-6.457,-163.134]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111,"s":[-227.949,-12.545],"to":[7.89,199.325],"ti":[9.338,-174.954]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":156,"s":[-297.153,568.725],"to":[-8.732,163.591],"ti":[0,-75.105]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":185,"s":[-256,943],"to":[0,279.833],"ti":[0,-0.667]},{"t":229.000009327361,"s":[-168,1299]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":57,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":91,"s":[100,1]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":123,"s":[100,100]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":181,"s":[100,1]},{"t":229.000009327361,"s":[100,100]}],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":55,"s":[0]},{"t":229.000009327361,"s":[277.579]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":56.0000022809268,"op":356.000014500177,"st":55.0000022401959,"bm":0}]}],"layers":[{"ddd":0,"ind":2,"ty":0,"nm":"SmallConfetti","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1000,1000,0],"ix":2},"a":{"a":0,"k":[1000,1000,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":2000,"h":2000,"ip":0,"op":300.00001221925,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"SmallConfetti","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[592,592,0],"ix":2},"a":{"a":0,"k":[1000,1000,0],"ix":1},"s":{"a":0,"k":[146.4,143.2,100],"ix":6}},"ao":0,"w":2000,"h":2000,"ip":0,"op":300.00001221925,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"SmallConfetti","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1400,1360,0],"ix":2},"a":{"a":0,"k":[1000,1000,0],"ix":1},"s":{"a":0,"k":[153.6,143.2,100],"ix":6}},"ao":0,"w":2000,"h":2000,"ip":0,"op":300.00001221925,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Riddler/Resources/Lottie/trophy.json b/Riddler/Resources/Lottie/trophy.json new file mode 100644 index 0000000..d7a291a --- /dev/null +++ b/Riddler/Resources/Lottie/trophy.json @@ -0,0 +1 @@ +{"v":"5.0.1","fr":30,"ip":0,"op":60,"w":320,"h":320,"ddd":0,"assets":[],"layers":[{"ind":7,"nm":"topo contornos","ks":{"p":{"a":1,"k":[{"t":17,"s":[160.001,9.507,0],"to":[0,0,0],"ti":[0,0,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[160.001,50.007,0]},{"t":22,"s":[160.001,50.007,0],"to":[0,5.917,0],"ti":[0,0.583,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[160.001,45.007,0]},{"t":26,"s":[160.001,45.007,0],"to":[0,-0.583,0],"ti":[0,-0.25,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[160.001,46.507,0]},{"t":29,"s":[160.001,46.507,0]}]},"a":{"a":0,"k":[98.938,7.841,0]},"s":{"a":1,"k":[{"t":17,"s":[0.555,173.834,100],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[109.991,75.033,100]},{"t":22,"s":[109.991,75.033,100],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[89.218,160.66,100]},{"t":26,"s":[89.218,160.66,100],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[95.298,114.553,100]},{"t":29,"s":[95.298,114.553,100]}]},"r":{"a":0,"k":0},"o":{"a":0,"k":100}},"ao":0,"ip":17,"op":60,"st":0,"bm":0,"sr":1,"ty":4,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[1.468,1.468],[2.126,0],[0,0],[0,0],[0,0],[-1.468,1.468],[0,2.126]],"o":[[0,-2.125],[-1.468,-1.467],[0,0],[0,0],[0,0],[2.126,0],[1.468,-1.468],[0,0]],"v":[[49.344,0],[47.142,-5.39],[41.752,-7.591],[-49.344,-7.591],[-49.344,7.591],[41.752,7.591],[47.142,5.39],[49.344,0]],"c":true},"hd":false}},{"ty":"fl","c":{"a":0,"k":[0.02,0.184,0.235,1]},"hd":false,"o":{"a":0,"k":100},"r":1},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Caminho 1","ln":"Caminho-1","hd":false},{"ty":"tr","p":{"a":0,"k":[148.281,7.841]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Grupo 1","ln":"Grupo-1","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[1.468,1.468],[2.126,0],[0,0],[1.45,-1.467],[0,-2.125],[-1.468,-1.468],[-2.143,0],[0,0],[-1.468,1.468],[0,2.126]],"o":[[0,-2.125],[-1.468,-1.467],[0,0],[-2.143,0],[-1.468,1.468],[0,2.126],[1.45,1.468],[0,0],[2.126,0],[1.468,-1.468],[0,0]],"v":[[98.688,0],[96.485,-5.39],[91.095,-7.591],[-91.096,-7.591],[-96.486,-5.39],[-98.688,0],[-96.486,5.39],[-91.096,7.591],[91.095,7.591],[96.485,5.39],[98.688,0]],"c":true},"hd":false}},{"ty":"fl","c":{"a":0,"k":[0.02,0.184,0.235,1]},"hd":false,"o":{"a":0,"k":100},"r":1},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Caminho 1","ln":"Caminho-1","hd":false},{"ty":"tr","p":{"a":0,"k":[98.938,7.841]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Grupo 2","ln":"Grupo-2","hd":false}],"ln":"topo-contornos"},{"ind":6,"nm":"caneca contornos","ks":{"p":{"a":1,"k":[{"t":11,"s":[160,193.816,0],"to":[0,0,0],"ti":[0,0,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[160,103.816,0]},{"t":17,"s":[160,103.816,0],"to":[0,-11,0],"ti":[0,-3.333,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[160,127.816,0]},{"t":21,"s":[160,127.816,0],"to":[0,3.333,0],"ti":[0,0.667,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[160,123.816,0]},{"t":24,"s":[160,123.816,0]}]},"a":{"a":0,"k":[83.914,84.008,0]},"s":{"a":1,"k":[{"t":11,"s":[14.198,3.539,100],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[109.534,111.904,100]},{"t":17,"s":[109.534,111.904,100],"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0.167]},"e":[109.534,88.096,100]},{"t":21,"s":[109.534,88.096,100],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[100,94.048,100]},{"t":24,"s":[100,94.048,100]}]},"r":{"a":1,"k":[{"t":11,"s":[0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"e":[0]},{"t":17,"s":[0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"e":[0]},{"t":21,"s":[0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"e":[0]},{"t":24,"s":[0]}]},"o":{"a":0,"k":100}},"ao":0,"ip":11,"op":60,"st":0,"bm":0,"sr":1,"ty":4,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[1.332,1.602],[2.059,0.237],[0,0],[0,0],[-8.739,3.407],[-6.765,6.106],[-4.151,8.114],[-0.979,9.447],[0,0]],"o":[[0.151,-2.125],[-1.317,-1.603],[0,0],[0,0],[9.481,0],[8.468,-3.307],[6.748,-6.09],[4.267,-8.351],[0,0],[0,0]],"v":[[41.677,-75.407],[39.906,-80.999],[34.844,-83.758],[-41.828,-83.758],[-41.828,83.758],[-14.499,78.647],[8.351,64.527],[24.698,43.22],[32.567,16.524],[41.677,-75.407]],"c":true},"hd":false}},{"ty":"fl","c":{"a":0,"k":[0.035,0.216,0.275,1]},"hd":false,"o":{"a":0,"k":100},"r":1},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Caminho 1","ln":"Caminho-1","hd":false},{"ty":"tr","p":{"a":0,"k":[125.751,84.008]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Grupo 1","ln":"Grupo-1","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[1.332,1.602],[2.059,0.237],[0,0],[1.333,-1.603],[-0.169,-2.108],[0,0],[-4.268,-8.351],[-6.764,-6.107],[-8.486,-3.307],[-9.497,0],[-8.739,3.407],[-6.765,6.106],[-4.151,8.114],[-0.979,9.447],[0,0]],"o":[[0.151,-2.125],[-1.317,-1.603],[0,0],[-2.058,0.237],[-1.332,1.619],[0,0],[0.978,9.447],[4.15,8.098],[6.748,6.106],[8.721,3.407],[9.481,0],[8.468,-3.307],[6.748,-6.09],[4.267,-8.351],[0,0],[0,0]],"v":[[83.514,-75.407],[81.742,-80.999],[76.681,-83.758],[-76.663,-83.758],[-81.751,-80.999],[-83.495,-75.407],[-74.411,16.524],[-66.542,43.22],[-50.171,64.527],[-27.32,78.647],[0.009,83.758],[27.337,78.647],[50.188,64.527],[66.535,43.22],[74.404,16.524],[83.514,-75.407]],"c":true},"hd":false}},{"ty":"fl","c":{"a":0,"k":[0.027,0.231,0.298,1]},"hd":false,"o":{"a":0,"k":100},"r":1},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Caminho 1","ln":"Caminho-1","hd":false},{"ty":"tr","p":{"a":0,"k":[83.914,84.008]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Grupo 2","ln":"Grupo-2","hd":false}],"ln":"caneca-contornos"},{"ind":5,"nm":"braco2 contornos","ks":{"p":{"a":1,"k":[{"t":17,"s":[187.177,114.948,0],"to":[0,0,0],"ti":[0,0,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[257.177,114.948,0]},{"t":24,"s":[257.177,114.948,0],"to":[9.667,0,0],"ti":[1.667,0,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[245.177,114.948,0]},{"t":29,"s":[245.177,114.948,0],"to":[-1.667,0,0],"ti":[-0.333,0,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[247.177,114.948,0]},{"t":33,"s":[247.177,114.948,0]}]},"a":{"a":0,"k":[43.402,57.143,0]},"s":{"a":1,"k":[{"t":17,"s":[22.014,100,100],"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0]},"e":[136.699,100,100]},{"t":24,"s":[136.699,100,100],"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0]},"e":[81.65,100,100]},{"t":29,"s":[81.65,100,100],"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0]},"e":[100,100,100]},{"t":33,"s":[100,100,100]}]},"r":{"a":0,"k":0},"o":{"a":0,"k":100}},"ao":0,"ip":17,"op":60,"st":0,"bm":0,"sr":1,"ty":4,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[2.093,0],[0,0],[1.485,-1.485],[0,-2.092],[-1.485,-1.484],[-2.092,0],[0,0],[0,0],[8.991,-12.18],[14.373,-4.47],[0,0],[0.979,-1.855],[-0.624,-1.99],[-1.839,-0.979],[-2.025,0.624],[0,0],[-7.372,5.28],[-5.262,7.135],[-2.799,8.385],[0,9.076],[0,0],[1.484,1.484]],"o":[[-1.484,-1.485],[0,0],[-2.092,0],[-1.485,1.484],[0,2.092],[1.485,1.484],[0,0],[0,0],[0,15.032],[-8.992,12.146],[0,0],[-2.007,0.625],[-0.978,1.857],[0.624,1.991],[1.856,0.995],[0,0],[8.671,-2.7],[7.203,-5.179],[5.281,-7.136],[2.885,-8.603],[0,0],[0,-2.092],[0,0]],"v":[[40.926,-54.666],[35.56,-56.893],[-13.555,-56.893],[-18.919,-54.666],[-21.147,-49.302],[-18.919,-43.937],[-13.555,-41.71],[27.969,-41.71],[27.969,-29.261],[14.482,11.556],[-20.565,36.48],[-37.518,41.769],[-41.997,45.488],[-42.529,51.258],[-38.835,55.712],[-33.014,56.269],[-16.06,50.98],[8.004,39.011],[26.703,20.539],[38.824,-2.742],[43.152,-29.261],[43.152,-49.302],[40.926,-54.666]],"c":true},"hd":false}},{"ty":"fl","c":{"a":0,"k":[0.02,0.184,0.235,1]},"hd":false,"o":{"a":0,"k":100},"r":1},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Caminho 1","ln":"Caminho-1","hd":false},{"ty":"tr","p":{"a":0,"k":[43.402,57.143]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Grupo 1","ln":"Grupo-1","hd":false}],"ln":"braco2-contornos"},{"ind":4,"nm":"braco1 contornos","ks":{"p":{"a":1,"k":[{"t":17,"s":[139.044,113.282,0],"to":[0,0,0],"ti":[0,0,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[56.044,113.282,0]},{"t":24,"s":[56.044,113.282,0],"to":[-11.833,0,0],"ti":[-1.667,0,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[68.044,113.282,0]},{"t":29,"s":[68.044,113.282,0],"to":[1.667,0,0],"ti":[0.333,0,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[66.044,113.282,0]},{"t":33,"s":[66.044,113.282,0]}]},"a":{"a":0,"k":[41.34,54.477,0]},"s":{"a":1,"k":[{"t":17,"s":[12.917,100,100],"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0]},"e":[129.028,100,100]},{"t":24,"s":[129.028,100,100],"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0]},"e":[85.486,100,100]},{"t":29,"s":[85.486,100,100],"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0]},"e":[100,100,100]},{"t":33,"s":[100,100,100]}]},"r":{"a":0,"k":0},"o":{"a":0,"k":100}},"ao":0,"ip":17,"op":60,"st":0,"bm":0,"sr":1,"ty":4,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[1.484,1.484],[2.092,0],[0,0],[1.485,-1.485],[0,-2.092],[0,0],[-3.154,-8.958],[-5.736,-7.322],[-7.709,-5.079],[-9.194,-2.295],[0,0],[-1.788,1.08],[-0.506,2.042],[1.079,1.788],[2.041,0.507],[0,0],[6.478,4.268],[4.69,5.989],[2.513,7.136],[0,7.744],[0,0],[0,0],[-1.484,1.484],[0,2.092]],"o":[[0,-2.092],[-1.484,-1.485],[0,0],[-2.092,0],[-1.484,1.484],[0,0],[0,9.464],[3.054,8.705],[5.719,7.304],[7.929,5.212],[0,0],[2.025,0.506],[1.805,-1.08],[0.523,-2.024],[-1.08,-1.805],[0,0],[-7.524,-1.89],[-6.326,-4.184],[-4.707,-5.988],[-2.581,-7.338],[0,0],[0,0],[2.092,0],[1.484,-1.485],[0,0]],"v":[[23.183,-46.636],[20.956,-52],[15.591,-54.227],[-33.499,-54.227],[-38.864,-52],[-41.09,-46.636],[-41.09,-29.986],[-36.359,-2.353],[-23.175,21.686],[-3.033,40.26],[22.651,51.52],[31.381,53.721],[37.1,52.861],[40.567,48.179],[39.732,42.461],[35.051,38.994],[26.321,36.818],[5.318,27.582],[-11.206,12.323],[-22.036,-7.364],[-25.908,-29.986],[-25.908,-39.045],[15.591,-39.045],[20.956,-41.271],[23.183,-46.636]],"c":true},"hd":false}},{"ty":"fl","c":{"a":0,"k":[0.02,0.184,0.235,1]},"hd":false,"o":{"a":0,"k":100},"r":1},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Caminho 1","ln":"Caminho-1","hd":false},{"ty":"tr","p":{"a":0,"k":[41.34,54.477]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Grupo 1","ln":"Grupo-1","hd":false}],"ln":"braco1-contornos"},{"ind":3,"nm":"base contornos","ks":{"p":{"a":1,"k":[{"t":0,"s":[160.006,287.734,0],"to":[0,0,0],"ti":[0,0,0],"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"e":[160.006,287.734,0]},{"t":5,"s":[160.006,287.734,0],"to":[0,0,0],"ti":[0,0,0],"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"e":[160.006,287.734,0]},{"t":8,"s":[160.006,287.734,0]}]},"a":{"a":0,"k":[68.572,7.841,0]},"s":{"a":1,"k":[{"t":0,"s":[0.105,182.892,100],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[127.708,80.871,100]},{"t":5,"s":[127.708,80.871,100],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[100,100,100]},{"t":8,"s":[100,100,100]}]},"r":{"a":0,"k":0},"o":{"a":0,"k":100}},"ao":0,"ip":0,"op":60,"st":0,"bm":0,"sr":1,"ty":4,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-1.468,1.468],[0,2.126],[1.468,1.468],[2.126,0],[0,0]],"o":[[0,0],[0,0],[2.126,0],[1.468,-1.466],[0,-2.125],[-1.468,-1.468],[0,0],[0,0]],"v":[[-34.161,-7.592],[-34.161,7.592],[26.569,7.592],[31.96,5.389],[34.161,-0.001],[31.96,-5.391],[26.569,-7.592],[-34.161,-7.592]],"c":true},"hd":false}},{"ty":"fl","c":{"a":0,"k":[0.02,0.184,0.235,1]},"hd":false,"o":{"a":0,"k":100},"r":1},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Caminho 1","ln":"Caminho-1","hd":false},{"ty":"tr","p":{"a":0,"k":[102.732,7.842]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Grupo 1","ln":"Grupo-1","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[1.451,-1.468],[0,-2.125],[-1.467,-1.466],[-2.142,0],[0,0],[-1.468,1.468],[0,2.126],[1.467,1.468],[2.126,0],[0,0]],"o":[[-2.142,0],[-1.467,1.468],[0,2.126],[1.451,1.468],[0,0],[2.126,0],[1.467,-1.466],[0,-2.125],[-1.468,-1.468],[0,0],[0,0]],"v":[[-60.731,-7.592],[-66.121,-5.391],[-68.322,-0.001],[-66.121,5.389],[-60.731,7.592],[60.73,7.592],[66.121,5.389],[68.322,-0.001],[66.121,-5.391],[60.73,-7.592],[-60.731,-7.592]],"c":true},"hd":false}},{"ty":"fl","c":{"a":0,"k":[0.02,0.184,0.235,1]},"hd":false,"o":{"a":0,"k":100},"r":1},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Caminho 1","ln":"Caminho-1","hd":false},{"ty":"tr","p":{"a":0,"k":[68.572,7.842]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Grupo 2","ln":"Grupo-2","hd":false}],"ln":"base-contornos"},{"ind":2,"nm":"base1 contornos","ks":{"p":{"a":1,"k":[{"t":5,"s":[160.006,283.367,0],"to":[0,0,0],"ti":[0,0,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[160.006,251.367,0]},{"t":10,"s":[160.006,251.367,0],"to":[0,-4.333,0],"ti":[0,-1,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[160.006,257.367,0]},{"t":13,"s":[160.006,257.367,0],"to":[0,0,0],"ti":[0,0,0],"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"e":[160.006,257.367,0]},{"t":15,"s":[160.006,257.367,0]}]},"a":{"a":0,"k":[53.389,30.615,0]},"s":{"a":1,"k":[{"t":5,"s":[100,2.009,100],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[92.508,116.332,100]},{"t":10,"s":[92.508,116.332,100],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[107.492,90.201,100]},{"t":13,"s":[107.492,90.201,100],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[100,100,100]},{"t":15,"s":[100,100,100]}]},"r":{"a":0,"k":0},"o":{"a":0,"k":100}},"ao":0,"ip":5,"op":60,"st":0,"bm":0,"sr":1,"ty":4,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[-1.468,1.468],[0,2.125],[0,0],[4.437,4.453],[6.31,0]],"o":[[0,0],[0,0],[0,0],[2.126,0],[1.468,-1.467],[0,0],[0,-6.293],[-4.436,-4.436],[0,0]],"v":[[3.796,-30.365],[-26.57,-30.365],[-26.57,30.365],[18.979,30.365],[24.369,28.164],[26.57,22.775],[26.57,-7.591],[19.915,-23.71],[3.796,-30.365]],"c":true},"hd":false}},{"ty":"fl","c":{"a":0,"k":[0.031,0.216,0.275,1]},"hd":false,"o":{"a":0,"k":100},"r":1},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Caminho 1","ln":"Caminho-1","hd":false},{"ty":"tr","p":{"a":0,"k":[79.958,30.615]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Grupo 1","ln":"Grupo-1","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[4.436,-4.436],[0,-6.309],[0,0],[-1.468,-1.467],[-2.142,0],[0,0],[-1.468,1.468],[0,2.125],[0,0],[4.437,4.453],[6.31,0]],"o":[[0,0],[-6.31,0],[-4.437,4.437],[0,0],[0,2.125],[1.451,1.468],[0,0],[2.126,0],[1.468,-1.467],[0,0],[0,-6.293],[-4.436,-4.436],[0,0]],"v":[[30.365,-30.365],[-30.365,-30.365],[-46.484,-23.71],[-53.139,-7.591],[-53.139,22.775],[-50.938,28.164],[-45.548,30.365],[45.548,30.365],[50.938,28.164],[53.139,22.775],[53.139,-7.591],[46.484,-23.71],[30.365,-30.365]],"c":true},"hd":false}},{"ty":"fl","c":{"a":0,"k":[0.027,0.231,0.298,1]},"hd":false,"o":{"a":0,"k":100},"r":1},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Caminho 1","ln":"Caminho-1","hd":false},{"ty":"tr","p":{"a":0,"k":[53.389,30.615]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Grupo 2","ln":"Grupo-2","hd":false}],"ln":"base1-contornos"},{"ind":1,"nm":"meio contornos","ks":{"p":{"a":1,"k":[{"t":8,"s":[160.009,269.773,0],"to":[0,0,0],"ti":[0,0,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[160.009,206.773,0]},{"t":13,"s":[160.009,206.773,0],"to":[0,-8.5,0],"ti":[0,-2,0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"e":[160.009,218.773,0]},{"t":16,"s":[160.009,218.773,0]}]},"a":{"a":0,"k":[23.024,38.46,0]},"s":{"a":1,"k":[{"t":8,"s":[100,1.196,100],"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0.167]},"e":[100,126.001,100]},{"t":13,"s":[100,126.001,100],"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0.167]},"e":[100,100,100]},{"t":16,"s":[100,100,100]}]},"r":{"a":0,"k":0},"o":{"a":0,"k":100}},"ao":0,"ip":8,"op":60,"st":0,"bm":0,"sr":1,"ty":4,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[11.387,-38.21],[-11.387,-38.21],[-11.387,38.21],[11.387,38.21],[11.387,-38.21]],"c":true},"hd":false}},{"ty":"fl","c":{"a":0,"k":[0.02,0.184,0.235,1]},"hd":false,"o":{"a":0,"k":100},"r":1},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Caminho 1","ln":"Caminho-1","hd":false},{"ty":"tr","p":{"a":0,"k":[34.411,38.46]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Grupo 1","ln":"Grupo-1","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[22.774,-38.21],[-22.774,-38.21],[-22.774,38.21],[22.774,38.21],[22.774,-38.21]],"c":true},"hd":false}},{"ty":"fl","c":{"a":0,"k":[0.02,0.184,0.235,1]},"hd":false,"o":{"a":0,"k":100},"r":1},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Caminho 1","ln":"Caminho-1","hd":false},{"ty":"tr","p":{"a":0,"k":[23.024,38.46]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0}}],"nm":"Grupo 2","ln":"Grupo-2","hd":false}],"ln":"meio-contornos"}],"markers":[]} \ No newline at end of file diff --git a/Riddler/Resources/riddles.json b/Riddler/Resources/riddles.json deleted file mode 100644 index e3f9c86..0000000 --- a/Riddler/Resources/riddles.json +++ /dev/null @@ -1,302 +0,0 @@ -[ - { - "id": 1, - "question": "What gets wetter the more it dries?", - "answers": [ "towel", "cloth", "flannel" ], - "hints": [ "Drying can be done in multiple ways.", "Some things are designed for drying." ] - }, - { - "id": 2, - "question": "You see me in water but I never get wet. What am I?", - "answers": [ "reflection" ], - "hints": [ "I look familiar.", "You can see me but you can't touch me." ] - }, - { - "id": 3, - "question": "What has holes but holds water?", - "answers": [ "sponge" ], - "hints": [ "Think of materials.", "Often found near sinks." ] - }, - { - "id": 4, - "question": "What has only two words but thousands of letters?", - "answers": [ "post office" ], - "hints": [ "There are many kinds of letters.", "All letters pass through it." ] - }, - { - "id": 5, - "question": "What becomes more useful when broken?", - "answers": [ "egg", "glowstick", "glow stick" ], - "hints": [ "Think white and yellow.", "Cracking." ] - }, - { - "id": 6, - "question": "What is harder to catch the faster you run?", - "answers": [ "breath" ], - "hints": [ "The word catch can be used in many ways." ] - }, - { - "id": 7, - "question": "What has four fingers and a thumb but is not living?", - "answers": [ "glove" ], - "hints": [ "Something hollow.", "Something you can wear." ] - }, - { - "id": 8, - "question": "What has many keys, but can't even open a single door?", - "answers": [ "keyboard", "laptop", "computer", "piano" ], - "hints": [ "You've used it recently.", "Take a guess and you'll see." ] - }, - { - "id": 9, - "question": "I can only live where there is light, but I die if the light touches me. What am I?", - "answers": [ "shadow" ], - "hints": [ "Everything has one in the light.", "Think figuratively." ] - }, - { - "id": 10, - "question": "Give me food and I will live. Give me water and I will die. What am I?", - "answers": [ "fire", "flame" ], - "hints": [ "You'll get hurt if you get too close to me.", "You can see and feel me, but you can't touch me." ] - }, - { - "id": 11, - "question": "I'm tall when I'm young and I'm short when I'm old. What am I?", - "answers": [ "candle", "pencil" ], - "hints": [ "My use is to burn.", "I hang around on cakes." ] - }, - { - "id": 12, - "question": "What has hands but cannot clap?", - "answers": [ "clock" ], - "hints": [ "One hand is shorter.", "It's always pointing." ] - }, - { - "id": 13, - "question": "What can run but cannot walk?", - "answers": [ "water" ], - "hints": [ "Think of states.", "You need me to survive." ] - }, - { - "id": 14, - "question": "What never asks questions but is often answered?", - "answers": [ "door", "phone", "doorbell", "mobile","mobile phone", "cell phone", "front door", "cell", "telephone" ], - "hints": [ "Questions aren't the only thing you can answer.", "Ring ring." ] - }, - { - "id": 15, - "question": "What kind of coat can only be put on when wet?", - "answers": [ "paint" ], - "hints": [ "There are many types of coat.", "You can't wear this type of coat." ] - }, - { - "id": 16, - "question": "What gets sharper the more you use it?", - "answers": [ "brain", "mind" ], - "hints": [ "You're using it right now.", "You need it to live." ] - }, - { - "id": 17, - "question": "If I have it, I don't share it. If I share it, I don't have it. What is it?", - "answers": [ "secret" ], - "hints": [ "Some people love them, some people hate them.", "Mysterious people may have a lot of them." ] - }, - { - "id": 18, - "question": "What can you catch but not throw?", - "answers": [ "breath", "illness", "cold", "disease" ], - "hints": [ "Something no one wants to catch.", "It can have a big effect on your mood." ] - }, - { - "id": 19, - "question": "What goes up and down but still remains in the same place?", - "answers": [ "stairs", "staircase" ], - "hints": [ "Most people use them everyday.", "You don't want to fall around them." ] - }, - { - "id": 20, - "question": "What has a head and a tail but no body?", - "answers": [ "coin" ], - "hints": [ "It can be flipped.", "Some people use them to make decisions." ] - }, - { - "id": 21, - "question": "Poor people have it. Rich people need it. If you eat it you die. What is it?", - "answers": [ "nothing" ], - "hints": [ "Close your eyes and you can see it.", "Even when all else is gone, it remains.", "What's the opposite of everything?" ] - }, - { - "id": 22, - "question": "What goes up when rain comes down?", - "answers": [ "umbrella", "brolly" ], - "hints": [ "What do you do when it starts raining?.", "You can get it in a tropical drink." ] - }, - { - "id": 23, - "question": "What travels around the world but stays in one corner?", - "answers": [ "stamp" ], - "hints": [ "You decide the destination.", "Highly collectible." ] - }, - { - "id": 24, - "question": "What is so delicate that saying its name breaks it?", - "answers": [ "silence", "quiet" ], - "hints": [ "You'll find it in a library.", "You can't hear it." ] - }, - { - "id": 25, - "question": "What is always coming, but never arrives?", - "answers": [ "tomorrow", "future" ], - "hints": [ "I'll do it _____.", "Think about time." ] - }, - { - "id": 26, - "question": "What goes through towns and over hills but never moves?", - "answers": [ "road", "path" ], - "hints": [ "Vehicles use them.", "Cities have a lot of these." ] - }, - { - "id": 27, - "question": "What has a neck but no head?", - "answers": [ "bottle" ], - "hints": [ "It's not living.", "They're hollow when empty." ] - }, - { - "id": 28, - "question": "What do you throw out when you want to use it, but take back when you are done?", - "answers": [ "anchor" ], - "hints": [ "They hold you back.", "Sailors use them." ] - }, - { - "id": 29, - "question": "What flies around all day but never goes anywhere?", - "answers": [ "flag" ], - "hints": [ "There are different types.", "Every country has one." ] - }, - { - "id": 30, - "question": "What gets bigger the more you take from it?", - "answers": [ "hole" ], - "hints": [ "It must be empty to exist.", "You do not want to fall in a deep one.", "You might need a shovel." ] - }, - { - "id": 31, - "question": "What gets whiter the dirtier it gets?", - "answers": [ "chalkboard", "blackboard" ], - "hints": [ "They are not used much today.", "They were used in schools.", "Think about chalk." ] - }, - { - "id": 32, - "question": "What runs around a garden without moving?", - "answers": [ "fence", "wall" ], - "hints": [ "It can get knocked over or broken.", "There are many forms of running...", "Some are taller than others.", "It is there for privacy." ] - }, - { - "id": 33, - "question": "He has married many women, but has never been married. Who is he?", - "answers": [ "priest", "vicar" ], - "hints": [ "He is close to someone but has never met them." ] - }, - { - "id": 34, - "question": "What has one eye but cannot see?", - "answers": [ "needle" ], - "hints": [ "They are sharp.", "The size of the eye can vary."] - }, - { - "id": 35, - "question": "What can you never eat for breakfast?", - "answers": [ "dinner", "lunch", "supper", "tea" ], - "hints": [ "It's not a specific food.", "It's a fact, not an opinion.", "Think about meals." ] - }, - { - "id": 36, - "question": "What has a heart but no other organs?", - "answers": [ "deck", "card", "deck of cards", "playing cards" ], - "hints": [ "The heart is always red.", "It is not living." ] - }, - { - "id": 37, - "question": "Take off my skin, I won't cry, but you will. What am I?", - "answers": [ "onion" ], - "hints": [ "It is not alive.", "You'll cry but you won't be sad." ] - }, - { - "id": 38, - "question": "What is easy to get into, but hard to get out of?", - "answers": [ "trouble" ], - "hints": [ "If you get into this badly, you could be arrested." ] - }, - { - "id": 39, - "question": "What has four wheels and flies?", - "answers": [ "garbage truck", "rubbish truck", "rubbish lorry" ], - "hints": [ "It comes to you once a week.", "It smells really bad." ] - }, - { - "id": 40, - "question": "What falls often but never gets hurt?", - "answers": [ "rain", "snow" ], - "hints": [ "It is cold when it falls.", "Sometimes lots of it falls but other times you barely notice it falling at all." ] - }, - { - "id": 41, - "question": "What is black when you buy it, red when you use it, and grey when you throw it away?", - "answers": [ "charcoal", "coal" ], - "hints": [ "It can be used for cooking.", "It turns to dust." ] - }, - { - "id": 42, - "question": "The more of it you take, the more of it you leave behind. What is it?", - "answers": [ "footprint", "footstep", "step" ], - "hints": [ "Some are larger than others.", "They are easily seen when it is snowing." ] - }, - { - "id": 43, - "question": "What belongs to you but others use it more than you do?", - "answers": [ "name" ], - "hints": [ "Everyone has one.", "Everyone allows others to use theirs.", "You rarely use you own." ] - }, - { - "id": 44, - "question": "I am as light as a feather, yet no man can hold me for long. What am I?", - "answers": [ "breath" ], - "hints": [ "Some people can hold it longer than others.", "Some smell nice but not all." ] - }, - { - "id": 45, - "question": "I have cities, but no houses. I have mountains, but no trees. I have water, but no fish. What am I?", - "answers": [ "map" ], - "hints": [ "It guides you.", "It needs to be updated often." ] - }, - { - "id": 46, - "question": "Many have heard me, but nobody has seen me, and I will not speak back until spoken to. What am I?", - "answers": [ "echo" ], - "hints": [ "They are not heard everywhere.", "They can only say what you say." ] - }, - { - "id": 47, - "question": "What tastes better than it smells?", - "answers": [ "toungue", "tongue" ], - "hints": [ "You use it daily.", "You need it to talk." ] - }, - { - "id": 48, - "question": "What stays where it is when it goes off?", - "answers": [ "alarm", "alarm clock" ], - "hints": [ "It goes off in the mornings.", "No one likes having it happen." ] - }, - { - "id": 49, - "question": "I start with the letter E, I end with the letter E. I contain only one letter, yet I am not the letter E. What am I?", - "answers": [ "envelope" ], - "hints": [ "Read the riddle carefully.", "There are many forms of letter." ] - }, - { - "id": 50, - "question": "What building has the most stories?", - "answers": [ "library", "bookstore", "bookshop" ], - "hints": [ "Stories doesn't just mean floors.", "Knowledge is stored here." ] - } -] diff --git a/Riddler/Screens/CorrectView.swift b/Riddler/Screens/CorrectView.swift deleted file mode 100644 index 0e9b8e5..0000000 --- a/Riddler/Screens/CorrectView.swift +++ /dev/null @@ -1,121 +0,0 @@ -import Pow -import SwiftUI - -// MARK: - CorrectView - -struct CorrectView: View { - - // MARK: Internal - - @EnvironmentObject var game: GameplayManager - - var body: some View { - VStack { - Spacer() - - if showTickAnimation { - Image(systemName: "checkmark") - .fontWeight(.bold) - .font(.system(size: 200)) - .foregroundColor(RKColors.primaryDark.swiftUIColor) - .padding(.top, 30) - .transition(.movingParts.pop(RKColors.primaryDark.swiftUIColor)) - } else { - Image(systemName: "checkmark") - .fontWeight(.bold) - .font(.system(size: 200)) - .opacity(0) - } - - Spacer() - Text("Correct") - .kerning(-2.0) - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 80)) - .foregroundStyle(RKColors.primaryDark.swiftUIColor) - - Spacer() - VStack { - HStack { - Text("Guesses:") - Spacer() - Text(String((game.player.riddleStats.last?.incorrectGuesses ?? 0) + 1)) - } - HStack { - Text("Time:") - Spacer() - Text(game.player.riddleStats.last?.formattedTimeTaken ?? "0 sec") - } - } - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 30)) - .foregroundStyle(RKColors.primaryDark.swiftUIColor) - .padding(.horizontal, 20) - .padding(.vertical, 10) - - Spacer() - HStack(spacing: 12) { - RKButton(title: "Suggest", icon: Image(systemName: "exclamationmark.bubble"), style: .primary, size: .fill, action: suggestTapped) - RKButton(title: "Share", icon: Image(systemName: "square.and.arrow.up"), style: .primary, size: .fill, action: shareTapped) - } - .padding(.horizontal, 16) - Spacer() - } - .padding(20) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(RKBackground(isDark: false)) - .onAppear { - showTickAnimation = true - game.correctAppeared() - } - .sheet(isPresented: $showSuggestBottomSheet) { - RKBottomSheetPrompt( - titleText: "Missing answer?", - bodyText: "Suggest an alterative answer to help improve Riddler.", - primaryText: "Suggest", - secondaryText: "Close", - primaryAction: pressDialogSuggest, - secondaryAction: pressDialogClose - ) - .presentationDetents([.fraction(0.35)]) - } - .toast(isShowing: $game.showShareCorrectToast, icon: Image(systemName: "bubble.left.fill"), text: "Copied to clipboard") - .toast(isShowing: $game.showAchievementToast, icon: Image(systemName: "trophy"), text: "Achievement Unlocked!") - .analyticsScreen(.correct()) - } - - func suggestTapped() { - Analytics.shared.event(.tapOpenSuggestDialog()) - showSuggestBottomSheet = true - } - - func shareTapped() { - Analytics.shared.event(.tapShareCorrect()) - game.shareCorrect() - } - - func pressDialogSuggest() { - Analytics.shared.event(.tapSuggestAnswer()) - showSuggestBottomSheet = false - if let url = URL(string: "mailto:suggest@rddle.me?subject=I%20have%20a%20valid%20answer%20on%20riddle%20%23\(game.player.currentRiddleIndex)") { - UIApplication.shared.open(url) - } - } - - func pressDialogClose() { - Analytics.shared.event(.tapCloseSuggestDialog()) - showSuggestBottomSheet = false - } - - // MARK: Private - - @State private var showSuggestBottomSheet: Bool = false - @State private var showTickAnimation = false -} - -// MARK: - CorrectView_Previews - -struct CorrectView_Previews: PreviewProvider { - static var previews: some View { - CorrectView() - .environmentObject(GameplayManager(storageManager: PreviewStorageManager())) - } -} diff --git a/Riddler/Screens/DebugView.swift b/Riddler/Screens/DebugView.swift deleted file mode 100644 index 3cf7991..0000000 --- a/Riddler/Screens/DebugView.swift +++ /dev/null @@ -1,45 +0,0 @@ -import SwiftUI - -// MARK: - DebugView - -struct DebugView: View { - - @EnvironmentObject var gameplay: GameplayManager - - var body: some View { - VStack(spacing: 48) { - Text("Debug") - .kerning(-2) - .font(.custom("Abel-Regular", size: 60)) - .foregroundStyle(RKColors.accent.swiftUIColor) - - VStack(spacing: 24) { - RKButton( - title: "Reset data", - size: .fill, - action: gameplay.debugResetTapped - ) - } - - Spacer() - - Text("v1.0.1") - .font(.custom("Abel-Regular", size: 20)) - .foregroundStyle(RKColors.accent.swiftUIColor) - } - .padding(.horizontal, 64) - .padding(.vertical, 32) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(RKBackground(isDark: true)) - } - -} - -// MARK: - DebugView_Previews - -struct DebugView_Previews: PreviewProvider { - static var previews: some View { - DebugView() - .environmentObject(GameplayManager(storageManager: PreviewStorageManager())) - } -} diff --git a/Riddler/Screens/HintView.swift b/Riddler/Screens/HintView.swift deleted file mode 100644 index 8256c01..0000000 --- a/Riddler/Screens/HintView.swift +++ /dev/null @@ -1,98 +0,0 @@ -import SwiftUI - -// MARK: - HintView - -struct HintView: View { - - // MARK: Internal - - @EnvironmentObject var game: GameplayManager - - var body: some View { - VStack { - Spacer() - Text(game.hintString) - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 30)) - .foregroundStyle(RKColors.accent.swiftUIColor) - .multilineTextAlignment(.center) - - Spacer() - HStack { - RKButton(title: buttonString, size: .fill, action: game.useHint) - if game.canRequestMoreHints { - RKIconButton(icon: Image(systemName: "plus"), action: addHintsTapped) - } - } - } - .sheet(isPresented: $showAddHintsSheet, onDismiss: addHintsSheetDismissed) { - RKBottomSheetPrompt( - titleText: "Unlock more hints?", - bodyText: "Watch a short ad to unlock 3 hints.", - primaryText: "Watch", - secondaryText: "Close", - primaryAction: watchAdTapped, - secondaryAction: closeAddHintsSheetTapped - ) - .presentationDetents([.fraction(0.3)]) - } - .toolbar { - ToolbarItem(placement: .principal) { - Text("\(game.player.currentRiddleHintsUsed)/\(game.riddles[game.player.currentRiddleIndex].hints.count)") - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 24)) - .foregroundStyle(RKColors.accent.swiftUIColor) - } - } - .padding(20) - .frame(maxWidth: .infinity) - .background(RKBackground(isDark: true)) - .toast(isShowing: $game.showNoMoreHintsToast, text: "Riddle has no more hints!") - .toast(isShowing: $game.showHintsRewardedToast, text: "3 hints added") - .overlay { - if game.showRewardedAd { - RewardedAdView(rewardedHandler: game.handleHintReward, completion: game.rewardedAdDidFinish) - } - } - .analyticsScreen(.hint()) - } - - // MARK: Private - - @State private var showAddHintsSheet: Bool = false - @State private var requestRewardedAdAfterDismiss: Bool = false - - private var buttonString: String { - game.player.hintsAvailable == 0 ? "Out of hints" : "Use hint: \(game.player.hintsAvailable) remaining" - } - - private func addHintsTapped() { - Analytics.shared.event(.tapGetMoreHints(numberOfHints: game.player.hintsAvailable)) - showAddHintsSheet = true - } - - private func closeAddHintsSheetTapped() { - Analytics.shared.event(.tapCloseGetMoreHintsDialog(numberOfHints: game.player.hintsAvailable)) - showAddHintsSheet = false - } - - private func watchAdTapped() { - Analytics.shared.event(.tapWatchRewardedAd(numberOfHints: game.player.hintsAvailable)) - requestRewardedAdAfterDismiss = true - showAddHintsSheet = false - } - - private func addHintsSheetDismissed() { - guard requestRewardedAdAfterDismiss else { return } - game.requestHints() - requestRewardedAdAfterDismiss = false - } - -} - -// MARK: - HintView_Previews - -struct HintView_Previews: PreviewProvider { - static var previews: some View { - HintView() - .environmentObject(GameplayManager(storageManager: PreviewStorageManager())) - } -} diff --git a/Riddler/Screens/MenuView.swift b/Riddler/Screens/MenuView.swift deleted file mode 100644 index 9dc01db..0000000 --- a/Riddler/Screens/MenuView.swift +++ /dev/null @@ -1,161 +0,0 @@ -import GameKit -import SwiftUI - -// MARK: - MenuView - -struct MenuView: View { - - // MARK: Internal - - @StateObject var startupManager: StartupManager - @StateObject var game: GameplayManager - - var body: some View { - NavigationStack(path: $game.navigationPath) { - ZStack { - VStack { - Spacer() - Spacer() - Text("Riddler") - .kerning(-2) - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 110)) - .foregroundStyle(RKColors.accent.swiftUIColor) - - Spacer() - - ZStack { - Grid { - GridRow { - Button(action: startTapped) { - Text("Start") - .font(RKFonts.Teko.regular.swiftUIFont(fixedSize: 48)) - .frame(width: 216, height: 64) - .padding(.bottom, -2) - .padding(.top, 2) - } - .buttonStyle(RKButtonStyle(style: .accent)) - .gridCellColumns(3) - } - - GridRow { - RKIconButton(icon: Image(systemName: "trophy"), size: .large, action: achievementsTapped) - RKIconButton(icon: Image(systemName: "person.and.background.striped.horizontal"), size: .large, action: leaderboardTapped) - RKIconButton(icon: Image(systemName: "gearshape"), size: .large, action: settingsTapped) - } - } - .opacity(startupManager.startupStep == .complete ? 1 : 0) - - ProgressView() - .controlSize(.large) - .tint(RKColors.accent.swiftUIColor) - .opacity(startupManager.startupStep != .complete ? 1 : 0) - } - - Spacer() - Spacer() - } - - switch startupManager.startupStep { - case .gameCenter: - GKAuthenticationView(failed: handleGKError, authenticated: handleGKAuthentication) - case .adsConsent: - UMPConsentView(failure: handleUMPError, completion: handleUMPConsent) - case .complete: - EmptyView() - } - } - .ignoresSafeArea(.all) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(RKBackground(isDark: true)) - .navigationDestination(for: NavigationRoute.self) { route in - switch route { - case .riddle: - RiddleView() - case .hint: - HintView() - case .victory: - VictoryView() - case .settings: - SettingsView() - } - } - .sheet(isPresented: $showLeaderboard) { - GKLeaderboardView() - } - .sheet(isPresented: $showAchievements) { - GKAchievementsView() - } - .alert("Unable to login to Game Center", isPresented: $showGKError) { - Button("OK", role: .cancel) {} - } - .analyticsScreen(.menu()) - } - .environmentObject(game) - } - - // MARK: Private - - @State private var showAchievements = false - @State private var showLeaderboard = false - @State private var showAchievementsAfterAuth = false - @State private var showLeaderboardAfterAuth = false - @State private var showGKError = false - - private func startTapped() { - Analytics.shared.event(.tapStart()) - game.startTapped() - } - - private func settingsTapped() { - Analytics.shared.event(.tapSettings()) - game.navigationPath.append(NavigationRoute.settings) - } - - private func leaderboardTapped() { - Analytics.shared.event(.tapLeaderboard()) - if !GKManager.shared.isAuthenticated { - startupManager.startupStep = .gameCenter - showLeaderboardAfterAuth = true - } else { - showLeaderboard = true - } - } - - private func achievementsTapped() { - Analytics.shared.event(.tapAchievements()) - if !GKManager.shared.isAuthenticated { - startupManager.startupStep = .gameCenter - showAchievementsAfterAuth = true - } else { - showAchievements = true - } - } - - private func handleGKAuthentication(player: GKPlayer) { - GKManager.shared.isAuthenticated = true - game.handleGameCenterAuthenticated() - startupManager.handleGameCenterComplete() - } - - private func handleGKError(error: Error) { - showGKError = true - startupManager.handleGameCenterComplete() - } - - private func handleUMPConsent() { - startupManager.handleAdsConsentcomplete() - } - - private func handleUMPError(error: Error) { - startupManager.handleAdsConsentcomplete() - } - -} - -// MARK: - MenuView_Previews - -struct MenuView_Previews: PreviewProvider { - static var previews: some View { - MenuView(startupManager: StartupManager(startupStep: .complete), game: GameplayManager(storageManager: PreviewStorageManager())) - } -} diff --git a/Riddler/Screens/RiddleView.swift b/Riddler/Screens/RiddleView.swift deleted file mode 100644 index efe779f..0000000 --- a/Riddler/Screens/RiddleView.swift +++ /dev/null @@ -1,98 +0,0 @@ -import AdSupport -import GoogleMobileAds -import Pow -import StoreKit -import SwiftUI - -// MARK: - RiddleView - -struct RiddleView: View { - - @Environment(\.requestReview) var requestReview - @EnvironmentObject var game: GameplayManager - @FocusState var isFocused: Bool - - var body: some View { - VStack { - if let riddle = game.currentRiddle { - VStack { - GeometryReader { geometry in - ScrollView(.vertical) { - Text(riddle.question) - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 30)) - .foregroundStyle(RKColors.accent.swiftUIColor) - .multilineTextAlignment(.center) - .frame(width: geometry.size.width) - .frame(minHeight: geometry.size.height) - } - } - - VStack(spacing: 10) { - HStack { - Spacer() - RKButton(title: "Hints: \(game.player.hintsAvailable)", action: game.openHints) - } - - HStack { - TextField(text: $game.answerString) { - Text("Enter answer...") - .foregroundStyle(RKColors.grey.swiftUIColor.opacity(0.8)) - } - .textInputAutocapitalization(.never) - .focused($isFocused) - .tint(RKColors.accent.swiftUIColor) - .foregroundStyle(RKColors.accent.swiftUIColor) - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 24)) - .padding(.horizontal, 16) - .frame(height: 48) - .background(RKColors.primaryDark.swiftUIColor.opacity(0.75)) - .clipShape(RoundedRectangle(cornerRadius: 8)) - .changeEffect(.shake(rate: .fast), value: game.player.currentRiddleIncorrectGuesses) - - RKIconButton(icon: Image(systemName: "chevron.right"), action: game.checkAnswer) - } - } - } - .padding(20) - .toolbar { - ToolbarItem(placement: .principal) { - Text(game.riddleNumberString) - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 24)) - .foregroundStyle(RKColors.accent.swiftUIColor) - } - } - } else { - VictoryView() - } - } - .sheet(isPresented: $game.showCorrect, onDismiss: game.correctDismissed) { - CorrectView() - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(RKBackground(isDark: true)) - .onAppear { - isFocused = true - } - .overlay { - if game.showInterstitialAd { - InterstitialAdView(completion: game.interstitialAdDidFinish) - } - } - .onChange(of: game.showAppStoreReviewPrompt) { showAppStoreReviewPrompt in - guard showAppStoreReviewPrompt else { return } - requestReview() - game.promptedForAppStoreReview() - } - .analyticsScreen(.riddle()) - } - -} - -// MARK: - RiddleView_Previews - -struct RiddleView_Previews: PreviewProvider { - static var previews: some View { - RiddleView() - .environmentObject(GameplayManager(storageManager: PreviewStorageManager())) - } -} diff --git a/Riddler/Screens/SettingsView.swift b/Riddler/Screens/SettingsView.swift deleted file mode 100644 index e1f1156..0000000 --- a/Riddler/Screens/SettingsView.swift +++ /dev/null @@ -1,116 +0,0 @@ -import SwiftUI -import UserMessagingPlatform - -// MARK: - SettingsView - -struct SettingsView: View { - - // MARK: Internal - - var body: some View { - ZStack { - VStack(spacing: 48) { - Text("Settings") - .kerning(-2) - .font(.custom("Abel-Regular", size: 60)) - .foregroundStyle(RKColors.accent.swiftUIColor) - - VStack(spacing: 24) { - RKButton( - title: "Privacy Policy", - icon: Image(systemName: "doc.fill"), - size: .fill, - action: privacyPolicyTapped - ) - - if UMPConsentInformation.sharedInstance.privacyOptionsRequirementStatus == .required { - RKButton( - title: "Update privacy", - icon: Image(systemName: "rectangle.and.pencil.and.ellipsis"), - size: .fill, - action: updatePrivacyTapped - ) - } - - RKButton( - title: "Report bug", - icon: Image(systemName: "exclamationmark.bubble.fill"), - size: .fill, - action: reportBugTapped - ) - } - - Spacer() - - Text("v1.4.0") - .font(.custom("Abel-Regular", size: 20)) - .foregroundStyle(RKColors.accent.swiftUIColor) - .onTapGesture(count: 5, perform: openDebugMenuTapped) - } - - if showPrivacyForm { - UMPPrivacyView(failure: handlePrivacyError, completion: handlePrivacyComplete) - } - } - .padding(.horizontal, 64) - .padding(.vertical, 32) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(RKBackground(isDark: true)) - .sheet(isPresented: $showDebugMenu) { - DebugView() - } - .alert("Unable to update privacy settings", isPresented: $showPrivacyError) { - Button("OK", role: .cancel) {} - } - .analyticsScreen(.settings()) - } - - // MARK: Private - - @State private var showDebugMenu: Bool = false - @State private var showPrivacyForm: Bool = false - @State private var showPrivacyError: Bool = false - - private func privacyPolicyTapped() { - Analytics.shared.event(.tapPrivacyPolicy()) - if let url = URL(string: "https://rddle.me/privacy") { - UIApplication.shared.open(url) - } - } - - private func updatePrivacyTapped() { - Analytics.shared.event(.tapUpdatePrivacy()) - showPrivacyForm = true - } - - private func reportBugTapped() { - Analytics.shared.event(.tapReportBug()) - if let url = URL(string: "mailto:bugs@rddle.me?subject=I%20have%20found%20a%20bug!") { - UIApplication.shared.open(url) - } - } - - private func openDebugMenuTapped() { - #if DEBUG - showDebugMenu = true - #endif - } - - private func handlePrivacyComplete() { - showPrivacyForm = false - } - - private func handlePrivacyError(error: Error) { - showPrivacyForm = false - showPrivacyError = true - } - -} - -// MARK: - SettingsView_Previews - -struct SettingsView_Previews: PreviewProvider { - static var previews: some View { - SettingsView() - } -} diff --git a/Riddler/Screens/StatsView.swift b/Riddler/Screens/StatsView.swift deleted file mode 100644 index 018f25e..0000000 --- a/Riddler/Screens/StatsView.swift +++ /dev/null @@ -1,61 +0,0 @@ -import SwiftUI - -// MARK: - StatsView - -struct StatsView: View { - - var stats: [DisplayStat] - - var body: some View { - VStack(spacing: 32) { - Spacer() - Text("Your Stats") - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 50)) - .foregroundStyle(RKColors.primaryDark.swiftUIColor) - - Spacer() - - VStack(spacing: 12) { - ForEach(stats, id: \.name) { stat in - StatItem(stat: stat) - } - } - Spacer() - } - .padding(.horizontal, 32) - .padding(.vertical, 32) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(RKBackground(isDark: false)) - .analyticsScreen(.stats()) - } -} - -// MARK: - StatItem - -struct StatItem: View { - let stat: DisplayStat - - var body: some View { - HStack { - Text(stat.name) - Spacer() - Text(stat.value) - } - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 24)) - .foregroundStyle(RKColors.primaryDark.swiftUIColor) - } -} - -// MARK: - StatsView_Previews - -struct StatsView_Previews: PreviewProvider { - - static let stats: [DisplayStat] = [ - .init(name: "Quickest time:", value: "1 min, 23 sec"), - .init(name: "Slowest time:", value: "3 hr, 23 min") - ] - - static var previews: some View { - StatsView(stats: stats) - } -} diff --git a/Riddler/Screens/VictoryView.swift b/Riddler/Screens/VictoryView.swift deleted file mode 100644 index d7cec5e..0000000 --- a/Riddler/Screens/VictoryView.swift +++ /dev/null @@ -1,67 +0,0 @@ -import Pow -import SwiftUI -import Vortex - -// MARK: - VictoryView - -struct VictoryView: View { - - @EnvironmentObject var game: GameplayManager - - var body: some View { - VStack(spacing: 32) { - Spacer() - - Image(systemName: "trophy") - .font(.system(size: 180)) - .fontWeight(.thin) - .foregroundStyle(RKColors.accent.swiftUIColor) - - VStack(spacing: 0) { - Text("You have beaten") - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 36)) - - Text("Riddler") - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 80)) - } - .multilineTextAlignment(.center) - .foregroundStyle(RKColors.accent.swiftUIColor) - - HStack(spacing: 12) { - RKButton(title: "Stats", icon: Image(systemName: "chart.bar.fill"), size: .fill, action: game.victoryStatsTapped) - RKButton(title: "Share", icon: Image(systemName: "square.and.arrow.up"), size: .fill, action: game.shareVictory) - } - .padding(.horizontal, 32) - Spacer() - } - .padding(20) - .frame(maxWidth: .infinity) - .background( - ZStack { - RKBackground(isDark: true) - VortexView(.fireworks) { - Circle() - .fill(.white) - .blendMode(.plusLighter) - .frame(width: 32) - .tag("circle") - } - .opacity(0.5) - } - ) - .sheet(isPresented: $game.showVictoryStats) { - StatsView(stats: game.victoryStats) - } - .toast(isShowing: $game.showShareVictoryToast, icon: Image(systemName: "bubble.left.fill"), text: "Copied to clipboard") - .analyticsScreen(.victory()) - } -} - -// MARK: - VictoryView_Previews - -struct VictoryView_Previews: PreviewProvider { - static var previews: some View { - VictoryView() - .environmentObject(GameplayManager(storageManager: PreviewStorageManager())) - } -} diff --git a/Riddler/UI/Ads/ConsentManager.swift b/Riddler/UI/Ads/ConsentManager.swift new file mode 100644 index 0000000..7d9446b --- /dev/null +++ b/Riddler/UI/Ads/ConsentManager.swift @@ -0,0 +1,261 @@ +import Foundation +import UserMessagingPlatform +import AppTrackingTransparency +import Firebase +import GoogleMobileAds + +class ConsentManager { + static let shared = ConsentManager() + private init() {} + + var consentForm: UMPConsentForm? = nil + var userConsentType = ConsentType.none + + var ConsentConfigured: Bool = false + + enum ConsentType { + case full + case partial + case none + } + + var completion: () -> Void = {} + + func startupConsent(completion: @escaping () -> Void) { + + self.completion = completion + + // Reset consent if flag set + if TestVariables.RESET_CONSENT { + UMPConsentInformation.sharedInstance.reset() + } + + // Create parameter list for consent update + let parameters = UMPRequestParameters() + parameters.tagForUnderAgeOfConsent = false + + // Check if consent needs to be collected + UMPConsentInformation.sharedInstance.requestConsentInfoUpdate(with: parameters, completionHandler: { updateError in + if updateError != nil { + // Consent is unknown therefore disable ads + print("Consent request error") + print(updateError ?? "") + self.updateConsentResults(disableAds: true) + + } else { + // Check if consent form is available + let formStatus = UMPConsentInformation.sharedInstance.formStatus + if formStatus == UMPFormStatus.available { + // Load consent form + self.loadConsentForm(andDisplay: true) + + } + } + }) + } + + private func loadConsentForm(andDisplay: Bool) { + // Load consent form + UMPConsentForm.load(completionHandler: { form, loadError in + if loadError != nil { + // Unable to display consent form therefore disable ads + print("Consent form error") + print(loadError ?? "") + self.updateConsentResults(disableAds: true) + + } else { + // Store the consent form to be used + print("Consent Form: Loaded") + self.consentForm = form + if andDisplay { + self.displayConsentForm(forceDisplay: false) + } + } + }) + } + + func displayConsentForm(forceDisplay: Bool) { + // Check if consent is required or form forced to display + if UMPConsentInformation.sharedInstance.consentStatus == UMPConsentStatus.required || forceDisplay { + // Display consent form + print("Consent Status: Required") + self.consentForm?.present(from: UIApplication.shared.windows.first!.rootViewController! as UIViewController) { dismissError in + if UMPConsentInformation.sharedInstance.consentStatus == UMPConsentStatus.obtained { + // Detect which level on consent provided + print("Consent Status: Obtained") + self.updateConsentResults(disableAds: false) + } else { + // Consent unknown therefore disable ads + print("Consent Status: Unknown") + self.updateConsentResults(disableAds: true) + } + } + + } else if UMPConsentInformation.sharedInstance.consentStatus == UMPConsentStatus.obtained { + print("Consent Status: Obtained") + self.updateConsentResults(disableAds: false) + } else if UMPConsentInformation.sharedInstance.consentStatus == UMPConsentStatus.notRequired { + print("Consent Status: Not Required") + self.updateConsentResults(disableAds: false) + } else if UMPConsentInformation.sharedInstance.consentStatus == UMPConsentStatus.unknown { + print("Consent Status: Unknown") + self.updateConsentResults(disableAds: true) + } + + // Load a new consent form in case form is shown again + self.loadConsentForm(andDisplay: false) + } + + // Sets the consent type to save whether consent given + private func updateConsentResults(disableAds: Bool) { + + print("Start: Consent") + + ConsentConfigured = true + + // If ads are disabled no checks required + if disableAds { + self.userConsentType = ConsentType.none + } else { + // Check to see what type of ads can be shown with the users current consent values + if self.canShowPersonalizedAds() { + self.userConsentType = ConsentType.full + print("Consent Type: Personalized") + } else if self.canShowAds() { + self.userConsentType = ConsentType.partial + print("Consent Type: Non Personalized") + } else { + self.userConsentType = ConsentType.none + print("Consent Type: None") + } + } + + // Check if Firebase is running + if FirebaseApp.app() != nil { + // Check if consent is still given + if self.userConsentType != ConsentType.full || TestVariables.DISABLE_FIREBASE { + // Delete any Firebase apps currently running + FirebaseApp.app()?.delete({ _ in }) + } + } else { + if self.userConsentType == ConsentType.full && !TestVariables.DISABLE_FIREBASE { + // Startup Firebase if permission is given and not already running + print("Firebase started") + FirebaseApp.configure() + } + } + + // Check if any consent given + if self.userConsentType != ConsentType.none { + // Startup Google Mobile Ads + GADMobileAds.sharedInstance().start(completionHandler: nil) + + InterstitialAd.shared.clearAd() + RewardedAd.shared.clearAd() + + InterstitialAd.shared.loadAd(withAdUnitID: interstitialId) + RewardedAd.shared.loadAd(withAdUnitID: rewardedId) + + } + + completion() + + } + + // Check if non personalized ads can be shown + private func canShowAds() -> Bool { + let settings = UserDefaults.standard + + //https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md#in-app-details + //https://support.google.com/admob/answer/9760862?hl=en&ref_topic=9756841 + + let purposeConsent = settings.string(forKey: "IABTCF_PurposeConsents") ?? "" + let vendorConsent = settings.string(forKey: "IABTCF_VendorConsents") ?? "" + let vendorLI = settings.string(forKey: "IABTCF_VendorLegitimateInterests") ?? "" + let purposeLI = settings.string(forKey: "IABTCF_PurposeLegitimateInterests") ?? "" + + let googleId = 755 + let hasGoogleVendorConsent = hasAttribute(input: vendorConsent, index: googleId) + let hasGoogleVendorLI = hasAttribute(input: vendorLI, index: googleId) + + // Minimum required for at least non-personalized ads + return hasConsentFor([1], purposeConsent, hasGoogleVendorConsent) + && hasConsentOrLegitimateInterestFor([2,7,9,10], purposeConsent, purposeLI, hasGoogleVendorConsent, hasGoogleVendorLI) + + } + + // Check if personalized ads can be shown + private func canShowPersonalizedAds() -> Bool { + let settings = UserDefaults.standard + + //https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md#in-app-details + //https://support.google.com/admob/answer/9760862?hl=en&ref_topic=9756841 + + // required for personalized ads + let purposeConsent = settings.string(forKey: "IABTCF_PurposeConsents") ?? "" + let vendorConsent = settings.string(forKey: "IABTCF_VendorConsents") ?? "" + let vendorLI = settings.string(forKey: "IABTCF_VendorLegitimateInterests") ?? "" + let purposeLI = settings.string(forKey: "IABTCF_PurposeLegitimateInterests") ?? "" + + let googleId = 755 + let hasGoogleVendorConsent = hasAttribute(input: vendorConsent, index: googleId) + let hasGoogleVendorLI = hasAttribute(input: vendorLI, index: googleId) + + if !hasAllowedATT() { + return false + } else { + return hasConsentFor([1,3,4], purposeConsent, hasGoogleVendorConsent) + && hasConsentOrLegitimateInterestFor([2,7,9,10], purposeConsent, purposeLI, hasGoogleVendorConsent, hasGoogleVendorLI) + } + } + + // Check if the user clicked allow on ATT + private func hasAllowedATT() -> Bool { + + var allowed = false + + ATTrackingManager.requestTrackingAuthorization { status in + switch status { + case .authorized: + allowed = true + case .denied: + allowed = false + case .notDetermined: + allowed = false + case .restricted: + allowed = false + @unknown default: + allowed = false + } + } + return allowed + } + + // Check if the user is in the EEA + private func isGDPR() -> Bool { + let settings = UserDefaults.standard + let gdpr = settings.integer(forKey: "IABTCF_gdprApplies") + return gdpr == 1 + } + + // Check if a binary string has a "1" at position "index" (1-based) + private func hasAttribute(input: String, index: Int) -> Bool { + return input.count >= index && String(Array(input)[index-1]) == "1" + } + + // Check if consent is given for a list of purposes + private func hasConsentFor(_ purposes: [Int], _ purposeConsent: String, _ hasVendorConsent: Bool) -> Bool { + return purposes.allSatisfy { i in hasAttribute(input: purposeConsent, index: i) } && hasVendorConsent + } + + // Check if a vendor either has consent or legitimate interest for a list of purposes + private func hasConsentOrLegitimateInterestFor(_ purposes: [Int], _ purposeConsent: String, _ purposeLI: String, _ hasVendorConsent: Bool, _ hasVendorLI: Bool) -> Bool { + return purposes.allSatisfy { i in + (hasAttribute(input: purposeLI, index: i) && hasVendorLI) || + (hasAttribute(input: purposeConsent, index: i) && hasVendorConsent) + } + } + + + +} diff --git a/Riddler/UI/Ads/FullscreenModifier.swift b/Riddler/UI/Ads/FullscreenModifier.swift new file mode 100644 index 0000000..159b0b5 --- /dev/null +++ b/Riddler/UI/Ads/FullscreenModifier.swift @@ -0,0 +1,54 @@ +import SwiftUI + +struct FullScreenAd: View { + + @Binding var isPresented: Bool + @Binding var isErrorShowing: Bool + @State var adType: AdType + + enum AdType { + case interstitial + case rewarded + } + + var rewardFunc: () -> Void + var adUnitId: String + var parent: Parent + + var body: some View { + ZStack { + parent + + if isPresented { + EmptyView() + .edgesIgnoringSafeArea(.all) + + if adType == .rewarded { + RewardedAdView(isPresented: $isPresented, isErrorShowing: $isErrorShowing, adUnitId: adUnitId, rewardFunc: rewardFunc) + .edgesIgnoringSafeArea(.all) + } else if adType == .interstitial { + InterstitialAdView(isPresented: $isPresented, adUnitId: adUnitId) + } + } + } + .onAppear { + if adType == .rewarded { + RewardedAd.shared.loadAd(withAdUnitID: adUnitId) + } else if adType == .interstitial { + InterstitialAd.shared.loadAd(withAdUnitID: adUnitId) + } + } + } + +} + +extension View { + func presentRewardedAd(isPresented: Binding, isErrorShowing: Binding, adUnitId: String, rewardFunc: @escaping (() -> Void), game: GameViewModel) -> some View { + FullScreenAd(isPresented: isPresented, isErrorShowing: isErrorShowing, adType: .rewarded, rewardFunc: rewardFunc, adUnitId: adUnitId, parent: self) + .environmentObject(game) + } + + func presentInterstitialAd(isPresented: Binding, adUnitId: String) -> some View { + FullScreenAd(isPresented: isPresented, isErrorShowing: .constant(false), adType: .interstitial, rewardFunc: {}, adUnitId: adUnitId, parent: self) + } +} diff --git a/Riddler/UI/Ads/InterstitialAd.swift b/Riddler/UI/Ads/InterstitialAd.swift new file mode 100644 index 0000000..9e5c1e3 --- /dev/null +++ b/Riddler/UI/Ads/InterstitialAd.swift @@ -0,0 +1,89 @@ +import SwiftUI +import GoogleMobileAds +import Firebase +import FirebaseAnalytics + +class InterstitialAd: NSObject { + var interstitialAd: GADInterstitialAd? + + static let shared = InterstitialAd() + + func loadAd(withAdUnitID id: String) { + let req = GADRequest() + GADInterstitialAd.load(withAdUnitID: id, request: req) { interstitialAd, err in + if let err = err { + print("Failed to load ad with error: \(err)") + + return + } + print("Ad loaded") + interstitialLoadedEvent() + self.interstitialAd = interstitialAd + } + } + + func clearAd() { + interstitialAd = nil + } + +} + +final class InterstitialAdView: NSObject, UIViewControllerRepresentable, GADFullScreenContentDelegate { + + let interstitialAd = InterstitialAd.shared.interstitialAd + @Binding var isPresented: Bool + var adUnitId: String + + init(isPresented: Binding, adUnitId: String) { + self._isPresented = isPresented + self.adUnitId = adUnitId + super.init() + + interstitialAd?.fullScreenContentDelegate = self + } + + func makeUIViewController(context: Context) -> some UIViewController { + let view = UIViewController() + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) { + self.showAd(from: view) + } + + return view + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {} + + func showAd(from root: UIViewController) { + if let ad = interstitialAd { + ad.present(fromRootViewController: root) + } else { + print("Ad not ready") + interstitialNotReadyEvent() + self.isPresented.toggle() + } + } + + func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) { + // Prepare another ad for the next time the view is presented + InterstitialAd.shared.loadAd(withAdUnitID: adUnitId) + + // Dismisses the view once ad dismissed + isPresented.toggle() + } + + /// Tells the delegate that the ad failed to present full screen content. + func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) { + print("Ad failed to display") + InterstitialAd.shared.loadAd(withAdUnitID: adUnitId) + isPresented = false + interstitialFailedToShow(error: error) + } + + /// Tells the delegate that the ad will present full screen content. + func adWillPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) { + print("Ad displaying") + interstitialShownEvent() + } + +} diff --git a/Riddler/UI/Ads/RewardedAd.swift b/Riddler/UI/Ads/RewardedAd.swift new file mode 100644 index 0000000..25dba26 --- /dev/null +++ b/Riddler/UI/Ads/RewardedAd.swift @@ -0,0 +1,99 @@ +import SwiftUI +import GoogleMobileAds +import Firebase +import FirebaseAnalytics + +class RewardedAd: NSObject { + var rewardedAd: GADRewardedAd? + + static let shared = RewardedAd() + + func loadAd(withAdUnitID id: String) { + let req = GADRequest() + GADRewardedAd.load(withAdUnitID: id, request: req) { rewardedAd, error in + if let error = error { + print("Failed to load ad with error: \(error)") + rewardedFailedToLoad(error: error) + return + } + rewardedLoadedEvent() + self.rewardedAd = rewardedAd + } + } + + func clearAd() { + rewardedAd = nil + } + +} + +final class RewardedAdView: NSObject, UIViewControllerRepresentable, GADFullScreenContentDelegate { + + let rewardedAd = RewardedAd.shared.rewardedAd + @Binding var isPresented: Bool + @Binding var isErrorShowing: Bool + let adUnitId: String + let rewardFunc: (() -> Void) + + init(isPresented: Binding, isErrorShowing: Binding, adUnitId: String, rewardFunc: @escaping (() -> Void)) { + self._isPresented = isPresented + self._isErrorShowing = isErrorShowing + self.adUnitId = adUnitId + self.rewardFunc = rewardFunc + + super.init() + + rewardedAd?.fullScreenContentDelegate = self + } + + func makeUIViewController(context: Context) -> some UIViewController { + let view = UIViewController() + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) { + self.showAd(from: view) + } + + return view + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { + + } + + func showAd(from root: UIViewController) { + if let ad = rewardedAd { + ad.present(fromRootViewController: root) { + self.rewardFunc() + } + } else { + print("Ad not ready") + rewardedNotReadyEvent() + self.isPresented = false + } + } + + func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) { + print("Ad finished displaying") + RewardedAd.shared.loadAd(withAdUnitID: adUnitId) + isPresented = false + } + + /// Tells the delegate that the ad failed to present full screen content. + func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) { + print("Ad failed to display") + RewardedAd.shared.loadAd(withAdUnitID: adUnitId) + rewardedFailedToShow(error: error) + isPresented = false + withAnimation { + isErrorShowing = true + } + + } + + /// Tells the delegate that the ad will present full screen content. + func adWillPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) { + print("Ad displaying") + rewardedShownEvent() + } + +} diff --git a/Riddler/UI/Components/BackButton.swift b/Riddler/UI/Components/BackButton.swift new file mode 100644 index 0000000..0eb2bf3 --- /dev/null +++ b/Riddler/UI/Components/BackButton.swift @@ -0,0 +1,25 @@ +import SwiftUI + +struct BackButton: View { + var pressBack: () -> Void + + var body: some View { + VStack { + HStack { + IconButton(image: "back", size: 32, action: pressBack) + Spacer() + } + Spacer() + } + .padding(20) + } +} + +struct BackButton_Previews: PreviewProvider { + static var previews: some View { + BackButton(pressBack: {}) + } +} + + + diff --git a/Riddler/UI/Components/Background.swift b/Riddler/UI/Components/Background.swift new file mode 100644 index 0000000..77bd2da --- /dev/null +++ b/Riddler/UI/Components/Background.swift @@ -0,0 +1,21 @@ +import SwiftUI + +struct Background: View { + var isDark: Bool + + var body: some View { + ZStack { + Color(isDark ? "Primary" : "Accent").ignoresSafeArea() + Image(isDark ? "bg_primary" : "bg_accent") + .resizable() + .edgesIgnoringSafeArea(.all) + } + } +} + +struct Background_Previews: PreviewProvider { + static var previews: some View { + Background(isDark: true) + Background(isDark: false) + } +} diff --git a/Riddler/UI/Components/Extensions.swift b/Riddler/UI/Components/Extensions.swift new file mode 100644 index 0000000..cb5955c --- /dev/null +++ b/Riddler/UI/Components/Extensions.swift @@ -0,0 +1,27 @@ +import SwiftUI + +extension View { + func hideKeyboard() { + let resign = #selector(UIResponder.resignFirstResponder) + UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil) + } +} + +extension View { + func placeholder(when shouldShow: Bool, alignment: Alignment = .leading, @ViewBuilder placeholder: () -> Content) -> some View { + ZStack(alignment: alignment) { + placeholder().opacity(shouldShow ? 1 : 0) + self + } + } +} + +extension View { + func placeholder(_ text: String, when shouldShow: Bool, alignment: Alignment = .leading) -> some View { + placeholder(when: shouldShow, alignment: alignment) { + Text(text) + .foregroundColor(.gray) + .font(Font.custom("Abel-Regular", size: 30)) + } + } +} diff --git a/Riddler/UI/Components/IconButton.swift b/Riddler/UI/Components/IconButton.swift new file mode 100644 index 0000000..1d53a06 --- /dev/null +++ b/Riddler/UI/Components/IconButton.swift @@ -0,0 +1,24 @@ +import SwiftUI + +struct IconButton: View { + let image: String + let size: CGFloat + let action: () -> Void + + var body: some View { + Button(action: self.action) { + Image(image) + .resizable() + .scaledToFit() + .frame(width: size, height: size) + .padding(10) + } + } + +} + +struct IconButton_Previews: PreviewProvider { + static var previews: some View { + IconButton(image: "back", size: 50, action: {}) + } +} diff --git a/Riddler/UI/Components/LottieView.swift b/Riddler/UI/Components/LottieView.swift new file mode 100644 index 0000000..72475c9 --- /dev/null +++ b/Riddler/UI/Components/LottieView.swift @@ -0,0 +1,33 @@ +import Foundation +import SwiftUI +import Lottie + +struct LottieView: UIViewRepresentable { + var name: String + var loopMode: LottieLoopMode = .playOnce + + var animationView = AnimationView() + + func makeUIView(context: UIViewRepresentableContext) -> some UIView { + let view = UIView(frame: .zero) + + animationView.animation = Animation.named(name) + animationView.contentMode = .scaleAspectFit + animationView.loopMode = loopMode + animationView.backgroundBehavior = .pauseAndRestore + animationView.animationSpeed = 1.5 + + animationView.play() + + animationView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(animationView) + + NSLayoutConstraint.activate([ + animationView.heightAnchor.constraint(equalTo: view.heightAnchor), + animationView.widthAnchor.constraint(equalTo: view.widthAnchor)]) + + return view + } + + func updateUIView(_ uiView: UIViewType, context: UIViewRepresentableContext) {} +} diff --git a/Riddler/UI/Components/TextIconButton.swift b/Riddler/UI/Components/TextIconButton.swift new file mode 100644 index 0000000..ce28edd --- /dev/null +++ b/Riddler/UI/Components/TextIconButton.swift @@ -0,0 +1,27 @@ +import SwiftUI + +struct TextIconButton: View { + var text: String + var image: String + var iconSize: CGFloat + var action: () -> Void + + var body: some View { + Button(action: action) { + HStack(alignment: .center) { + Text(text) + .padding(.trailing, 4) + Image(image) + .resizable() + .frame(width: iconSize, height: iconSize) + } + .frame(width: 160, height: 50) + } + } +} + +struct TextIconButton_Previews: PreviewProvider { + static var previews: some View { + TextIconButton(text: "Stats", image: "stats", iconSize: 28, action: {}) + } +} diff --git a/Riddler/UI/Components/Toast.swift b/Riddler/UI/Components/Toast.swift new file mode 100644 index 0000000..18f54c9 --- /dev/null +++ b/Riddler/UI/Components/Toast.swift @@ -0,0 +1,63 @@ +import SwiftUI + +struct ToastModifier: ViewModifier { + let image: String + let text: String + @Binding var isShowing: Bool + let duration: TimeInterval + let isError: Bool + let customImage: Bool + + func body(content: Content) -> some View { + ZStack { + content + if isShowing { + toast + .zIndex(1) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + duration) { + withAnimation() { + isShowing.toggle() + } + } + } + } + } + } + + private var toast: some View { + VStack { + Spacer() + HStack(alignment: customImage ? .center : .lastTextBaseline, spacing: 20) { + if image != "" { + if customImage { + Image(image) + .resizable() + .scaledToFit() + .frame(width: 36, height: 36) + } else { + Image(systemName: image) + .font(.system(size: 25)) + } + } + Text(text) + .modifier(ButtonTextStyle(textSize: 30)) + } + .foregroundColor(isError ? Color("PrimaryDark") : Color("Accent")) + .padding() + .padding(.horizontal, 10) + .background(isError ? Color("Error") : Color("Primary")) + .cornerRadius(5) + .shadow(radius: 15) + } + .padding(30) + .padding(.bottom, 60) + } + +} + +extension View { + func toast(image: String, customImage: Bool = false, text: String, isShowing: Binding, duration: TimeInterval = 2, isError: Bool = false) -> some View { + modifier(ToastModifier(image: image, text: text, isShowing: isShowing, duration: duration, isError: isError, customImage: customImage)) + } +} diff --git a/Riddler/UI/Dialogs/AdFailedDialog.swift b/Riddler/UI/Dialogs/AdFailedDialog.swift new file mode 100644 index 0000000..dcc7866 --- /dev/null +++ b/Riddler/UI/Dialogs/AdFailedDialog.swift @@ -0,0 +1,53 @@ +import SwiftUI + +struct AdFailedDialog: View { + + var privacyAction: () -> Void + var closeAction: () -> Void + + var body: some View { + ZStack { + // Darken background + Color.black + .edgesIgnoringSafeArea(.all) + .opacity(0.8) + + // Dialog box + + VStack(spacing: 25) { + + Text("Ad Failed to Load") + .modifier(AccentTextStyle(textSize: 30)) + .multilineTextAlignment(.center) + + Group { + Text("Check your internet connection. Update your ") + + Text("privacy settings").foregroundColor(Color("Accent")) + + Text(" to allow ads.") + } + .modifier(SecondaryTextStyle(textSize: 20)) + .multilineTextAlignment(.center) + .onTapGesture(perform: privacyAction) + + + Button(action: closeAction) { + Text("Close") + .padding(EdgeInsets(top: 4, leading: 10, bottom: 4, trailing: 10)) + .frame(width: 125) + } + .buttonStyle(SecondaryButtonStyle()) + .modifier(ButtonTextStyle(textSize: 30)) + } + .padding(20) + .background(Color("Primary")) + .cornerRadius(5) + } + .zIndex(1) + } +} + +struct AdFailedDialog_Previews: PreviewProvider { + static var previews: some View { + AdFailedDialog(privacyAction: {}, closeAction: {}) + } +} diff --git a/Riddler/UI/Dialogs/BasicDialog.swift b/Riddler/UI/Dialogs/BasicDialog.swift new file mode 100644 index 0000000..f70cb1c --- /dev/null +++ b/Riddler/UI/Dialogs/BasicDialog.swift @@ -0,0 +1,62 @@ +import SwiftUI + +struct BasicDialog: View { + + var titleText: String + var bodyText: String + + var primaryText: String + var secondaryText: String + + var primaryAction: () -> Void + var secondaryAction: () -> Void + + var body: some View { + ZStack { + // Darken background + Color.black + .edgesIgnoringSafeArea(.all) + .opacity(0.8) + + // Dialog box + VStack(spacing: 20) { + Text(titleText) + .modifier(AccentTextStyle(textSize: 30)) + .multilineTextAlignment(.center) + + Text(bodyText) + .modifier(AccentTextStyle(textSize: 20)) + .multilineTextAlignment(.center) + + + HStack(spacing: 24) { + Button(action: secondaryAction) { + Text(secondaryText) + .padding(EdgeInsets(top: 4, leading: 10, bottom: 4, trailing: 10)) + .frame(width: 125) + } + .buttonStyle(SecondaryButtonStyle()) + .modifier(ButtonTextStyle(textSize: 30)) + + Button(action: primaryAction) { + Text(primaryText) + .padding(EdgeInsets(top: 4, leading: 10, bottom: 4, trailing: 10)) + .frame(width: 125) + } + .buttonStyle(AccentButtonStyle(disabled: false)) + .modifier(ButtonTextStyle(textSize: 30)) + } + } + .padding(20) + .background(Color("Primary")) + .cornerRadius(5) + } + .zIndex(1) + } +} + +struct BasicDialog_Previews: PreviewProvider { + static var previews: some View { + BasicDialog(titleText: "Need some help?", bodyText: "Watch a video to unlock 3 more hints", primaryText: "Watch", secondaryText: "Close", primaryAction: {}, secondaryAction: {}) + } +} diff --git a/Riddler/UI/GameKit/AchievementsView.swift b/Riddler/UI/GameKit/AchievementsView.swift new file mode 100644 index 0000000..57e8e30 --- /dev/null +++ b/Riddler/UI/GameKit/AchievementsView.swift @@ -0,0 +1,45 @@ +import SwiftUI +import UIKit +import GameKit + +final class AchievementsView: NSObject, UIViewControllerRepresentable, GKGameCenterControllerDelegate { + + @Binding var isPresented: Bool + + init(isPresented: Binding) { + self._isPresented = isPresented + } + + func makeUIViewController(context: Context) -> some UIViewController { + let viewController = GKGameCenterViewController(state: .achievements) + viewController.gameCenterDelegate = self + return viewController + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { + + } + + func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) { + isPresented = false + } + +} + +struct AchievementModifier: ViewModifier { + + @Binding var isPresented: Bool + + func body(content: Content) -> some View { + content + if isPresented { + AchievementsView(isPresented: $isPresented) + } + } +} + +extension View { + func presentAchievements(isPresented: Binding) -> some View { + modifier(AchievementModifier(isPresented: isPresented)) + } +} diff --git a/Riddler/UI/GameKit/AuthenticateView.swift b/Riddler/UI/GameKit/AuthenticateView.swift new file mode 100644 index 0000000..54f1504 --- /dev/null +++ b/Riddler/UI/GameKit/AuthenticateView.swift @@ -0,0 +1,74 @@ +import SwiftUI +import UIKit +import GameKit + +struct AuthenticateView: UIViewControllerRepresentable { + + @Binding var isPresented: Bool + + init(isPresented: Binding) { + self._isPresented = isPresented + } + + func makeUIViewController(context: Context) -> some UIViewController { + let viewController = UIViewController() + + if !GKLocalPlayer.local.isAuthenticated { + + GKLocalPlayer.local.authenticateHandler = { vc, error in + + if let vc = vc { + viewController.present(vc, animated: true) + } + if error != nil { + print("GC: Error") + print(error?.localizedDescription ?? "") + GameKitManager.shared.gameKitEnabled = false + isPresented = false + print("GC: Authenticted = \(GKLocalPlayer.local.isAuthenticated)") + } + if vc == nil && error == nil { + GameKitManager.shared.gameKitEnabled = true + + if TestVariables.RESET_ACHIEVEMENTS { + print("GC: Achievements Reset") + GKAchievement.resetAchievements() + } + + isPresented = false + print("GC: Authenticted = \(GKLocalPlayer.local.isAuthenticated)") + } + + } + } else { + GameKitManager.shared.gameKitEnabled = true + isPresented = false + print("GC: Authenticted = \(GKLocalPlayer.local.isAuthenticated)") + } + + return viewController + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {} + +} + +struct AuthenticateModifier: ViewModifier { + + @Binding var isPresented: Bool + + func body(content: Content) -> some View { + content + if isPresented { + AuthenticateView(isPresented: $isPresented) + } + } +} + +extension View { + func presentAuthenticate(isPresented: Binding) -> some View { + modifier(AuthenticateModifier(isPresented: isPresented)) + } +} + + diff --git a/Riddler/UI/GameKit/GameKitManager.swift b/Riddler/UI/GameKit/GameKitManager.swift new file mode 100644 index 0000000..a64d65c --- /dev/null +++ b/Riddler/UI/GameKit/GameKitManager.swift @@ -0,0 +1,11 @@ +import Foundation + +class GameKitManager { + static let shared = GameKitManager() + private init() {} + + var gameKitEnabled: Bool = false + var gameKitStarted: Bool = false + + +} diff --git a/Riddler/UI/GameKit/LeaderboardView.swift b/Riddler/UI/GameKit/LeaderboardView.swift new file mode 100644 index 0000000..c9c4340 --- /dev/null +++ b/Riddler/UI/GameKit/LeaderboardView.swift @@ -0,0 +1,45 @@ +import SwiftUI +import UIKit +import GameKit + +final class LeaderboardView: NSObject, UIViewControllerRepresentable, GKGameCenterControllerDelegate { + + @Binding var isPresented: Bool + + init(isPresented: Binding) { + self._isPresented = isPresented + } + + func makeUIViewController(context: Context) -> some UIViewController { + let viewController = GKGameCenterViewController(leaderboardID: "riddle_progress", playerScope: .friendsOnly, timeScope: .allTime) + viewController.gameCenterDelegate = self + return viewController + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { + + } + + func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) { + isPresented = false + } + +} + +struct LeaderboardModifier: ViewModifier { + + @Binding var isPresented: Bool + + func body(content: Content) -> some View { + content + if isPresented { + LeaderboardView(isPresented: $isPresented) + } + } +} + +extension View { + func presentLeaderboard(isPresented: Binding) -> some View { + modifier(LeaderboardModifier(isPresented: isPresented)) + } +} diff --git a/Riddler/UI/RKBackground.swift b/Riddler/UI/RKBackground.swift deleted file mode 100644 index 0eeee08..0000000 --- a/Riddler/UI/RKBackground.swift +++ /dev/null @@ -1,27 +0,0 @@ -import SwiftUI - -// MARK: - RKBackground - -struct RKBackground: View { - var isDark: Bool - - var body: some View { - ZStack { - Color(uiColor: isDark ? RKColors.primary.color : RKColors.accent.color) - .ignoresSafeArea(edges: .all) - Image(isDark ? "primaryBackground" : "accentBackground") - .resizable() - .aspectRatio(contentMode: .fill) - .ignoresSafeArea(edges: .all) - } - } -} - -// MARK: - Background_Previews - -struct Background_Previews: PreviewProvider { - static var previews: some View { - RKBackground(isDark: true) - RKBackground(isDark: false) - } -} diff --git a/Riddler/UI/RKBottomSheetPrompt.swift b/Riddler/UI/RKBottomSheetPrompt.swift deleted file mode 100644 index e3bea8c..0000000 --- a/Riddler/UI/RKBottomSheetPrompt.swift +++ /dev/null @@ -1,55 +0,0 @@ -import SwiftUI - -// MARK: - RKBottomSheetPrompt - -struct RKBottomSheetPrompt: View { - - var titleText: String - var bodyText: String - var primaryText: String - var secondaryText: String - var primaryAction: () -> Void - var secondaryAction: () -> Void - - var body: some View { - VStack(spacing: 0) { - Text(titleText) - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 36)) - .foregroundStyle(RKColors.accent.swiftUIColor) - .multilineTextAlignment(.center) - - Spacer(minLength: 16) - - Text(bodyText) - .font(RKFonts.Abel.regular.swiftUIFont(fixedSize: 24)) - .foregroundStyle(RKColors.accent.swiftUIColor) - .multilineTextAlignment(.center) - .fixedSize(horizontal: false, vertical: true) - - Spacer(minLength: 16) - - HStack(spacing: 24) { - RKButton(title: secondaryText, style: .grey, size: .fill, action: secondaryAction) - RKButton(title: primaryText, style: .accent, size: .fill, action: primaryAction) - } - } - .frame(maxWidth: .infinity) - .padding(16) - .background(RKColors.primary.swiftUIColor) - } -} - -// MARK: - BasicDialog_Previews - -struct BasicDialog_Previews: PreviewProvider { - static var previews: some View { - RKBottomSheetPrompt( - titleText: "Need some help?", - bodyText: "Watch a video to unlock 3 more hints", - primaryText: "Watch", - secondaryText: "Close", - primaryAction: {}, - secondaryAction: {} - ) - } -} diff --git a/Riddler/UI/RKButton.swift b/Riddler/UI/RKButton.swift deleted file mode 100644 index e73aa0f..0000000 --- a/Riddler/UI/RKButton.swift +++ /dev/null @@ -1,150 +0,0 @@ -import SwiftUI - -// MARK: - RKButton - -struct RKButton: View { - - // MARK: Lifecycle - - init(title: String, icon: Image? = nil, style: Style = .accent, size: Size = .flex, action: @escaping () -> Void) { - self.title = title - self.icon = icon - self.style = style - self.size = size - self.action = action - } - - // MARK: Internal - - enum Style { - case accent - case primary - case grey - - // MARK: Internal - - func foregroundColor() -> Color { - switch self { - case .accent, .grey: RKColors.primaryDark.swiftUIColor - case .primary: RKColors.accent.swiftUIColor - } - } - - func backgroundColor(isPressed: Bool) -> Color { - isPressed ? pressedBackgroundColor : defaultBackgroundColor - } - - // MARK: Private - - private var defaultBackgroundColor: Color { - switch self { - case .accent: RKColors.accent.swiftUIColor - case .primary: RKColors.primary.swiftUIColor - case .grey: RKColors.grey.swiftUIColor - } - } - - private var pressedBackgroundColor: Color { - switch self { - case .accent: RKColors.accentDark.swiftUIColor - case .primary: RKColors.primaryDark.swiftUIColor - case .grey: RKColors.grey2.swiftUIColor - } - } - - } - - enum Size { - case fill - case flex - - // MARK: Internal - - var maxWidth: CGFloat? { - switch self { - case .flex: nil - case .fill: .infinity - } - } - - var horizontalPadding: CGFloat? { - switch self { - case .flex: 24 - case .fill: nil - } - } - } - - var body: some View { - Button(action: action) { - Label( - title: { - Text(title) - .padding(.bottom, -2) - .fixedSize(horizontal: false, vertical: true) - .multilineTextAlignment(.center) - .padding(.vertical, 4) - }, - icon: { - if let icon { - icon - .font(.system(size: 20)) - .fontWeight(.bold) - } - } - ) - .frame(maxWidth: size.maxWidth) - .frame(minHeight: 48) - .padding(.horizontal, size.horizontalPadding) - } - .buttonStyle(RKButtonStyle(style: style)) - } - - // MARK: Private - - private let title: String - private let icon: Image? - private let style: Style - private let size: Size - private let action: () -> Void - -} - -// MARK: - RKButton_Previews - -struct RKButton_Previews: PreviewProvider { - static var previews: some View { - ScrollView { - VStack(spacing: 0) { - VStack { - RKButton(title: "Share", style: .accent, action: {}) - RKButton(title: "Share", icon: Image(systemName: "chart.bar.fill"), style: .accent, action: {}) - RKButton(title: "Share", style: .accent, size: .fill, action: {}) - RKButton(title: "Share", icon: Image(systemName: "chart.bar.fill"), style: .accent, size: .fill, action: {}) - } - .padding(16) - .background(RKColors.primary.swiftUIColor) - - VStack { - RKButton(title: "Share", style: .primary, action: {}) - RKButton(title: "Share", icon: Image(systemName: "chart.bar.fill"), style: .primary, action: {}) - RKButton(title: "Share", style: .primary, size: .fill, action: {}) - RKButton(title: "Share", icon: Image(systemName: "chart.bar.fill"), style: .primary, size: .fill, action: {}) - } - .padding(16) - .background(RKColors.accent.swiftUIColor) - - VStack { - RKButton(title: "Share", style: .grey, action: {}) - RKButton(title: "Share", icon: Image(systemName: "chart.bar.fill"), style: .grey, action: {}) - RKButton(title: "Share", style: .grey, size: .fill, action: {}) - RKButton(title: "Share", icon: Image(systemName: "chart.bar.fill"), style: .grey, size: .fill, action: {}) - } - .padding(16) - .background(RKColors.primary.swiftUIColor) - } - .padding(32) - } - .background(RKColors.primary.swiftUIColor) - } -} diff --git a/Riddler/UI/RKButtonStyle.swift b/Riddler/UI/RKButtonStyle.swift deleted file mode 100644 index aa6757a..0000000 --- a/Riddler/UI/RKButtonStyle.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation -import SwiftUI - -struct RKButtonStyle: ButtonStyle { - - var style: RKButton.Style - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .font(RKFonts.Teko.regular.swiftUIFont(fixedSize: 28)) - .background(style.backgroundColor(isPressed: configuration.isPressed)) - .foregroundColor(style.foregroundColor()) - .cornerRadius(8) - } -} diff --git a/Riddler/UI/RKIconButton.swift b/Riddler/UI/RKIconButton.swift deleted file mode 100644 index 61d645b..0000000 --- a/Riddler/UI/RKIconButton.swift +++ /dev/null @@ -1,76 +0,0 @@ -import SwiftUI - -// MARK: - RKIconButton - -struct RKIconButton: View { - - // MARK: Lifecycle - - init(icon: Image, style: RKButton.Style = .accent, size: Size = .general, action: @escaping () -> Void) { - self.icon = icon - self.style = style - self.size = size - self.action = action - } - - // MARK: Internal - - enum Size { - case general - case large - - // MARK: Internal - - var iconSize: CGFloat { - switch self { - case .general: 24 - case .large: 32 - } - } - - var padding: CGFloat { - switch self { - case .general: 12 - case .large: 16 - } - } - } - - var body: some View { - Button(action: action) { - icon - .resizable() - .fontWeight(.bold) - .scaledToFit() - .frame(width: size.iconSize, height: size.iconSize) - .padding(size.padding) - } - .buttonStyle(RKButtonStyle(style: style)) - } - - // MARK: Private - - private let icon: Image - private let style: RKButton.Style - private let size: Size - private let action: () -> Void - -} - -// MARK: - RKIconButton_Previews - -struct RKIconButton_Previews: PreviewProvider { - static var previews: some View { - VStack { - RKIconButton(icon: Image(systemName: "chart.bar.fill"), style: .accent, size: .general, action: {}) - RKIconButton(icon: Image(systemName: "chart.bar.fill"), style: .accent, size: .large, action: {}) - - RKIconButton(icon: Image(systemName: "chart.bar.fill"), style: .primary, size: .general, action: {}) - RKIconButton(icon: Image(systemName: "chart.bar.fill"), style: .primary, size: .large, action: {}) - - RKIconButton(icon: Image(systemName: "chart.bar.fill"), style: .grey, size: .general, action: {}) - RKIconButton(icon: Image(systemName: "chart.bar.fill"), style: .grey, size: .large, action: {}) - } - .padding(32) - } -} diff --git a/Riddler/UI/RKToast.swift b/Riddler/UI/RKToast.swift deleted file mode 100644 index 9b84d7d..0000000 --- a/Riddler/UI/RKToast.swift +++ /dev/null @@ -1,73 +0,0 @@ -import SwiftUI - -// MARK: - RKToastModifier - -struct RKToastModifier: ViewModifier { - - // MARK: Lifecycle - - init(isShowing: Binding, icon: Image?, text: String, duration: TimeInterval, isError: Bool) { - _isShowing = isShowing - self.icon = icon - self.text = text - self.duration = duration - self.isError = isError - } - - // MARK: Internal - - @Binding var isShowing: Bool - let icon: Image? - let text: String - let duration: TimeInterval - let isError: Bool - - func body(content: Content) -> some View { - ZStack { - content - if isShowing { - toast - .zIndex(1) - .onAppear { - DispatchQueue.main.asyncAfter(deadline: .now() + duration) { - withAnimation { - isShowing.toggle() - } - } - } - } - } - } - - // MARK: Private - - private var toast: some View { - VStack { - Spacer() - HStack(alignment: .center, spacing: 20) { - if let icon { - icon - .font(.system(size: 20)) - .fontWeight(.bold) - } - Text(text) - .font(RKFonts.Teko.regular.swiftUIFont(fixedSize: 30)) - } - .foregroundColor(isError ? RKColors.primaryDark.swiftUIColor : RKColors.accent.swiftUIColor) - .padding() - .padding(.horizontal, 12) - .background(isError ? RKColors.error.swiftUIColor : RKColors.primaryDark.swiftUIColor) - .cornerRadius(8) - .shadow(radius: 16) - } - .padding(32) - .padding(.bottom, 64) - } - -} - -extension View { - func toast(isShowing: Binding, icon: Image? = nil, text: String, duration: TimeInterval = 2, isError: Bool = false) -> some View { - modifier(RKToastModifier(isShowing: isShowing, icon: icon, text: text, duration: duration, isError: isError)) - } -} diff --git a/Riddler/UI/Screens/CorrectView.swift b/Riddler/UI/Screens/CorrectView.swift new file mode 100644 index 0000000..345f9fa --- /dev/null +++ b/Riddler/UI/Screens/CorrectView.swift @@ -0,0 +1,109 @@ +import SwiftUI + +struct CorrectView: View { + + // ViewModel + @EnvironmentObject var game: GameViewModel + + @Binding var showCorrect: Bool + @Binding var showAchievementToast: Bool + + @State private var showShareToast: Bool = false + @State private var showSuggestDialog: Bool = false + + var body: some View { + ZStack { + Background(isDark: false) + + VStack { + Image("check_large") + .renderingMode(.template) + .resizable() + .scaledToFit() + .frame(width: 220, height: 220) + .foregroundColor(Color("PrimaryDark")) + .padding(.top, 30) + + Spacer() + Text("Correct") + .modifier(PrimaryTextStyle(textSize: 80)) + + Spacer() + let textSize: CGFloat = 30 + VStack { + HStack { + Text("Guesses:") + Spacer() + Text(String(game.player.riddleStats[game.player.riddle - 1].guesses)) + } + HStack { + Text("Time:") + Spacer() + Text(game.player.riddleStats[game.player.riddle - 1].timeString) + } + } + .modifier(PrimaryTextStyle(textSize: textSize)) + .padding(.horizontal, 20) + .padding(.vertical, 10) + + Spacer() + HStack(spacing: 12) { + TextIconButton(text: "Suggest", image: "feedback", iconSize: 30, action: pressSuggest) + .buttonStyle(PrimaryButtonStyle()) + .modifier(ButtonTextStyle(textSize: 30)) + + TextIconButton(text: "Share", image: "share", iconSize: 30, action: pressShare) + .buttonStyle(PrimaryButtonStyle()) + .modifier(ButtonTextStyle(textSize: 30)) + } + + } + .padding(20) + + if showSuggestDialog { + BasicDialog(titleText: "Missing answer?", bodyText: "Have an answer you think is also correct?", primaryText: "Suggest", secondaryText: "Close", primaryAction: pressDialogSuggest, secondaryAction: pressDialogClose) + } + } + .toast(image: "bubble.left.fill", text: "Copied to clipboard", isShowing: $showShareToast) + .toast(image: "trophy", customImage: true, text: "Achievement Unlocked!", isShowing: $showAchievementToast, duration: 5) + } + + func pressSuggest() { + withAnimation(.easeInOut(duration: 0.2)) { + showSuggestDialog = true + } + } + + func pressShare() { + // Log event to Firebase + copyResultsEvent(player: game.player) + + withAnimation(.easeInOut(duration: 0.2)) { + showShareToast = true + } + UIPasteboard.general.string = game.player.riddleResults() + } + + func pressDialogSuggest() { + withAnimation(.easeInOut(duration: 0.2)) { + showSuggestDialog = false + if let url = URL(string: "mailto:suggest@rddle.me?subject=I%20have%20a%20valid%20answer%20on%20riddle%20%23\(game.player.riddle)") { + UIApplication.shared.open(url) + } + } + } + + func pressDialogClose() { + withAnimation(.easeInOut(duration: 0.2)) { + showSuggestDialog = false + } + } + +} + +struct CorrectView_Previews: PreviewProvider { + static var previews: some View { + CorrectView(showCorrect: .constant(true), showAchievementToast: .constant(false)) + .environmentObject(GameViewModel()) + } +} diff --git a/Riddler/UI/Screens/HintView.swift b/Riddler/UI/Screens/HintView.swift new file mode 100644 index 0000000..7ad2507 --- /dev/null +++ b/Riddler/UI/Screens/HintView.swift @@ -0,0 +1,146 @@ +import SwiftUI + +struct HintView: View { + + // ViewModel + @EnvironmentObject var game: GameViewModel + + // UI State + @Binding var showHint: Bool + + @State private var showAdDialog: Bool = false + @State private var showAdFailedDialog: Bool = false + @State private var showAdError: Bool = false + @State private var showRewardedVideo: Bool = false + @State private var showHintToast: Bool = false + + var body: some View { + ZStack { + Background(isDark: true) + BackButton(pressBack: pressBack) + .buttonStyle(AccentButtonStyle()) + + // Main UI + VStack { + Text("\(game.player.riddleStats[game.player.riddle].hintsUsed)/\(game.riddles[game.player.riddle].hints.count)") + .modifier(AccentTextStyle(textSize: 50)) + + Spacer() + Text(game.hintString()) + .modifier(AccentTextStyle(textSize: 30)) + .modifier(MultilineTextStyle()) + + Spacer() + HStack { + Button(action: pressHint) { + Text(buttonString) + //.padding(EdgeInsets(top: 0, leading: 30, bottom: 0, trailing: 30)) + .frame(width: 250, height: 50) + } + .modifier(ButtonTextStyle(textSize: 30)) + .buttonStyle(AccentButtonStyle(disabled: buttonDisabled)) + .disabled(buttonDisabled) + + IconButton(image: "add", size: 30, action: pressAdd) + .buttonStyle(AccentButtonStyle()) + + } + + } + .padding(20) + + // Ad Dialog + if showAdDialog { + BasicDialog(titleText: "Need some help?", bodyText: "Watch an ad to unlock 3 more hints", primaryText: "Watch", secondaryText: "Close", primaryAction: pressWatch, secondaryAction: pressAdClose) + } + + // Ad failed to load dialog + if showAdFailedDialog { + AdFailedDialog(privacyAction: pressUpdatePrivacy, closeAction: pressAdFailedClose) + } + + + } + .toast(image: "exclamationmark.triangle.fill", text: "Failed to show ad", isShowing: $showAdError, isError: true) + .toast(image: "", text: "Riddle has no more hints!", isShowing: $showHintToast) + + .navigationBarBackButtonHidden(true) + .navigationBarHidden(true) + + .presentRewardedAd(isPresented: $showRewardedVideo, isErrorShowing: $showAdError, adUnitId: rewardedId, rewardFunc: hintReward, game: game) + + } + + var buttonString: String { + return game.player.hints == 0 ? "Out of hints" : "Use hint: \(game.player.hints) remaining" + } + + var buttonHidden: Bool { + return game.player.riddleStats[game.player.riddle].hintsUsed == 2 ? true : false + } + + var buttonDisabled: Bool { + return game.player.hints == 0 ? true : false + } + + func hintReward() { + game.hintReward() + } + + // MARK: - Button actions + func pressBack() { + showHint = false + } + + func pressAdClose() { + withAnimation(.easeOut(duration: 0.15)) { + showAdDialog = false + } + } + + func pressAdFailedClose() { + withAnimation(.easeIn(duration: 0.15)) { + showAdFailedDialog = false + } + } + + func pressHint() { + if game.hintUsable() { + game.useHint() + } else { + showHintToast = true + } + } + + func pressAdd() { + withAnimation(.easeIn(duration: 0.15)) { + showAdDialog = true + } + } + + func pressWatch() { + showAdDialog = false + + // Check to see if ad is loaded + if RewardedAd.shared.rewardedAd != nil { + showRewardedVideo = true + } else { + withAnimation(.easeIn(duration: 0.15)) { + showAdFailedDialog = true + } + } + } + + func pressUpdatePrivacy() { + showAdFailedDialog = false + + ConsentManager.shared.displayConsentForm(forceDisplay: true) + } +} + +struct HintView_Previews: PreviewProvider { + static var previews: some View { + HintView(showHint: .constant(true)) + .environmentObject(GameViewModel()) + } +} diff --git a/Riddler/UI/Screens/MenuView.swift b/Riddler/UI/Screens/MenuView.swift new file mode 100644 index 0000000..1934807 --- /dev/null +++ b/Riddler/UI/Screens/MenuView.swift @@ -0,0 +1,96 @@ +import SwiftUI + +struct MenuView: View { + + // ViewModel + @StateObject var game = GameViewModel() + + // UI state + @State private var mainScreen: String? = nil + + // GameKit bindings + @Binding var showGameCenter: Bool + @Binding var showLeaderboard: Bool + @Binding var showAchievements: Bool + + var body: some View { + NavigationView { + ZStack { + // Navigtaion + NavigationLink(destination: RiddleView(mainScreen: $mainScreen), tag: "Riddle", selection: $mainScreen) { EmptyView() } + NavigationLink(destination: SettingsView(mainScreen: $mainScreen), tag: "Settings", selection: $mainScreen) { EmptyView() } + NavigationLink(destination: EmptyView(), label: {}) + + // Background + Background(isDark: true) + + // Menu + VStack { + Spacer() + Text("Riddler") + .modifier(AccentTextStyle(textSize: 100)) + + Spacer() + VStack(spacing: 10) { + + Button(action: pressStart) { + Text("Start").frame(width: 227, height: 70) + } + .buttonStyle(AccentButtonStyle()) + .modifier(ButtonTextStyle(textSize: 60)) + + HStack { + IconButton(image: "trophy", size: 50, action: pressAchievements) + .buttonStyle(AccentButtonStyle()) + + IconButton(image: "leaderboard", size: 50, action: pressLeaderboards) + .buttonStyle(AccentButtonStyle()) + + IconButton(image: "settings", size: 50, action: pressSettings) + .buttonStyle(AccentButtonStyle()) + } + } + + Spacer() + HStack { + Spacer() + Text("v1.02") + .modifier(AccentTextStyle(textSize: 20)) + } + } + .padding(20) + } + .navigationBarHidden(true) + } + .navigationViewStyle(StackNavigationViewStyle()) + .environmentObject(game) + } + + // Button actions + func pressStart() { + mainScreen = "Riddle" + } + + func pressSettings() { + mainScreen = "Settings" + } + + func pressAchievements() { + // Log event to firebase + openAchievementsEvent(player: game.player) + showAchievements = true + } + + func pressLeaderboards() { + // Log event to firebase + openLeaderboardEvent(player: game.player) + showLeaderboard = true + } + +} + +struct MenuView_Previews: PreviewProvider { + static var previews: some View { + MenuView(showGameCenter: .constant(false), showLeaderboard: .constant(false), showAchievements: .constant(false)) + } +} diff --git a/Riddler/UI/Screens/RiddleView.swift b/Riddler/UI/Screens/RiddleView.swift new file mode 100644 index 0000000..c76d3db --- /dev/null +++ b/Riddler/UI/Screens/RiddleView.swift @@ -0,0 +1,156 @@ +import SwiftUI +import GoogleMobileAds +import StoreKit +import AdSupport +import Firebase + +struct RiddleView: View { + + // ViewModel + @EnvironmentObject var game: GameViewModel + + // Parent UI State + @Binding var mainScreen: String? + + // UI State + @State private var showCorrect = false + @State private var showHints = false + @State private var showVictory: Bool = false + @State private var showRating = false + @State private var showInterstitial = false + @State private var showAchievementToast = false + + @State private var answerString = "" + @State private var attempts = 0 + + var body: some View { + + ZStack { + GeometryReader { metrics in + ZStack { + NavigationLink(destination: HintView(showHint: $showHints), isActive: $showHints) { EmptyView() } + + Background(isDark: true) + BackButton(pressBack: pressBack) + .buttonStyle(AccentButtonStyle()) + + VStack { + // Riddle number + Text(game.riddleNumString) + .modifier(AccentTextStyle(textSize: 50)) + Spacer() + + // Riddle + Text(game.riddles[game.player.riddle].question) + .modifier(AccentTextStyle(textSize: 30)) + .modifier(MultilineTextStyle()) + Spacer() + + VStack(spacing: 10) { + // Hint button + HStack { + Spacer() + Button(action: pressHint) { + Text("Hints: \(game.player.hints)") + .padding(EdgeInsets(top: 0, leading: 30, bottom: 0, trailing: 30)) + } + .buttonStyle(AccentButtonStyle(disabled: false)) + .modifier(ButtonTextStyle(textSize: 30)) + } + + // Answer box + HStack { + TextField("", text: $answerString) + .placeholder("Answer", when: answerString.isEmpty) + .modifier(AccentTextFieldStyle(textSize: 30)) + .modifier(Shake(animatableData: CGFloat(attempts))) + + IconButton(image: "forward", size: 32, action: pressEnter) + .buttonStyle(AccentButtonStyle()) + } + } + } + .padding(20) + + .sheet(isPresented: $showCorrect, content: { + CorrectView(showCorrect: $showCorrect, showAchievementToast: $showAchievementToast) + .onDisappear(perform: correctDismiss) + .environmentObject(game) + }) + + } + .presentInterstitialAd(isPresented: $showInterstitial, adUnitId: interstitialId) + + .navigationBarBackButtonHidden(true) + .navigationBarHidden(true) + + .onTapGesture { hideKeyboard() } + } + + if game.player.completed { + VictoryView(mainScreen: $mainScreen) + } + } + } + + // MARK: - Button actions + func pressBack() { + mainScreen = nil + } + + func pressHint() { + // Log event to firebase + openHintsEvent(player: game.player) + showHints = true + } + + func pressEnter() { + let results = game.checkAnswer(inputAnswer: answerString) + if results.0 == true { + if results.1 == true { + showAchievementToast = true + } + + hideKeyboard() + showCorrect = true + answerString = "" + } else { + withAnimation(.default) { + self.attempts += 1 + } + } + } + + func correctDismiss() { + + game.updateRiddleStart() + + if game.player.completed { + withAnimation(.easeIn(duration: 0.25)) { + showVictory = true + } + } else { + // Either showing rating dialog or ad + if game.player.askRating() && !TestVariables.DISABLE_RATING_POPUP { + if let windowScene = UIApplication.shared.windows.first?.windowScene { SKStoreReviewController.requestReview(in: windowScene) + game.setRatingAsk() + } + } else if !TestVariables.DISABLE_ADS { +// if game.player.shouldShowAd() { +// game.showAd() + print("Should show ad") + showInterstitial = true +// } + } + } + } + +} + +// Previews +struct RiddleView_Previews: PreviewProvider { + static var previews: some View { + RiddleView(mainScreen: .constant("Riddle")) + .environmentObject(GameViewModel()) + } +} diff --git a/Riddler/UI/Screens/SettingsView.swift b/Riddler/UI/Screens/SettingsView.swift new file mode 100644 index 0000000..dd9a4be --- /dev/null +++ b/Riddler/UI/Screens/SettingsView.swift @@ -0,0 +1,88 @@ +import SwiftUI + +struct SettingsView: View { + + @Binding var mainScreen: String? + + var body: some View { + + ZStack { + Background(isDark: true) + BackButton(pressBack: pressBack) + .buttonStyle(AccentButtonStyle()) + + VStack { + Spacer() + Text("Settings") + .modifier(AccentTextStyle(textSize: 60)) + .padding(40) + + Spacer() + VStack(spacing: 20) { + SettingsItem(labelText: "Privacy Settings", buttonText: "Update", + pressAction: pressPrivacySettings) + SettingsItem(labelText: "Privacy Policy", buttonText: "Read", + pressAction: pressPrivacyPolicy) + SettingsItem(labelText: "Found a bug?", buttonText: "Report", + pressAction: pressBugReport) + } + .padding(.horizontal, 20) + Spacer() + } + .padding(20) + } + .navigationBarBackButtonHidden(true) + .navigationBarHidden(true) + } + + struct SettingsItem: View { + let labelText: String + let buttonText: String + let pressAction: () -> Void + + var body: some View { + HStack { + Text(labelText) + .modifier(AccentTextStyle(textSize: 25)) + Spacer() + Button(action: pressAction) { + Text(buttonText) + .frame(width: 100, height: 40) + } + .buttonStyle(AccentButtonStyle()) + .modifier(ButtonTextStyle(textSize: 25)) + } + } + } + + + // Button functions + + func pressBack() { + mainScreen = nil + } + + func pressPrivacySettings() { + // Display consent form + ConsentManager.shared.displayConsentForm(forceDisplay: true) + } + + func pressPrivacyPolicy() { + if let url = URL(string: "https://rddle.me/privacy") { + UIApplication.shared.open(url) + } + } + + func pressBugReport() { + if let url = URL(string: "mailto:bugs@rddle.me?subject=I%20have%20found%20a%20bug!") { + UIApplication.shared.open(url) + } + } + +} + +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + SettingsView(mainScreen: .constant("Settings")) + } +} diff --git a/Riddler/UI/Screens/StatsView.swift b/Riddler/UI/Screens/StatsView.swift new file mode 100644 index 0000000..ffe1d91 --- /dev/null +++ b/Riddler/UI/Screens/StatsView.swift @@ -0,0 +1,67 @@ +import SwiftUI + +struct StatsView: View { + + let statNames: [String] = [ + "Total time:", + "Quickest time:", + "Slowest time:", + "Total guesses:", + "Correct first guesses:", + "Most incorrect guesses:", + "Hints used:", + "Correct without hints:" + ] + + var statValues: [String] + + var body: some View { + ZStack { + Background(isDark: false) + + VStack { + Spacer() + Text("Stats") + .modifier(PrimaryTextStyle(textSize: 60)) + .padding(40) + + Spacer() + VStack(spacing: 12) { + ForEach((0.. ProjectionTransform { + ProjectionTransform(CGAffineTransform(translationX: amount * sin(animatableData * .pi * shakesPerUnit), y: 0)) + } +} diff --git a/Riddler/UI/Styles/ButtonStyles.swift b/Riddler/UI/Styles/ButtonStyles.swift new file mode 100644 index 0000000..ea5bf13 --- /dev/null +++ b/Riddler/UI/Styles/ButtonStyles.swift @@ -0,0 +1,35 @@ +import Foundation +import SwiftUI + +struct AccentButtonStyle: ButtonStyle { + var disabled: Bool = false + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .background(disabled ? Color("Disabled") : (configuration.isPressed ? Color("AccentDark") : Color("Accent"))) + .foregroundColor(Color("PrimaryDark")) + .cornerRadius(5) + } +} + +struct PrimaryButtonStyle: ButtonStyle { + var disabled: Bool = false + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .background(disabled ? Color("Disabled") : (configuration.isPressed ? Color("PrimaryDark") : Color("Primary"))) + .foregroundColor(Color("Accent")) + .cornerRadius(5) + } +} + +struct SecondaryButtonStyle: ButtonStyle { + var disabled: Bool = false + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .background(disabled ? Color("Disabled") : (configuration.isPressed ? Color("SecondaryDark") : Color("Secondary"))) + .foregroundColor(Color("PrimaryDark")) + .cornerRadius(5) + } +} diff --git a/Riddler/UI/Styles/TextStyles.swift b/Riddler/UI/Styles/TextStyles.swift new file mode 100644 index 0000000..5dc7d4e --- /dev/null +++ b/Riddler/UI/Styles/TextStyles.swift @@ -0,0 +1,66 @@ +import Foundation +import SwiftUI + +struct ButtonTextStyle: ViewModifier { + var textSize: CGFloat + func body(content: Content) -> some View { + content + .font(Font.custom("Teko-Regular", size: textSize)) + } +} + +struct ContentTextStyle: ViewModifier { + var textSize: CGFloat + func body(content: Content) -> some View { + content + .font(Font.custom("Abel-Regular", size: textSize)) + } +} + +struct PrimaryTextStyle: ViewModifier { + var textSize: CGFloat + func body(content: Content) -> some View { + content + .modifier(ContentTextStyle(textSize: textSize)) + .foregroundColor(Color("PrimaryDark")) + } +} + +struct SecondaryTextStyle: ViewModifier { + var textSize: CGFloat + func body(content: Content) -> some View { + content + .modifier(ContentTextStyle(textSize: textSize)) + .foregroundColor(Color("Secondary")) + } +} + +struct AccentTextStyle: ViewModifier { + var textSize: CGFloat + func body(content: Content) -> some View { + content + .modifier(ContentTextStyle(textSize: textSize)) + .foregroundColor(Color("Accent")) + } +} + +struct MultilineTextStyle: ViewModifier { + func body(content: Content) -> some View { + content + .multilineTextAlignment(.center) + .minimumScaleFactor(0.2) + } +} + +struct AccentTextFieldStyle: ViewModifier { + var textSize: CGFloat + func body(content: Content) -> some View { + content + .modifier(ContentTextStyle(textSize: textSize)) + .padding(6) + .foregroundColor(Color("AccentDark")) + .accentColor(Color("AccentDark")) + .background(Color("PrimaryDark")) + .cornerRadius(5) + } +} diff --git a/Riddler/Utilities/Analytics.swift b/Riddler/Utilities/Analytics.swift new file mode 100644 index 0000000..3305941 --- /dev/null +++ b/Riddler/Utilities/Analytics.swift @@ -0,0 +1,201 @@ +// +// Analytics.swift +// Riddler +// +// Created by Oliver Stenning on 13/07/2022. +// + +import Foundation +import FirebaseAnalytics + +//MARK: - Game Events +func guessEvent(guess: String, correct: Bool, player: Player) { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("guess", parameters: [ + "guess" : guess, + "correct" : correct, + "riddle_num" : player.riddle + 1, + "incorrect_guesses" : player.riddleStats[player.riddle].incorrectGuesses, + "hints_used" : player.riddleStats[player.riddle].hintsUsed, + "time_taken_text" : player.riddleStats[player.riddle].timeString, + "time_taken" : player.riddleStats[player.riddle].time + ]) + } +} + +func openHintsEvent(player: Player) { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("open_hints", parameters: [ + "hints_used" : player.riddleStats[player.riddle].hintsUsed, + "total_hints_used" : player.hintsUsed, + "hints_remaining" : player.hints, + "riddle_num" : player.riddle, + "incorrect_guesses" : player.riddleStats[player.riddle].incorrectGuesses, + "time_taken_text" : player.riddleStats[player.riddle].timeString, + "time_taken" : player.riddleStats[player.riddle].time + ]) + } +} + +func useHintEvent(player: Player) { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("use_hint", parameters: [ + "hints_used" : player.riddleStats[player.riddle].hintsUsed, + "total_hints_used" : player.hintsUsed, + "hints_remaining" : player.hints, + "riddle_num" : player.riddle, + "incorrect_guesses" : player.riddleStats[player.riddle].incorrectGuesses, + "time_taken_text" : player.riddleStats[player.riddle].timeString, + "time_taken" : player.riddleStats[player.riddle].time + ]) + } +} + +func unlockHintsEvent(player: Player) { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("unlock_hint", parameters: [ + "hints_used" : player.riddleStats[player.riddle].hintsUsed, + "total_hints_used" : player.hintsUsed, + "hints_remaining" : player.hints, + "riddle_num" : player.riddle, + "incorrect_guesses" : player.riddleStats[player.riddle].incorrectGuesses, + "time_taken_text" : player.riddleStats[player.riddle].timeString, + "time_taken" : player.riddleStats[player.riddle].time + ]) + } +} + +func copyResultsEvent(player: Player) { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("copy_results", parameters: [ + "riddle_num" : player.riddle, + "guesses" : player.riddleStats[player.riddle - 1].guesses, + "hints_used" : player.riddleStats[player.riddle - 1].hintsUsed, + "time_taken_text" : player.riddleStats[player.riddle - 1].timeString, + "time_taken" : player.riddleStats[player.riddle - 1].time + ]) + } +} + +func victoryEvent(player: Player) { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("victory", parameters: [ + "total_hints_used" : player.hintsUsed, + "total_guesses" : player.guesses, + "time_played_text" : player.timeString, + "time_played" : player.time + ]) + } +} + +func openLeaderboardEvent(player: Player) { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("open_leaderboard", parameters: [ + "riddle_num" : player.riddle, + "time_played_text" : player.timeString, + "time_played" : player.time + ]) + } +} + +func openAchievementsEvent(player: Player) { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("open_achievements", parameters: [ + "riddle_num" : player.riddle, + "time_played_text" : player.timeString, + "time_played" : player.time + ]) + } +} + +//MARK: - Interstitial Ad +func interstitialLoadedEvent() { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("interstitial_loaded", parameters: [:]) + } +} + +func interstitialShownEvent() { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("interstitial_shown", parameters: [:]) + } +} + +func interstitialNotReadyEvent() { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("interstitial_not_ready", parameters: [:]) + } +} + +func interstitialFailedToLoad(error: Error) { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("interstitial_failed_to_load", parameters: [ + "error_code" : error._code, + "description" : error.localizedDescription + ]) + } +} + +func interstitialFailedToShow(error: Error) { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("interstitial_failed_to_show", parameters: [ + "error_code" : error._code, + "description" : error.localizedDescription + ]) + } +} + +//MARK: - Rewarded Ad +func rewardedLoadedEvent() { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("rewarded_loaded", parameters: [:]) + } +} + +func rewardedShownEvent() { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("rewarded_shown", parameters: [:]) + } +} + +func rewardedNotReadyEvent() { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("rewarded_not_ready", parameters: [:]) + } +} + +func rewardedFailedToLoad(error: Error) { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("rewarded_failed_to_load", parameters: [ + "error_code" : error._code, + "description" : error.localizedDescription + ]) + } +} + +func rewardedFailedToShow(error: Error) { + // Only log events if tracking consented + if ConsentManager.shared.userConsentType == ConsentManager.ConsentType.full { + Analytics.logEvent("rewarded_failed_to_show", parameters: [ + "error_code" : error._code, + "description" : error.localizedDescription + ]) + } +} diff --git a/Riddler/Utilities/Array+Extensions.swift b/Riddler/Utilities/Array+Extensions.swift deleted file mode 100644 index ec842c2..0000000 --- a/Riddler/Utilities/Array+Extensions.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -extension Array { - subscript(index: Int, default defaultValue: @autoclosure () -> Element) -> Element { - guard index >= 0, index < endIndex else { return defaultValue() } - return self[index] - } - - subscript(safeIndex index: Int) -> Element? { - guard index >= 0, index < endIndex else { return nil } - return self[index] - } -} diff --git a/Riddler/Utilities/BundleDecodable.swift b/Riddler/Utilities/BundleDecodable.swift index 9e53009..4cffd4a 100644 --- a/Riddler/Utilities/BundleDecodable.swift +++ b/Riddler/Utilities/BundleDecodable.swift @@ -1,20 +1,20 @@ import Foundation extension Bundle { - func decode(_ file: String) -> [Type] { - guard let url = url(/service/forresource: file, withExtension: nil) else { - fatalError("Failed to locate \(file) in bundle.") - } - - guard let data = try? Data(contentsOf: url) else { - fatalError("Failed to load \(file) from bundle.") - } - - let decoder = JSONDecoder() - - guard let loaded = try? decoder.decode([Type].self, from: data) else { - fatalError("Failed to decode \(file) from bundle.") - } - return loaded - } + func decode(_ file: String) -> [Type] { + guard let url = self.url(/service/forresource: file, withExtension: nil) else { + fatalError("Failed to locate \(file) in bundle.") + } + + guard let data = try?Data(contentsOf: url) else { + fatalError("Failed to load \(file) from bundle.") + } + + let decoder = JSONDecoder() + + guard let loaded = try? decoder.decode([Type].self, from:data) else { + fatalError("Failed to decode \(file) from bundle.") + } + return loaded + } } diff --git a/Riddler/Utilities/Date+Extensions.swift b/Riddler/Utilities/Date+Extensions.swift deleted file mode 100644 index 6a75a9f..0000000 --- a/Riddler/Utilities/Date+Extensions.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -public extension Date { - - func isAfter(_ date: Date) -> Bool { - self > date - } - - func isBefore(_ date: Date) -> Bool { - self < date - } - -} diff --git a/Riddler/Utilities/Logger+Extensions.swift b/Riddler/Utilities/Logger+Extensions.swift deleted file mode 100644 index 9aa102d..0000000 --- a/Riddler/Utilities/Logger+Extensions.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation -import OSLog - -extension Logger { - - private static var subsystem = Bundle.main.bundleIdentifier! - - static let gameplay = Logger(subsystem: subsystem, category: "gameplay") - static let dataStorage = Logger(subsystem: subsystem, category: "dataStorage") - static let gameCenter = Logger(subsystem: subsystem, category: "gameCenter") - static let consent = Logger(subsystem: subsystem, category: "consent") - static let ads = Logger(subsystem: subsystem, category: "ads") - static let analytics = Logger(subsystem: subsystem, category: "analytics") - -} diff --git a/Riddler/Utilities/TestVariables.swift b/Riddler/Utilities/TestVariables.swift new file mode 100644 index 0000000..3e982c5 --- /dev/null +++ b/Riddler/Utilities/TestVariables.swift @@ -0,0 +1,21 @@ +import Foundation + +struct TestVariables { + static let RESET_DATA = false + static let DISABLE_ADS = false + static let RESET_CONSENT = false + static let DISABLE_CONSENT_FORM = false + static let DISABLE_FIREBASE = false + static let DISABLE_RATING_POPUP = false + static let DISABLE_GAMECENTER = false + static let RESET_ACHIEVEMENTS = false + + static let START_RIDDLE = 0 + static let COMPLETED = false +} + +struct ConfigVariables { + static let HINT_START_AMOUNT = 3 + static let HINT_UNLOCK_AMOUNT = 3 + static let TIME_BETWEEN_ADS = 180 +} diff --git a/Riddler/Utilities/TimeFormatter.swift b/Riddler/Utilities/TimeFormatter.swift index 3544f5f..2eafae8 100644 --- a/Riddler/Utilities/TimeFormatter.swift +++ b/Riddler/Utilities/TimeFormatter.swift @@ -1,30 +1,63 @@ import Foundation -enum TimeFormatter { - - // MARK: Internal - - static func timeDifference(from startTime: Date, to endTime: Date) -> String { - let timeDifference = Calendar.current.dateComponents([.year, .day, .hour, .minute, .second], from: startTime, to: endTime) - - let components: [String?] = [ - format(unitValue: timeDifference.year ?? 0, unitName: "year"), - format(unitValue: timeDifference.day ?? 0, unitName: "day"), - format(unitValue: timeDifference.hour ?? 0, unitName: "hr"), - format(unitValue: timeDifference.minute ?? 0, unitName: "min"), - format(unitValue: timeDifference.second ?? 0, unitName: "sec") - ] - - let timeArray = components.compactMap { $0 }.prefix(2) - let timeString = timeArray.joined(separator: ", ") - return timeString.isEmpty ? "0 sec" : timeString - } - - // MARK: Private - - private static func format(unitValue: Int, unitName: String) -> String? { - guard unitValue != 0 else { return nil } - return "\(unitValue) \(unitName)\(unitValue == 1 ? "" : "s")" - } +func timeDifference(startTime: Date, endTime: Date) -> Int { + let timeDifference = Calendar.current.dateComponents([.second], from: startTime, to: endTime) + return timeDifference.second ?? 0 +} +func timeDifferenceString(startTime: Date, endTime: Date) -> String { + var timeString = "" + var timeArray: [String] = [] + + let timeDifference = Calendar.current.dateComponents([.year, .day, .hour, .minute, .second], from: startTime, to: endTime) + + let years = timeDifference.year ?? 0 + let days = timeDifference.day ?? 0 + let hours = timeDifference.hour ?? 0 + let minutes = timeDifference.minute ?? 0 + let seconds = timeDifference.second ?? 0 + + if years > 0 { + if years == 1 { + timeArray.append("\(years) year") + } else { + timeArray.append("\(years) years") + } + } + + if days > 0 { + if days == 1 { + timeArray.append("\(days) day") + } else { + timeArray.append("\(days) days") + } + } + + if hours > 0 { + if hours == 1 { + timeArray.append("\(hours) hr") + } else { + timeArray.append("\(hours) hrs") + } + } + + if minutes > 0 { + timeArray.append("\(minutes) min") + } + + if seconds > 0 { + timeArray.append("\(seconds) sec") + } + + while timeArray.count > 2 { + timeArray.removeLast() + } + + timeString = timeArray.joined(separator: ", ") + + if timeString == "" { + timeString = "1 sec" + } + + return timeString } diff --git a/Riddler/Utilities/TimeInterval+Extensions.swift b/Riddler/Utilities/TimeInterval+Extensions.swift deleted file mode 100644 index 4bb9d58..0000000 --- a/Riddler/Utilities/TimeInterval+Extensions.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation - -public extension TimeInterval { - - static func minutes(_ numberOfMinutes: Double) -> TimeInterval { - numberOfMinutes * 60 - } - - static func hours(_ numberOfHours: Double) -> TimeInterval { - numberOfHours * minutes(60) - } - - static func days(_ numberOfDays: Double) -> TimeInterval { - numberOfDays * hours(24) - } - - static func weeks(_ numberOfWeeks: Double) -> TimeInterval { - numberOfWeeks * days(7) - } - -} diff --git a/Riddler/Utilities/View+Extensions.swift b/Riddler/Utilities/View+Extensions.swift deleted file mode 100644 index c511e3f..0000000 --- a/Riddler/Utilities/View+Extensions.swift +++ /dev/null @@ -1,9 +0,0 @@ -import SwiftUI - -extension View { - func analyticsScreen(_ screen: AnalyticsScreen) -> some View { - onAppear { - Analytics.shared.screen(screen) - } - } -} diff --git a/RiddlerTests/RiddlerTests.swift b/RiddlerTests/RiddlerTests.swift new file mode 100644 index 0000000..7be3675 --- /dev/null +++ b/RiddlerTests/RiddlerTests.swift @@ -0,0 +1,36 @@ +// +// RiddlerTests.swift +// RiddlerTests +// +// Created by Oliver Stenning on 09/02/2022. +// + +import XCTest +@testable import Riddler + +class RiddlerTests: 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/RiddlerUITests/RiddlerUITests.swift b/RiddlerUITests/RiddlerUITests.swift new file mode 100644 index 0000000..1e12c74 --- /dev/null +++ b/RiddlerUITests/RiddlerUITests.swift @@ -0,0 +1,42 @@ +// +// RiddlerUITests.swift +// RiddlerUITests +// +// Created by Oliver Stenning on 09/02/2022. +// + +import XCTest + +class RiddlerUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + 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 { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/RiddlerUITests/RiddlerUITestsLaunchTests.swift b/RiddlerUITests/RiddlerUITestsLaunchTests.swift new file mode 100644 index 0000000..c96ec55 --- /dev/null +++ b/RiddlerUITests/RiddlerUITestsLaunchTests.swift @@ -0,0 +1,25 @@ +import XCTest + +class RiddlerUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} diff --git a/Screenshots/App Icon.png b/Screenshots/App Icon.png new file mode 100644 index 0000000..ed4aa8b Binary files /dev/null and b/Screenshots/App Icon.png differ diff --git a/Screenshots/AppIcon.png b/Screenshots/AppIcon.png deleted file mode 100644 index dcc0bc4..0000000 Binary files a/Screenshots/AppIcon.png and /dev/null differ diff --git a/Screenshots/Correct.png b/Screenshots/Correct.png index 1df2d55..d35bd23 100644 Binary files a/Screenshots/Correct.png and b/Screenshots/Correct.png differ diff --git a/Screenshots/Menu.png b/Screenshots/Menu.png index 51a6bbc..60c1ef5 100644 Binary files a/Screenshots/Menu.png and b/Screenshots/Menu.png differ diff --git a/Screenshots/Question.png b/Screenshots/Question.png new file mode 100644 index 0000000..c149b2a Binary files /dev/null and b/Screenshots/Question.png differ diff --git a/Screenshots/Riddle.png b/Screenshots/Riddle.png deleted file mode 100644 index 547d27b..0000000 Binary files a/Screenshots/Riddle.png and /dev/null differ diff --git a/swiftgen.yml b/swiftgen.yml deleted file mode 100644 index 9ca9a2c..0000000 --- a/swiftgen.yml +++ /dev/null @@ -1,17 +0,0 @@ -fonts: - - inputs: Riddler/Resources/Fonts - outputs: - - templateName: swift5 - params: - enumName: RKFonts - publicAccess: - output: Riddler/Generated/Fonts+Generated.swift - -xcassets: - - inputs: Riddler/Resources/Colors.xcassets - outputs: - - templateName: swift5 - params: - enumName: RKColors - publicAccess: - output: Riddler/Generated/Colors+Generated.swift