From b2e0c5b34c014c0d28b5f7f371dee65a4eb33973 Mon Sep 17 00:00:00 2001 From: COVIDSafe Support <64945427+covidsafe-support@users.noreply.github.com> Date: Tue, 26 May 2020 17:13:26 +1000 Subject: [PATCH] COVIDSafe code from version 1.4 (#3) --- CovidSafe.xcodeproj/project.pbxproj | 190 +- .../xcschemes/covid-staging.xcscheme | 6 + CovidSafe/API/CovidNetworking.swift | 2 +- CovidSafe/AppDelegate.swift | 44 +- CovidSafe/Base.lproj/Main.storyboard | 382 +- CovidSafe/BluetraceConfig.swift | 6 +- CovidSafe/CentralController.swift | 50 +- CovidSafe/ContactViewController.swift | 17 +- CovidSafe/Crypto.swift | 196 + CovidSafe/Debug.storyboard | 8 +- CovidSafe/Encounter+CoreDataProperties.swift | 67 +- CovidSafe/Encounter+EncounterRecord.swift | 5 +- CovidSafe/Encounter+Util.swift | 5 +- CovidSafe/EncounterDB+migration.swift | 110 + CovidSafe/EncounterDB.swift | 75 + CovidSafe/EncounterMessageManager.swift | 33 +- CovidSafe/EncounterRecord.swift | 8 + .../xcmapping.xml | 91 + CovidSafe/EncounterV2Mapping.swift | 102 + CovidSafe/Feedback/README.md | 94 + CovidSafe/HomeViewController.swift | 64 +- CovidSafe/InfoViewController.swift | 32 +- CovidSafe/InitialScreenViewController.swift | 71 +- CovidSafe/LogViewController.swift | 24 +- CovidSafe/MigrationViewController.swift | 22 + CovidSafe/OTPViewController.swift | 2 - CovidSafe/PeripheralController.swift | 32 +- CovidSafe/PushNotificationConstants.swift | 4 +- CovidSafe/SecKey+CovidSafe.swift | 67 + CovidSafe/UploadHelper.swift | 5 +- CovidSafe/spinner_migrating_db.json | 5514 +++++++++++++++++ .../ModelV2.xcdatamodel/contents | 16 + Podfile.lock | 2 +- 33 files changed, 6865 insertions(+), 481 deletions(-) create mode 100644 CovidSafe/Crypto.swift create mode 100644 CovidSafe/EncounterDB+migration.swift create mode 100644 CovidSafe/EncounterDB.swift create mode 100644 CovidSafe/EncounterV1toV2.xcmappingmodel/xcmapping.xml create mode 100644 CovidSafe/EncounterV2Mapping.swift create mode 100644 CovidSafe/Feedback/README.md create mode 100644 CovidSafe/MigrationViewController.swift create mode 100644 CovidSafe/SecKey+CovidSafe.swift create mode 100644 CovidSafe/spinner_migrating_db.json create mode 100644 CovidSafe/tracer.xcdatamodeld/ModelV2.xcdatamodel/contents diff --git a/CovidSafe.xcodeproj/project.pbxproj b/CovidSafe.xcodeproj/project.pbxproj index 89e46d9..6467548 100644 --- a/CovidSafe.xcodeproj/project.pbxproj +++ b/CovidSafe.xcodeproj/project.pbxproj @@ -36,8 +36,9 @@ 30BE1CB523F15D47005DCE4F /* OTPViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BE1CB423F15D47005DCE4F /* OTPViewController.swift */; }; 30E91BE923EFE514002D592A /* UploadDataVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E91BE823EFE514002D592A /* UploadDataVC.swift */; }; 30E91BEB23EFEA0B002D592A /* CodeInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E91BEA23EFEA0B002D592A /* CodeInputView.swift */; }; - 30FADDAE23F6896C006C125F /* Encounter+Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FADDAD23F6896C006C125F /* Encounter+Event.swift */; }; - 469DD4CFF262D29768A8CBEF /* Pods_CovidSafe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BE2A0527997C5042B9E2A41 /* Pods_CovidSafe.framework */; }; + 3A8951FDED310D7ABA54259E /* Pods_CovidSafe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34E9EB96EA6E42A571E73C8E /* Pods_CovidSafe.framework */; }; + 5904A5C22462471A008C8012 /* EncounterV1toV2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 5904A5C12462471A008C8012 /* EncounterV1toV2.xcmappingmodel */; }; + 5904A5C32462471A008C8012 /* EncounterV1toV2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 5904A5C12462471A008C8012 /* EncounterV1toV2.xcmappingmodel */; }; 590888AF2431B9E3008C9B9F /* UITextView + Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B55E1912430760500C9E798 /* UITextView + Extensions.swift */; }; 590888B02431B9E7008C9B9F /* UIColor + Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC141AD2430685800399FA8 /* UIColor + Extensions.swift */; }; 590888B12431B9EB008C9B9F /* Question3ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B86119424303F5E00EA4B6B /* Question3ViewController.swift */; }; @@ -52,13 +53,27 @@ 590C99332432C1C400A5EC71 /* UploadDataHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B69E7E82430C22E00561DD9 /* UploadDataHomeViewController.swift */; }; 592CBB802441A583001FFCE9 /* PersonalDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 592CBB7F2441A583001FFCE9 /* PersonalDetailsViewController.swift */; }; 592CBB812441A583001FFCE9 /* PersonalDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 592CBB7F2441A583001FFCE9 /* PersonalDetailsViewController.swift */; }; + 59490B64245FE3DA00C9802B /* EncounterV2Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59490B63245FE3DA00C9802B /* EncounterV2Mapping.swift */; }; + 59490B65245FE3DA00C9802B /* EncounterV2Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59490B63245FE3DA00C9802B /* EncounterV2Mapping.swift */; }; + 594E77BF24736B77009B8B34 /* EncounterDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594E77BE24736B77009B8B34 /* EncounterDB.swift */; }; + 594E77C024736B77009B8B34 /* EncounterDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594E77BE24736B77009B8B34 /* EncounterDB.swift */; }; + 594E77C2247387B1009B8B34 /* EncounterDB+migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594E77C1247387B1009B8B34 /* EncounterDB+migration.swift */; }; + 594E77C3247387B1009B8B34 /* EncounterDB+migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594E77C1247387B1009B8B34 /* EncounterDB+migration.swift */; }; 5956CD8F246D44EA00EA4D4A /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5956CD8E246D44EA00EA4D4A /* CoreBluetooth.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 5956CD90246D44F100EA4D4A /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5956CD8E246D44EA00EA4D4A /* CoreBluetooth.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 5957EB67244E936E002F5388 /* UnderSixteenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B7ABF27244D6BE100BB249B /* UnderSixteenViewController.swift */; }; + 5961ABEA2474E358004040DF /* MigrationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5961ABE92474E358004040DF /* MigrationViewController.swift */; }; + 5961ABEB2474E358004040DF /* MigrationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5961ABE92474E358004040DF /* MigrationViewController.swift */; }; + 5961ABED2474E464004040DF /* spinner_migrating_db.json in Resources */ = {isa = PBXBuildFile; fileRef = 5961ABEC2474E464004040DF /* spinner_migrating_db.json */; }; + 5961ABEE2474E464004040DF /* spinner_migrating_db.json in Resources */ = {isa = PBXBuildFile; fileRef = 5961ABEC2474E464004040DF /* spinner_migrating_db.json */; }; 596B189924496D32003E190F /* Encounter+Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596B189824496D32003E190F /* Encounter+Util.swift */; }; 596B189A24496D32003E190F /* Encounter+Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596B189824496D32003E190F /* Encounter+Util.swift */; }; 596B189C24499591003E190F /* UploadHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596B189B24499591003E190F /* UploadHelper.swift */; }; 596B189D24499591003E190F /* UploadHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596B189B24499591003E190F /* UploadHelper.swift */; }; + 597BB7CC245FAEC00067A2E2 /* SecKey+CovidSafe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597BB7CB245FAEC00067A2E2 /* SecKey+CovidSafe.swift */; }; + 597BB7CD245FAEC00067A2E2 /* SecKey+CovidSafe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597BB7CB245FAEC00067A2E2 /* SecKey+CovidSafe.swift */; }; + 597BB7CF245FB1250067A2E2 /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597BB7CE245FB1250067A2E2 /* Crypto.swift */; }; + 597BB7D0245FB1250067A2E2 /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 597BB7CE245FB1250067A2E2 /* Crypto.swift */; }; 59898603245173C200966E61 /* URLHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59898602245173C200966E61 /* URLHelper.swift */; }; 59898604245173C200966E61 /* URLHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59898602245173C200966E61 /* URLHelper.swift */; }; 59ACB574242F195A00E63E3C /* InitiateUploadAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59ACB573242F195A00E63E3C /* InitiateUploadAPI.swift */; }; @@ -73,8 +88,6 @@ 59B7417124514126006E1EEA /* RegistrationConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59B7416F24514126006E1EEA /* RegistrationConsentViewController.swift */; }; 59F25D69245B917A002A7ED8 /* Spinner_home.json in Resources */ = {isa = PBXBuildFile; fileRef = 59F25D68245B917A002A7ED8 /* Spinner_home.json */; }; 59F25D6A245B917A002A7ED8 /* Spinner_home.json in Resources */ = {isa = PBXBuildFile; fileRef = 59F25D68245B917A002A7ED8 /* Spinner_home.json */; }; - 59F25D6C245BBCA2002A7ED8 /* Spinner_home_upload_complete.json in Resources */ = {isa = PBXBuildFile; fileRef = 59F25D6B245BBCA2002A7ED8 /* Spinner_home_upload_complete.json */; }; - 59F25D6D245BBCA2002A7ED8 /* Spinner_home_upload_complete.json in Resources */ = {isa = PBXBuildFile; fileRef = 59F25D6B245BBCA2002A7ED8 /* Spinner_home_upload_complete.json */; }; 59F25D6F245BED80002A7ED8 /* Debug.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 59F25D6E245BED80002A7ED8 /* Debug.storyboard */; }; 5B337AAB245A9BBC00537620 /* UploadDataErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B337AAA245A9BBC00537620 /* UploadDataErrorViewController.swift */; }; 5B337AAC245A9EEC00537620 /* UploadDataPrefaceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B577814245A584C0088F111 /* UploadDataPrefaceViewController.swift */; }; @@ -96,11 +109,9 @@ 5B92D674243018040049877B /* BluetraceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DC7B8D0242B8536008E1715 /* BluetraceConfig.swift */; }; 5B92D675243018040049877B /* IssueInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D300242DF1B000DC9E2A /* IssueInfo.swift */; }; 5B92D676243018040049877B /* ADGColorPallete.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D30A242DF1B000DC9E2A /* ADGColorPallete.swift */; }; - 5B92D677243018040049877B /* Encounter+Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FADDAD23F6896C006C125F /* Encounter+Event.swift */; }; 5B92D678243018040049877B /* UIKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D2FC242DF1B000DC9E2A /* UIKitExtensions.swift */; }; 5B92D679243018040049877B /* InitialScreenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FACD53923F25A9A0042A33A /* InitialScreenViewController.swift */; }; 5B92D67A243018040049877B /* ContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8DD05923E2F08400E097EF /* ContactViewController.swift */; }; - 5B92D67B243018040049877B /* OnboardingStep2cViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58D817823F16C3900345771 /* OnboardingStep2cViewController.swift */; }; 5B92D67C243018040049877B /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8DD05B23E2F0A700E097EF /* LogViewController.swift */; }; 5B92D67D243018040049877B /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5F83AF23F045A800770DEF /* HomeViewController.swift */; }; 5B92D67E243018040049877B /* InfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8DD05D23E2F0BA00E097EF /* InfoViewController.swift */; }; @@ -188,7 +199,6 @@ 5DD41D4723DCB03D00FD4AB0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5DD41D4523DCB03D00FD4AB0 /* LaunchScreen.storyboard */; }; 5DD41D4F23DCB05600FD4AB0 /* CentralController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DD41D4E23DCB05600FD4AB0 /* CentralController.swift */; }; 5DD41D5323DD4CA400FD4AB0 /* PeripheralController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DD41D5223DD4CA400FD4AB0 /* PeripheralController.swift */; }; - 7AB66BCCCA27E969EE126F9F /* Pods_CovidSafe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BE2A0527997C5042B9E2A41 /* Pods_CovidSafe.framework */; }; 7F19B4DD23F565850071A11E /* PogoInstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F19B4DC23F565850071A11E /* PogoInstructionsViewController.swift */; }; 7F2F0BA223EFFF75006D7404 /* OnboardingStep2ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2F0BA123EFFF75006D7404 /* OnboardingStep2ViewController.swift */; }; 7F36305F23F7F81400CC6E1D /* PushNotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F36305E23F7F81400CC6E1D /* PushNotificationConstants.swift */; }; @@ -223,7 +233,6 @@ A767D334242DF1B100DC9E2A /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D309242DF1B000DC9E2A /* Action.swift */; }; A767D335242DF1B100DC9E2A /* ADGColorPallete.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D30A242DF1B000DC9E2A /* ADGColorPallete.swift */; }; A767D336242DF1B100DC9E2A /* GetJMCTargetAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D30B242DF1B000DC9E2A /* GetJMCTargetAction.swift */; }; - AC1B51D6C926C60CCC1FB565 /* Pods_CovidSafe_staging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0A3F236B583DE5A31A50F1F /* Pods_CovidSafe_staging.framework */; }; B605A7B12427429D008BA819 /* PlistHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B605A7B02427429D008BA819 /* PlistHelper.swift */; }; B60F8BE4242659810007A641 /* UILabelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60F8BE3242659810007A641 /* UILabelExtension.swift */; }; B615C5A723F8EB1700345969 /* UIProgressView + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B615C5A623F8EB1700345969 /* UIProgressView + Extension.swift */; }; @@ -233,11 +242,11 @@ C56CF43F23F18A15006B05B0 /* OnboardingStep4ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56CF43E23F18A15006B05B0 /* OnboardingStep4ViewController.swift */; }; C585C83B23EEB99B0061B7C6 /* GradientButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C585C83A23EEB99B0061B7C6 /* GradientButton.swift */; }; C58D817723F169DB00345771 /* OnboardingStep2bViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58D817623F169DB00345771 /* OnboardingStep2bViewController.swift */; }; - C58D817923F16C3900345771 /* OnboardingStep2cViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58D817823F16C3900345771 /* OnboardingStep2cViewController.swift */; }; C5D209FC23F4476F007233BE /* UITextViewFixed.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D209FB23F4476F007233BE /* UITextViewFixed.swift */; }; D8DEB6822423AE2E00D99925 /* OnboardingStep1bViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DEB6812423AE2E00D99925 /* OnboardingStep1bViewController.swift */; }; D8EB201B23FA722D001C60EC /* HelpNavController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8EB201A23FA722D001C60EC /* HelpNavController.swift */; }; D8EB201D23FBE216001C60EC /* help_center_article_style.css in Resources */ = {isa = PBXBuildFile; fileRef = D8EB201C23FBE216001C60EC /* help_center_article_style.css */; }; + F4992D5F7DAE4B13FF4BB47D /* Pods_CovidSafe_staging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFB86126400FA51506E1B416 /* Pods_CovidSafe_staging.framework */; }; FB12C4C1242F0480007E893B /* RespondToAuthChallengeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12C4C0242F047F007E893B /* RespondToAuthChallengeAPI.swift */; }; FB12C4C3242F0FE9007E893B /* GetTempIdAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12C4C2242F0FE9007E893B /* GetTempIdAPI.swift */; }; FB12C4C524304AF0007E893B /* OnboardingStep1aViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12C4C424304AF0007E893B /* OnboardingStep1aViewController.swift */; }; @@ -245,6 +254,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 03E2D9045555F5F013130375 /* Pods-CovidSafe.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe.release.xcconfig"; path = "Target Support Files/Pods-CovidSafe/Pods-CovidSafe.release.xcconfig"; sourceTree = ""; }; 0B1810112431EE610005D11F /* PhoneNumberParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberParser.swift; sourceTree = ""; }; 0B22A56A242F286900D1FE60 /* UINavigationBar+Style.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Style.swift"; sourceTree = ""; }; 0B55E1912430760500C9E798 /* UITextView + Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView + Extensions.swift"; sourceTree = ""; }; @@ -255,6 +265,7 @@ 0BC141AB24305D9C00399FA8 /* NSMutableString + Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMutableString + Extensions.swift"; sourceTree = ""; }; 0BC141AD2430685800399FA8 /* UIColor + Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor + Extensions.swift"; sourceTree = ""; }; 122AF4E79D17C983066C1CEB /* Pods-CovidSafe-staging.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.release.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.release.xcconfig"; sourceTree = ""; }; + 1925EA5F4413AD52AC198894 /* Pods-CovidSafe.covid-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe.covid-production.xcconfig"; path = "Target Support Files/Pods-CovidSafe/Pods-CovidSafe.covid-production.xcconfig"; sourceTree = ""; }; 1B86118D24303E7D00EA4B6B /* Questions.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Questions.storyboard; sourceTree = ""; }; 1B86119024303EF200EA4B6B /* Question1ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Question1ViewController.swift; sourceTree = ""; }; 1B86119224303F4A00EA4B6B /* Question2ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Question2ViewController.swift; sourceTree = ""; }; @@ -264,6 +275,7 @@ 1B86119A24303FA200EA4B6B /* Question3ErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Question3ErrorViewController.swift; sourceTree = ""; }; 1B86119C24303FC000EA4B6B /* QuestionUploadDataViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionUploadDataViewController.swift; sourceTree = ""; }; 20A0AADA27329C83CFAB5A7C /* Pods-CovidSafe.covid-staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe.covid-staging.xcconfig"; path = "Target Support Files/Pods-CovidSafe/Pods-CovidSafe.covid-staging.xcconfig"; sourceTree = ""; }; + 2B916D8946F8A94E32E569C7 /* Pods-CovidSafe.covid-staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe.covid-staging.xcconfig"; path = "Target Support Files/Pods-CovidSafe/Pods-CovidSafe.covid-staging.xcconfig"; sourceTree = ""; }; 30BE1CA923F108BA005DCE4F /* UploadFileData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadFileData.swift; sourceTree = ""; }; 30BE1CAC23F119FD005DCE4F /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; 30BE1CAE23F1349F005DCE4F /* EncounterRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncounterRecord.swift; sourceTree = ""; }; @@ -271,15 +283,27 @@ 30BE1CB423F15D47005DCE4F /* OTPViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTPViewController.swift; sourceTree = ""; }; 30E91BE823EFE514002D592A /* UploadDataVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadDataVC.swift; sourceTree = ""; }; 30E91BEA23EFEA0B002D592A /* CodeInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeInputView.swift; sourceTree = ""; }; - 30FADDAD23F6896C006C125F /* Encounter+Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Encounter+Event.swift"; sourceTree = ""; }; 342FF717762F714E56F4412D /* Pods-CovidSafe.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe.release.xcconfig"; path = "Target Support Files/Pods-CovidSafe/Pods-CovidSafe.release.xcconfig"; sourceTree = ""; }; + 34E9EB96EA6E42A571E73C8E /* Pods_CovidSafe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CovidSafe.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 39E21BD3842669F051A5D6D8 /* Pods-CovidSafe.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe.debug.xcconfig"; path = "Target Support Files/Pods-CovidSafe/Pods-CovidSafe.debug.xcconfig"; sourceTree = ""; }; 3A403F07F3B7C5A94CBFC43D /* Pods-CovidSafe-staging.covid-staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.covid-staging.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.covid-staging.xcconfig"; sourceTree = ""; }; + 46A5730925DA6B664DFE9546 /* Pods-CovidSafe-staging.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.debug.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.debug.xcconfig"; sourceTree = ""; }; 46D0C3F015CA753BB4D4787D /* Pods-CovidSafe-staging.covid-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.covid-production.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.covid-production.xcconfig"; sourceTree = ""; }; + 49A22E09D113DF058C94C6E6 /* Pods-CovidSafe-staging.covid-staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.covid-staging.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.covid-staging.xcconfig"; sourceTree = ""; }; + 5904A5C12462471A008C8012 /* EncounterV1toV2.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = EncounterV1toV2.xcmappingmodel; sourceTree = ""; }; 5909E4AA245043C400D41C26 /* CovidPersistentContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CovidPersistentContainer.swift; sourceTree = ""; }; 592CBB7F2441A583001FFCE9 /* PersonalDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalDetailsViewController.swift; sourceTree = ""; }; + 59490B61245FE22C00C9802B /* ModelV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ModelV2.xcdatamodel; sourceTree = ""; }; + 59490B63245FE3DA00C9802B /* EncounterV2Mapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncounterV2Mapping.swift; sourceTree = ""; }; + 594E77BE24736B77009B8B34 /* EncounterDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncounterDB.swift; sourceTree = ""; }; + 594E77C1247387B1009B8B34 /* EncounterDB+migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EncounterDB+migration.swift"; sourceTree = ""; }; 5956CD8E246D44EA00EA4D4A /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; }; + 5961ABE92474E358004040DF /* MigrationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationViewController.swift; sourceTree = ""; }; + 5961ABEC2474E464004040DF /* spinner_migrating_db.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = spinner_migrating_db.json; sourceTree = ""; }; 596B189824496D32003E190F /* Encounter+Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Encounter+Util.swift"; sourceTree = ""; }; 596B189B24499591003E190F /* UploadHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadHelper.swift; sourceTree = ""; }; + 597BB7CB245FAEC00067A2E2 /* SecKey+CovidSafe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SecKey+CovidSafe.swift"; sourceTree = ""; }; + 597BB7CE245FB1250067A2E2 /* Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = ""; }; 59898602245173C200966E61 /* URLHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLHelper.swift; sourceTree = ""; }; 59ACB573242F195A00E63E3C /* InitiateUploadAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitiateUploadAPI.swift; sourceTree = ""; }; 59ACB575242F404500E63E3C /* DataUploadS3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataUploadS3.swift; sourceTree = ""; }; @@ -288,7 +312,6 @@ 59AF2EB12435A38100ACCAF2 /* CovidRequestRetrier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CovidRequestRetrier.swift; sourceTree = ""; }; 59B7416F24514126006E1EEA /* RegistrationConsentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationConsentViewController.swift; sourceTree = ""; }; 59F25D68245B917A002A7ED8 /* Spinner_home.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Spinner_home.json; sourceTree = ""; }; - 59F25D6B245BBCA2002A7ED8 /* Spinner_home_upload_complete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Spinner_home_upload_complete.json; sourceTree = ""; }; 59F25D6E245BED80002A7ED8 /* Debug.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Debug.storyboard; sourceTree = ""; }; 5B337AAA245A9BBC00537620 /* UploadDataErrorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadDataErrorViewController.swift; sourceTree = ""; }; 5B337AAE245AA26300537620 /* Spinner_upload.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Spinner_upload.json; sourceTree = ""; }; @@ -299,7 +322,6 @@ 5B92D666243012330049877B /* CovidSafe-config.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "CovidSafe-config.plist"; path = "Resources/STG/CovidSafe-config.plist"; sourceTree = ""; }; 5B92D6D9243018040049877B /* COVIDSafe-staging.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "COVIDSafe-staging.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 5BD3EE8224330E1A0004A007 /* UploadDataStep2VC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadDataStep2VC.swift; sourceTree = ""; }; - 5BE2A0527997C5042B9E2A41 /* Pods_CovidSafe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CovidSafe.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5BFFD94A242EC120003AEF4F /* PhoneValidationAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneValidationAPI.swift; sourceTree = ""; }; 5D269C0A23E22CC400ADF2DE /* DeviceIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceIdentifier.swift; sourceTree = ""; }; 5D269C0C23E2958F00ADF2DE /* BluetraceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetraceManager.swift; sourceTree = ""; }; @@ -331,6 +353,7 @@ 7FACD53923F25A9A0042A33A /* InitialScreenViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialScreenViewController.swift; sourceTree = ""; }; 7FEC361423F16A1E00127AFB /* UIViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtension.swift; sourceTree = ""; }; 7FF75C212429FEE800C11FEA /* CountriesData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountriesData.swift; sourceTree = ""; }; + 81B28CDCE1F29BA0F1D30CCE /* Pods-CovidSafe-staging.covid-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.covid-production.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.covid-production.xcconfig"; sourceTree = ""; }; A767D2B6242DF1B000DC9E2A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Feedback.strings; sourceTree = ""; }; A767D2B7242DF1B000DC9E2A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/NewFeedbackFlow.strings; sourceTree = ""; }; A767D2D1242DF1B000DC9E2A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/NewFeedbackFlow.storyboard; sourceTree = ""; }; @@ -369,9 +392,7 @@ C56CF43E23F18A15006B05B0 /* OnboardingStep4ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep4ViewController.swift; sourceTree = ""; }; C585C83A23EEB99B0061B7C6 /* GradientButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButton.swift; sourceTree = ""; }; C58D817623F169DB00345771 /* OnboardingStep2bViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep2bViewController.swift; sourceTree = ""; }; - C58D817823F16C3900345771 /* OnboardingStep2cViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep2cViewController.swift; sourceTree = ""; }; C5D209FB23F4476F007233BE /* UITextViewFixed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextViewFixed.swift; sourceTree = ""; }; - D0A3F236B583DE5A31A50F1F /* Pods_CovidSafe_staging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CovidSafe_staging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D8DEB6812423AE2E00D99925 /* OnboardingStep1bViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep1bViewController.swift; sourceTree = ""; }; D8EB201A23FA722D001C60EC /* HelpNavController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpNavController.swift; sourceTree = ""; }; D8EB201C23FBE216001C60EC /* help_center_article_style.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = help_center_article_style.css; sourceTree = ""; }; @@ -380,6 +401,8 @@ FB12C4C0242F047F007E893B /* RespondToAuthChallengeAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RespondToAuthChallengeAPI.swift; sourceTree = ""; }; FB12C4C2242F0FE9007E893B /* GetTempIdAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetTempIdAPI.swift; sourceTree = ""; }; FB12C4C424304AF0007E893B /* OnboardingStep1aViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingStep1aViewController.swift; sourceTree = ""; }; + FB81BAD81D73C6FD42202A04 /* Pods-CovidSafe-staging.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CovidSafe-staging.release.xcconfig"; path = "Target Support Files/Pods-CovidSafe-staging/Pods-CovidSafe-staging.release.xcconfig"; sourceTree = ""; }; + FFB86126400FA51506E1B416 /* Pods_CovidSafe_staging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CovidSafe_staging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -388,7 +411,7 @@ buildActionMask = 2147483647; files = ( 5956CD8F246D44EA00EA4D4A /* CoreBluetooth.framework in Frameworks */, - AC1B51D6C926C60CCC1FB565 /* Pods_CovidCare_staging.framework in Frameworks */, + F4992D5F7DAE4B13FF4BB47D /* Pods_CovidSafe_staging.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -397,8 +420,7 @@ buildActionMask = 2147483647; files = ( 5956CD90246D44F100EA4D4A /* CoreBluetooth.framework in Frameworks */, - 469DD4CFF262D29768A8CBEF /* Pods_CovidCare.framework in Frameworks */, - 7AB66BCCCA27E969EE126F9F /* Pods_CovidCare.framework in Frameworks */, + 3A8951FDED310D7ABA54259E /* Pods_CovidSafe.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -432,9 +454,9 @@ 30BE1CB223F13B26005DCE4F /* Models */ = { isa = PBXGroup; children = ( + 59490B62245FE3BD00C9802B /* V2 */, 5D8DD05F23E319B300E097EF /* Encounter+CoreDataClass.swift */, 5D8DD06023E319B300E097EF /* Encounter+CoreDataProperties.swift */, - 30FADDAD23F6896C006C125F /* Encounter+Event.swift */, 596B189824496D32003E190F /* Encounter+Util.swift */, ); name = Models; @@ -449,7 +471,6 @@ FB12C4C424304AF0007E893B /* OnboardingStep1aViewController.swift */, 7F2F0BA123EFFF75006D7404 /* OnboardingStep2ViewController.swift */, C58D817623F169DB00345771 /* OnboardingStep2bViewController.swift */, - C58D817823F16C3900345771 /* OnboardingStep2cViewController.swift */, C56CF43E23F18A15006B05B0 /* OnboardingStep4ViewController.swift */, 5D8DD05923E2F08400E097EF /* ContactViewController.swift */, 5D8DD05B23E2F0A700E097EF /* LogViewController.swift */, @@ -465,6 +486,7 @@ 5B7ABF24244D3BC600BB249B /* IsolationSuccessViewController.swift */, 5B7ABF27244D6BE100BB249B /* UnderSixteenViewController.swift */, 59B7416F24514126006E1EEA /* RegistrationConsentViewController.swift */, + 5961ABE92474E358004040DF /* MigrationViewController.swift */, ); name = "View Controllers"; sourceTree = ""; @@ -510,20 +532,40 @@ isa = PBXGroup; children = ( 5909E4AA245043C400D41C26 /* CovidPersistentContainer.swift */, + 594E77BE24736B77009B8B34 /* EncounterDB.swift */, + 594E77C1247387B1009B8B34 /* EncounterDB+migration.swift */, ); name = CoreData; sourceTree = ""; }; + 59490B62245FE3BD00C9802B /* V2 */ = { + isa = PBXGroup; + children = ( + 59490B63245FE3DA00C9802B /* EncounterV2Mapping.swift */, + 5904A5C12462471A008C8012 /* EncounterV1toV2.xcmappingmodel */, + ); + name = V2; + sourceTree = ""; + }; 5949DC522434859600AE76BC /* lottie */ = { isa = PBXGroup; children = ( + 5961ABEC2474E464004040DF /* spinner_migrating_db.json */, 5B337AAE245AA26300537620 /* Spinner_upload.json */, 59F25D68245B917A002A7ED8 /* Spinner_home.json */, - 59F25D6B245BBCA2002A7ED8 /* Spinner_home_upload_complete.json */, ); name = lottie; sourceTree = ""; }; + 597BB7CA245FACE20067A2E2 /* Crypto */ = { + isa = PBXGroup; + children = ( + 597BB7CB245FAEC00067A2E2 /* SecKey+CovidSafe.swift */, + 597BB7CE245FB1250067A2E2 /* Crypto.swift */, + ); + name = Crypto; + sourceTree = ""; + }; 59AF2E97243552FB00ACCAF2 /* Certificates */ = { isa = PBXGroup; children = ( @@ -603,6 +645,7 @@ 5DD41D3923DCB03B00FD4AB0 /* CovidSafe */ = { isa = PBXGroup; children = ( + 597BB7CA245FACE20067A2E2 /* Crypto */, 5949DC522434859600AE76BC /* lottie */, 5B92D661243005B10049877B /* Resources */, A767D2AE242DF1B000DC9E2A /* Feedback */, @@ -700,8 +743,8 @@ isa = PBXGroup; children = ( 5956CD8E246D44EA00EA4D4A /* CoreBluetooth.framework */, - 5BE2A0527997C5042B9E2A41 /* Pods_CovidCare.framework */, - D0A3F236B583DE5A31A50F1F /* Pods_CovidCare_staging.framework */, + 34E9EB96EA6E42A571E73C8E /* Pods_CovidSafe.framework */, + FFB86126400FA51506E1B416 /* Pods_CovidSafe_staging.framework */, ); name = Frameworks; sourceTree = ""; @@ -732,6 +775,14 @@ 342FF717762F714E56F4412D /* Pods-CovidSafe.release.xcconfig */, E3B9CCF78D93EFF0EF2A0ADB /* Pods-CovidSafe-staging.debug.xcconfig */, 122AF4E79D17C983066C1CEB /* Pods-CovidSafe-staging.release.xcconfig */, + 39E21BD3842669F051A5D6D8 /* Pods-CovidSafe.debug.xcconfig */, + 2B916D8946F8A94E32E569C7 /* Pods-CovidSafe.covid-staging.xcconfig */, + 03E2D9045555F5F013130375 /* Pods-CovidSafe.release.xcconfig */, + 1925EA5F4413AD52AC198894 /* Pods-CovidSafe.covid-production.xcconfig */, + 46A5730925DA6B664DFE9546 /* Pods-CovidSafe-staging.debug.xcconfig */, + 49A22E09D113DF058C94C6E6 /* Pods-CovidSafe-staging.covid-staging.xcconfig */, + FB81BAD81D73C6FD42202A04 /* Pods-CovidSafe-staging.release.xcconfig */, + 81B28CDCE1F29BA0F1D30CCE /* Pods-CovidSafe-staging.covid-production.xcconfig */, ); path = Pods; sourceTree = ""; @@ -868,10 +919,11 @@ 5B92D6C2243018040049877B /* Localizable.strings in Resources */, 5B92D6C7243018040049877B /* CovidSafe-config.plist in Resources */, 5B92D6C8243018040049877B /* NewFeedbackFlow.strings in Resources */, + 5961ABEE2474E464004040DF /* spinner_migrating_db.json in Resources */, + 59AF2EAD2435801400ACCAF2 /* AmazonRootCA4.cer in Resources */, 59F25D6F245BED80002A7ED8 /* Debug.storyboard in Resources */, 5B92D6C9243018040049877B /* LaunchScreen.storyboard in Resources */, 5B92D6CB243018040049877B /* Assets.xcassets in Resources */, - 59F25D6D245BBCA2002A7ED8 /* Spinner_home_upload_complete.json in Resources */, 5B92D6CC243018040049877B /* Feedback.strings in Resources */, 5B337AB0245AA26300537620 /* Spinner_upload.json in Resources */, 5B92D6CF243018040049877B /* Main.storyboard in Resources */, @@ -893,9 +945,10 @@ A767D30F242DF1B000DC9E2A /* NewFeedbackFlow.strings in Resources */, 5DD41D4723DCB03D00FD4AB0 /* LaunchScreen.storyboard in Resources */, 5DD41D4423DCB03D00FD4AB0 /* Assets.xcassets in Resources */, - 59F25D6C245BBCA2002A7ED8 /* Spinner_home_upload_complete.json in Resources */, A767D30E242DF1B000DC9E2A /* Feedback.strings in Resources */, 5B337AAF245AA26300537620 /* Spinner_upload.json in Resources */, + 59AF2EAA2435801400ACCAF2 /* AmazonRootCA3.cer in Resources */, + 5961ABED2474E464004040DF /* spinner_migrating_db.json in Resources */, 5DD41D4223DCB03B00FD4AB0 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -998,16 +1051,16 @@ 5B92D674243018040049877B /* BluetraceConfig.swift in Sources */, 5B92D675243018040049877B /* IssueInfo.swift in Sources */, 5B92D676243018040049877B /* ADGColorPallete.swift in Sources */, - 5B92D677243018040049877B /* Encounter+Event.swift in Sources */, 5B92D678243018040049877B /* UIKitExtensions.swift in Sources */, 5B92D679243018040049877B /* InitialScreenViewController.swift in Sources */, + 59490B65245FE3DA00C9802B /* EncounterV2Mapping.swift in Sources */, 5B92D67A243018040049877B /* ContactViewController.swift in Sources */, - 5B92D67B243018040049877B /* OnboardingStep2cViewController.swift in Sources */, 5B92D67C243018040049877B /* LogViewController.swift in Sources */, 5B92D67D243018040049877B /* HomeViewController.swift in Sources */, 5B92D67E243018040049877B /* InfoViewController.swift in Sources */, 5B92D67F243018040049877B /* PogoInstructionsViewController.swift in Sources */, FBBBFCE82430A933002B174D /* OnboardingStep1aViewController.swift in Sources */, + 594E77C3247387B1009B8B34 /* EncounterDB+migration.swift in Sources */, 590C99332432C1C400A5EC71 /* UploadDataHomeViewController.swift in Sources */, 59B7417124514126006E1EEA /* RegistrationConsentViewController.swift in Sources */, 5B92D680243018040049877B /* CodeInputView.swift in Sources */, @@ -1020,11 +1073,13 @@ 5B92D685243018040049877B /* UILabelExtension.swift in Sources */, 5B92D686243018040049877B /* AppDelegate.swift in Sources */, 5B92D687243018040049877B /* PhoneNumberViewController.swift in Sources */, + 5961ABEB2474E358004040DF /* MigrationViewController.swift in Sources */, 5B92D688243018040049877B /* BluetraceUtils.swift in Sources */, 5B92D689243018040049877B /* NewFeedbackFlowController.swift in Sources */, 5B92D68A243018040049877B /* Outcome.swift in Sources */, 5B92D68B243018040049877B /* Encounter+EncounterRecord.swift in Sources */, 0B42D0DE2432B39E00E4F44C /* Question2ErrorViewController.swift in Sources */, + 594E77C024736B77009B8B34 /* EncounterDB.swift in Sources */, 590888B42431B9F5008C9B9F /* UploadDataNavigationController.swift in Sources */, 5B92D68C243018040049877B /* GetJMCTargetAction.swift in Sources */, 5B92D68D243018040049877B /* Logging.swift in Sources */, @@ -1032,9 +1087,12 @@ 5B92D68E243018040049877B /* HelpNavController.swift in Sources */, 5B92D68F243018040049877B /* OTPViewController.swift in Sources */, 596B189A24496D32003E190F /* Encounter+Util.swift in Sources */, + 5904A5C32462471A008C8012 /* EncounterV1toV2.xcmappingmodel in Sources */, 5B92D690243018040049877B /* Encounter+CoreDataClass.swift in Sources */, 5B92D691243018040049877B /* RespondToAuthChallengeAPI.swift in Sources */, 5B92D692243018040049877B /* BluetraceManager.swift in Sources */, + 597BB7CD245FAEC00067A2E2 /* SecKey+CovidSafe.swift in Sources */, + 597BB7D0245FB1250067A2E2 /* Crypto.swift in Sources */, 5B92D693243018040049877B /* UIColor+Hex.swift in Sources */, 5B92D694243018040049877B /* OnboardingStep1ViewController.swift in Sources */, 5B92D695243018040049877B /* DeviceInfoExtension.swift in Sources */, @@ -1113,17 +1171,17 @@ 0B69E7EB2430CF4100561DD9 /* UploadDataThankYouHomeViewController.swift in Sources */, 1B86119924303F9600EA4B6B /* Question2ErrorViewController.swift in Sources */, A767D335242DF1B100DC9E2A /* ADGColorPallete.swift in Sources */, - 30FADDAE23F6896C006C125F /* Encounter+Event.swift in Sources */, A767D327242DF1B100DC9E2A /* UIKitExtensions.swift in Sources */, 7FACD53A23F25A9A0042A33A /* InitialScreenViewController.swift in Sources */, - C58D817923F16C3900345771 /* OnboardingStep2cViewController.swift in Sources */, 59B7417024514126006E1EEA /* RegistrationConsentViewController.swift in Sources */, 5D5F83B023F045A800770DEF /* HomeViewController.swift in Sources */, 5B337AAB245A9BBC00537620 /* UploadDataErrorViewController.swift in Sources */, FB12C4C524304AF0007E893B /* OnboardingStep1aViewController.swift in Sources */, 7F19B4DD23F565850071A11E /* PogoInstructionsViewController.swift in Sources */, 30E91BEB23EFEA0B002D592A /* CodeInputView.swift in Sources */, + 597BB7CF245FB1250067A2E2 /* Crypto.swift in Sources */, 30BE1CAF23F1349F005DCE4F /* EncounterRecord.swift in Sources */, + 59490B64245FE3DA00C9802B /* EncounterV2Mapping.swift in Sources */, C58D817723F169DB00345771 /* OnboardingStep2bViewController.swift in Sources */, 59ACB574242F195A00E63E3C /* InitiateUploadAPI.swift in Sources */, A767D324242DF1B100DC9E2A /* AsyncAction.swift in Sources */, @@ -1138,8 +1196,10 @@ A767D316242DF1B000DC9E2A /* Logging.swift in Sources */, D8EB201B23FA722D001C60EC /* HelpNavController.swift in Sources */, 596B189924496D32003E190F /* Encounter+Util.swift in Sources */, + 5961ABEA2474E358004040DF /* MigrationViewController.swift in Sources */, 30BE1CB523F15D47005DCE4F /* OTPViewController.swift in Sources */, 5D8DD06123E319B300E097EF /* Encounter+CoreDataClass.swift in Sources */, + 594E77BF24736B77009B8B34 /* EncounterDB.swift in Sources */, FB12C4C1242F0480007E893B /* RespondToAuthChallengeAPI.swift in Sources */, 5D269C0D23E2958F00ADF2DE /* BluetraceManager.swift in Sources */, 1B86119124303EF200EA4B6B /* Question1ViewController.swift in Sources */, @@ -1153,6 +1213,7 @@ A767D325242DF1B100DC9E2A /* SendFeedbackAction.swift in Sources */, C5D209FC23F4476F007233BE /* UITextViewFixed.swift in Sources */, C585C83B23EEB99B0061B7C6 /* GradientButton.swift in Sources */, + 597BB7CC245FAEC00067A2E2 /* SecKey+CovidSafe.swift in Sources */, 5B7ABF25244D3BC600BB249B /* IsolationSuccessViewController.swift in Sources */, A767D331242DF1B100DC9E2A /* ViewControllerFactory.swift in Sources */, 0BA617CE242E09B200E6C631 /* FeedbackViewController.swift in Sources */, @@ -1186,6 +1247,7 @@ A767D334242DF1B100DC9E2A /* Action.swift in Sources */, 59AF2E992435533A00ACCAF2 /* CovidCertificates.swift in Sources */, 7F2F0BA223EFFF75006D7404 /* OnboardingStep2ViewController.swift in Sources */, + 594E77C2247387B1009B8B34 /* EncounterDB+migration.swift in Sources */, 59898603245173C200966E61 /* URLHelper.swift in Sources */, A767D31F242DF1B000DC9E2A /* HTTPPostFeedbackAction.swift in Sources */, 7F36305F23F7F81400CC6E1D /* PushNotificationConstants.swift in Sources */, @@ -1196,6 +1258,7 @@ 1B86119324303F4A00EA4B6B /* Question2ViewController.swift in Sources */, 5D8DD06223E319B300E097EF /* Encounter+CoreDataProperties.swift in Sources */, A767D32A242DF1B100DC9E2A /* Issue.swift in Sources */, + 5904A5C22462471A008C8012 /* EncounterV1toV2.xcmappingmodel in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1308,21 +1371,21 @@ }; 59E766DD242CBC590015EA07 /* covid-staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 20A0AADA27329C83CFAB5A7C /* Pods-CovidSafe.covid-staging.xcconfig */; + baseConfigurationReference = 2B916D8946F8A94E32E569C7 /* Pods-CovidSafe.covid-staging.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 25; DEVELOPMENT_TEAM = 45792XH5L8; - INFOPLIST_FILE = CovidSafe/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3; + MARKETING_VERSION = 1.4; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe; PRODUCT_NAME = COVIDSafe; @@ -1391,21 +1454,21 @@ }; 59E766DF242CBC8C0015EA07 /* covid-production */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 679DE00E3E364DA756795844 /* Pods-CovidSafe.covid-production.xcconfig */; + baseConfigurationReference = 1925EA5F4413AD52AC198894 /* Pods-CovidSafe.covid-production.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 25; DEVELOPMENT_TEAM = 45792XH5L8; - INFOPLIST_FILE = CovidSafe/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3; + MARKETING_VERSION = 1.4; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe; PRODUCT_NAME = COVIDSafe; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1419,24 +1482,25 @@ }; 5B92D6D5243018040049877B /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E3B9CCF78D93EFF0EF2A0ADB /* Pods-CovidSafe-staging.debug.xcconfig */; + baseConfigurationReference = 46A5730925DA6B664DFE9546 /* Pods-CovidSafe-staging.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 19; + CURRENT_PROJECT_VERSION = 25; DEVELOPMENT_TEAM = 45792XH5L8; - INFOPLIST_FILE = CovidSafe/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3; + MARKETING_VERSION = 1.4; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe; + PRODUCT_MODULE_NAME = COVIDSafe; PRODUCT_NAME = "COVIDSafe-staging"; PROVISIONING_PROFILE_SPECIFIER = ""; SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065"; @@ -1448,24 +1512,25 @@ }; 5B92D6D6243018040049877B /* covid-staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3A403F07F3B7C5A94CBFC43D /* Pods-CovidSafe-staging.covid-staging.xcconfig */; + baseConfigurationReference = 49A22E09D113DF058C94C6E6 /* Pods-CovidSafe-staging.covid-staging.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 19; + CURRENT_PROJECT_VERSION = 25; DEVELOPMENT_TEAM = 45792XH5L8; - INFOPLIST_FILE = CovidSafe/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3; + MARKETING_VERSION = 1.4; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe; + PRODUCT_MODULE_NAME = COVIDSafe; PRODUCT_NAME = "COVIDSafe-staging"; PROVISIONING_PROFILE_SPECIFIER = ""; SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065"; @@ -1477,23 +1542,24 @@ }; 5B92D6D7243018040049877B /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 122AF4E79D17C983066C1CEB /* Pods-CovidSafe-staging.release.xcconfig */; + baseConfigurationReference = FB81BAD81D73C6FD42202A04 /* Pods-CovidSafe-staging.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 19; + CURRENT_PROJECT_VERSION = 25; DEVELOPMENT_TEAM = 45792XH5L8; - INFOPLIST_FILE = CovidSafe/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3; + MARKETING_VERSION = 1.4; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe; + PRODUCT_MODULE_NAME = COVIDSafe; PRODUCT_NAME = "COVIDSafe-staging"; PROVISIONING_PROFILE_SPECIFIER = ""; SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065"; @@ -1506,23 +1572,24 @@ }; 5B92D6D8243018040049877B /* covid-production */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 46D0C3F015CA753BB4D4787D /* Pods-CovidSafe-staging.covid-production.xcconfig */; + baseConfigurationReference = 81B28CDCE1F29BA0F1D30CCE /* Pods-CovidSafe-staging.covid-production.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 19; + CURRENT_PROJECT_VERSION = 25; DEVELOPMENT_TEAM = 45792XH5L8; - INFOPLIST_FILE = CovidSafe/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3; + MARKETING_VERSION = 1.4; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe; + PRODUCT_MODULE_NAME = COVIDSafe; PRODUCT_NAME = "COVIDSafe-staging"; PROVISIONING_PROFILE_SPECIFIER = ""; SERVICE_UUID = "CC0AC8B7-03B5-4252-8D84-44D199E16065"; @@ -1651,21 +1718,21 @@ }; 5DD41D4C23DCB03D00FD4AB0 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6EBCA1AD6D10F8DF7BBDC660 /* Pods-CovidSafe.debug.xcconfig */; + baseConfigurationReference = 39E21BD3842669F051A5D6D8 /* Pods-CovidSafe.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 25; DEVELOPMENT_TEAM = 45792XH5L8; - INFOPLIST_FILE = CovidSafe/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3; + MARKETING_VERSION = 1.4; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe; PRODUCT_NAME = COVIDSafe; @@ -1679,21 +1746,21 @@ }; 5DD41D4D23DCB03D00FD4AB0 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 342FF717762F714E56F4412D /* Pods-CovidSafe.release.xcconfig */; + baseConfigurationReference = 03E2D9045555F5F013130375 /* Pods-CovidSafe.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 25; DEVELOPMENT_TEAM = 45792XH5L8; - INFOPLIST_FILE = CovidSafe/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3; + MARKETING_VERSION = 1.4; PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe; PRODUCT_NAME = COVIDSafe; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1747,9 +1814,10 @@ 5DD41D7723DE141700FD4AB0 /* tracer.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 59490B61245FE22C00C9802B /* ModelV2.xcdatamodel */, 5DD41D7823DE141700FD4AB0 /* Model.xcdatamodel */, ); - currentVersion = 5DD41D7823DE141700FD4AB0 /* Model.xcdatamodel */; + currentVersion = 59490B61245FE22C00C9802B /* ModelV2.xcdatamodel */; path = tracer.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/CovidSafe.xcodeproj/xcshareddata/xcschemes/covid-staging.xcscheme b/CovidSafe.xcodeproj/xcshareddata/xcschemes/covid-staging.xcscheme index aa09098..31f44c4 100644 --- a/CovidSafe.xcodeproj/xcshareddata/xcschemes/covid-staging.xcscheme +++ b/CovidSafe.xcodeproj/xcshareddata/xcschemes/covid-staging.xcscheme @@ -50,6 +50,12 @@ ReferencedContainer = "container:CovidSafe.xcodeproj"> + + + + Bool { setupCoredataDir() - Encounter.timestamp(for: .appStarted) let firstRun = UserDefaults.standard.bool(forKey: "HasBeenLaunched") if( !firstRun ) { let keychain = KeychainSwift() @@ -49,7 +48,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Remote config setup let _ = TracerRemoteConfig() - + return true } @@ -164,27 +163,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { scheduleReminderNotifications() } - // MARK: - Core Data stack - - lazy var persistentContainer: CovidPersistentContainer = { - /* - The persistent container for the application. This implementation - creates and returns a container, having loaded the store for the - application to it. This property is optional since there are legitimate - error conditions that could cause the creation of the store to fail. - */ - let container = CovidPersistentContainer(name: "tracer") - container.loadPersistentStores(completionHandler: { (storeDescription, error) in - if let error = error as NSError? { - fatalError("Unresolved error \(error), \(error.userInfo)") - } - }) - return container - }() - func applicationDidBecomeActive(_ application: UIApplication) { DLog("applicationDidBecomeActive") - Encounter.timestamp(for: .appEnteredForeground) startAccelerometerUpdates() clearOldDataInContext() @@ -199,7 +179,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationDidEnterBackground(_ application: UIApplication) { DLog("applicationDidEnterBackground") - Encounter.timestamp(for: .appEnteredBackground) self.dismissBlackscreen() stopAccelerometerUpdates() @@ -214,25 +193,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillTerminate(_ application: UIApplication) { DLog("applicationWillTerminate") - Encounter.timestamp(for: .appTerminating) stopAccelerometerUpdates() } - // MARK: - Core Data Saving support - - func saveContext () { - let context = persistentContainer.viewContext - if context.hasChanges { - do { - try context.save() - } catch { - let nserror = error as NSError - fatalError("Unresolved error \(nserror), \(nserror.userInfo)") - } - } - } - func clearOldDataInContext() { var calendar = Calendar.current calendar.timeZone = NSTimeZone.local @@ -250,7 +214,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { registerBackgroundTask() let dispatchQueue = DispatchQueue(label: "DeleteOldData", qos: .background) dispatchQueue.async{ - let managedContext = self.persistentContainer.viewContext + guard let persistentContainer = EncounterDB.shared.persistentContainer else { + self.endBackgroundTask() + return + } + let managedContext = persistentContainer.viewContext if let oldFetchRequest = Encounter.fetchOldEncounters() { let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: oldFetchRequest) do { diff --git a/CovidSafe/Base.lproj/Main.storyboard b/CovidSafe/Base.lproj/Main.storyboard index 1c84ef8..fc36a5f 100644 --- a/CovidSafe/Base.lproj/Main.storyboard +++ b/CovidSafe/Base.lproj/Main.storyboard @@ -20,7 +20,7 @@ - + @@ -38,9 +38,9 @@ - @@ -107,7 +124,7 @@ - + @@ -125,10 +142,18 @@ + + + + + + + + - + @@ -239,7 +264,7 @@ Together we can help stop the spread and stay healthy. - + @@ -952,7 +979,6 @@ They will need to register using their own device and phone number so that COVID - @@ -960,73 +986,6 @@ They will need to register using their own device and phone number so that COVID - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1039,7 +998,7 @@ They will need to register using their own device and phone number so that COVID - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2247,16 +2143,77 @@ and save lives. - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2722,7 +2679,7 @@ See the COVIDSafe *privacy policy* for further details about your rights about y - - - - - - - + + + + + - - diff --git a/CovidSafe/BluetraceConfig.swift b/CovidSafe/BluetraceConfig.swift index dc625d5..700751f 100644 --- a/CovidSafe/BluetraceConfig.swift +++ b/CovidSafe/BluetraceConfig.swift @@ -13,8 +13,12 @@ struct BluetraceConfig { static let BluetoothServiceID = CBUUID(string: "\(PlistHelper.getvalueFromInfoPlist(withKey: "TRACER_SVC_ID") ?? "B82AB3FC-1595-4F6A-80F0-FE094CC218F9")") static let OrgID = "AU_DTA" - static let ProtocolVersion = 1 + static let ProtocolVersion = 2 static let CentralScanInterval = 60.0 // in seconds static let CentralScanDuration = 10 // in seconds + + static let DummyModel = "" + static let DummyRSSI = 999 + static let DummyTxPower = 999 } diff --git a/CovidSafe/CentralController.swift b/CovidSafe/CentralController.swift index 4397096..b87c15f 100644 --- a/CovidSafe/CentralController.swift +++ b/CovidSafe/CentralController.swift @@ -65,22 +65,30 @@ class CentralController: NSObject { func shouldRecordEncounter(_ encounter: EncounterRecord) -> Bool { guard let scannedDate = encounter.timestamp else { + DLog("Not recorded encounter before \(encounter)") return true } if abs(scannedDate.timeIntervalSinceNow) > BluetraceConfig.CentralScanInterval { + DLog("Encounter last recorded \(abs(scannedDate.timeIntervalSinceNow)) seconds ago") return true } return false } func shouldReconnectToPeripheral(peripheral: CBPeripheral) -> Bool { + guard peripheral.state == .disconnected else { + return false + } guard let encounteredPeripheral = scannedPeripherals[peripheral.identifier] else { + DLog("Not previously encountered CBPeripheral \(String(describing: peripheral.name))") return true } guard let scannedDate = encounteredPeripheral.encounter.timestamp else { + DLog("Not previously recorded an encounter with \(encounteredPeripheral)") return true } if abs(scannedDate.timeIntervalSinceNow) > BluetraceConfig.CentralScanInterval { + DLog("Peripheral last recorded \(abs(scannedDate.timeIntervalSinceNow)) seconds ago") return true } return false @@ -110,7 +118,6 @@ extension CentralController: CBCentralManagerDelegate { switch central.state { case .poweredOn: DLog("CC Starting a scan") - Encounter.timestamp(for: .scanningStarted) // for all peripherals that are not disconnected, disconnect them self.scannedPeripherals.forEach { (scannedPeri) in @@ -128,7 +135,7 @@ extension CentralController: CBCentralManagerDelegate { central.connect(recoveredPeripheral) } - central.scanForPeripherals(withServices: [BluetraceConfig.BluetoothServiceID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: NSNumber(true)]) + central.scanForPeripherals(withServices: [BluetraceConfig.BluetoothServiceID], options:nil) default: DLog("State chnged to \(central.state)") } @@ -196,10 +203,8 @@ extension CentralController: CBCentralManagerDelegate { if let encounteredPeripheral = scannedPeripherals[peripheral.identifier] { if shouldReconnectToPeripheral(peripheral: encounteredPeripheral.peripheral) { peripheral.delegate = self - if peripheral.state != .connected { - central.connect(peripheral) - DLog("found previous peripheral from more than 60 seconds ago") - } + central.connect(peripheral) + DLog("found previous peripheral from more than 60 seconds ago") } else { DLog("iOS Peripheral \(peripheral) has been discovered already in this window, will not attempt to connect to it again") if let scannedDate = encounteredPeripheral.encounter.timestamp { @@ -217,7 +222,8 @@ extension CentralController: CBCentralManagerDelegate { func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { let peripheralStateString = BluetraceUtils.peripheralStateToString(peripheral.state) DLog("CC didConnect peripheral peripheralCentral state: \(BluetraceUtils.centralStateToString(central.state)), Peripheral state: \(peripheralStateString)") - guard shouldReconnectToPeripheral(peripheral: peripheral) else { + + guard let seenPeripheral = scannedPeripherals[peripheral.identifier], shouldRecordEncounter(seenPeripheral.encounter) else { central.cancelPeripheralConnection(peripheral) return } @@ -283,15 +289,22 @@ extension CentralController: CBPeripheralDelegate { DLog("rssi should be present in \(currEncounter.encounter)") return } - - let dataToWrite = CentralWriteData(modelC: DeviceIdentifier.getModel(), - rssi: rssi, - txPower: currEncounter.encounter.txPower, - msg: tempId, - org: BluetraceConfig.OrgID, - v: BluetraceConfig.ProtocolVersion) + let encounterToBroadcast = EncounterBlob(modelC: DeviceIdentifier.getModel(), + rssi: rssi, + txPower: currEncounter.encounter.txPower, + modelP: nil, + msg: tempId) + do { + let jsonMsg = try JSONEncoder().encode(encounterToBroadcast) + let encryptedMsg = try Crypto.encrypt(dataToEncrypt: jsonMsg) + let dataToWrite = CentralWriteData(modelC: BluetraceConfig.DummyModel, + rssi: Double(BluetraceConfig.DummyRSSI), + txPower: Double(BluetraceConfig.DummyTxPower), + msg: encryptedMsg, + org: BluetraceConfig.OrgID, + v: BluetraceConfig.ProtocolVersion) let encodedData = try JSONEncoder().encode(dataToWrite) peripheral.writeValue(encodedData, for: characteristic, type: .withResponse) } catch { @@ -314,8 +327,7 @@ extension CentralController: CBPeripheralDelegate { if let scannedPeri = scannedPeripherals[peripheral.identifier], let characteristicValue = characteristic.value, - shouldRecordEncounter(scannedPeri.encounter) - { + shouldRecordEncounter(scannedPeri.encounter) { do { let peripheralCharData = try JSONDecoder().decode(PeripheralCharacteristicsData.self, from: characteristicValue) var encounterStruct = scannedPeri.encounter @@ -325,10 +337,12 @@ extension CentralController: CBPeripheralDelegate { encounterStruct.v = peripheralCharData.v encounterStruct.timestamp = Date() scannedPeripherals.updateValue((scannedPeri.peripheral, encounterStruct), forKey: peripheral.identifier) - encounterStruct.saveToCoreData() + // here the remote blob will be msg and modelp if v1, msg if v2 + // local blob will be rssi, txpower, modelc + try encounterStruct.saveRemotePeripheralToCoreData() DLog("Central recorded encounter with \(String(describing: scannedPeri.peripheral.name))") } catch { - DLog("Error: \(error). CharacteristicValue is \(characteristicValue)") + DLog("Error: \(error). CharacteristicValue is \(String(data: characteristicValue, encoding: .utf8) ?? "")") } } else { DLog("Error: scannedPeripherals[peripheral.identifier] is \(String(describing: scannedPeripherals[peripheral.identifier])), characteristic.value is \(String(describing: characteristic.value))") diff --git a/CovidSafe/ContactViewController.swift b/CovidSafe/ContactViewController.swift index e903452..cbb8005 100644 --- a/CovidSafe/ContactViewController.swift +++ b/CovidSafe/ContactViewController.swift @@ -19,12 +19,13 @@ final class ContactViewController: UIViewController { } func fetchContacts() { - guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { - return + guard let persistentContainer = + EncounterDB.shared.persistentContainer else { + return } - let managedContext = appDelegate.persistentContainer.viewContext + let managedContext = persistentContainer.viewContext let fetchRequest = Encounter.fetchRequestForRecords() - let sortByContactId = NSSortDescriptor(key: "msg", ascending: false) + let sortByContactId = NSSortDescriptor(key: "timestamp", ascending: false) fetchRequest.sortDescriptors = [sortByContactId] fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedContext, sectionNameKeyPath: nil, cacheName: nil) fetchedResultsController?.delegate = self @@ -44,11 +45,11 @@ final class ContactViewController: UIViewController { } var contactCounts: [String: Int] = [:] for encounter in encounters { - if encounter.msg != nil { - if contactCounts[encounter.msg!] == nil { - contactCounts[encounter.msg!] = 0 + if encounter.remoteBlob != nil { + if contactCounts[encounter.remoteBlob!] == nil { + contactCounts[encounter.remoteBlob!] = 0 } - contactCounts[encounter.msg!]! += 1 + contactCounts[encounter.remoteBlob!]! += 1 } } var contactTuples = contactCounts.map { ($0.key, $0.value) } diff --git a/CovidSafe/Crypto.swift b/CovidSafe/Crypto.swift new file mode 100644 index 0000000..59a8084 --- /dev/null +++ b/CovidSafe/Crypto.swift @@ -0,0 +1,196 @@ +// +// Crypto.swift +// CovidSafe +// +// Copyright © 2020 Australian Government. All rights reserved. +// + +import CommonCrypto +import Foundation +import Security + +enum SecurityError: Error { + case PublicKeyCopyError + case EncryptionFailedError(_ status: CCCryptorStatus) + case DigestFailedError + case EncryptionLengthError + case EncryptionKeyLengthError + case UnexpectedNilKeys +} + + +class Crypto { + private static var cachedExportPublicKey: Data? + private static var cachedAesKey: Data? + private static var cachedMacKey: Data? + private static var keyGenTime: Int64 = Int64.min + private static var counter: UInt16 = 0 + private static let NONCE_PADDING: Data = Data([UInt8](repeating: UInt8(0x0E), count: 14)) + private static let keyCacheQueue = DispatchQueue(label: "au.gov.health.covidsafe.crypto") + private static let KEY_GEN_TIME_DELTA: Int64 = 450 // 7.5 minutes + #if DEBUG + private static let publicKey = Data(base64Encoded: "BNrAcR+C6nkCpIYS9KWYt0Z5Sbleh7UybHmIT2T9YzuR9RzTh3YZcMBjr1K6smeDJW7sPCvMFJNWVPkk3exqjkQ=") + #else + private static let publicKey = Data(base64Encoded: "BDQbOM4lxeK6ed9br26qvcwsYgaUK9w3CozIHP1gOhR7+qwb7vrh0kSSUUtsayekard9EHElA9RNn/3dJW9hr7I=") + #endif + + /** + Get a series of secrets that can be decrypted by the server key. The returned data is: + 1. the ephemeral public key used for decrypting + 2. the AES encryption key + 3. the HMAC signature key + 4. the IV for AES encryption + - Parameter serverKey: X9.63 formatted P-256 public key for the server + - Throws: Errors from Security framework, or `SecurityError.PublicKeyCopyError` + if function failed to derive public key from the ephemeral private key + - Returns: + - publicKey: exported public P-256 key (compressed form) + - aesKey: ephemeral 16-byte AES-128 key + - macKey: ephemeral 16-byte key for HMAC + - iv: ephemeral 16-byte AES-128 IV + */ + private static func getEphemeralSecrets(_ serverKey: Data) throws -> (publicKey: Data, aesKey: Data, macKey: Data) { + // Server public key + var err: Unmanaged? + let serverKeyOptions: [CFString: Any] = [ + kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, + kSecAttrKeySizeInBits: 256, + kSecAttrKeyClass: kSecAttrKeyClassPublic, + ] + guard let serverPublicKey = SecKeyCreateWithData(serverKey as CFData, serverKeyOptions as CFDictionary, &err) else { + throw err!.takeRetainedValue() as Error + } + + // CREATE A LOCAL EPHEMERAL P-256 KEYPAIR + let ephereralPublicKeyAttributes: [CFString: Any] = [ + kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, + kSecAttrKeySizeInBits: 256, + ] + + guard let ephemeralPrivateKey = SecKeyCreateRandomKey(ephereralPublicKeyAttributes as CFDictionary, &err) else { + throw err!.takeRetainedValue() as Error + } + guard let ephemeralPublicKey = SecKeyCopyPublicKey(ephemeralPrivateKey) else { + throw SecurityError.PublicKeyCopyError + } + + // Exported ephemeral public key for sending/MACing later (compressed format, per ANSI X9.62) + let exportPublicKey = try ephemeralPublicKey.CopyCompressedECPublicKey() + + // COMPUTE SHARED SECRET + let params = [SecKeyKeyExchangeParameter.requestedSize.rawValue: 32] + guard let sharedSecret = SecKeyCopyKeyExchangeResult(ephemeralPrivateKey, + SecKeyAlgorithm.ecdhKeyExchangeStandard, + serverPublicKey, + params as CFDictionary, + &err) as Data? else { + throw err!.takeRetainedValue() as Error + } + + // KDF THE SHARED SECRET TO GET ENC KEY, MAC KEY + var keysHashCtx = CC_SHA256_CTX() + + // For keys we'll be using SHA256(sharedSecret) + var res: Int32 + var keysHashValue = Data(count: Int(CC_SHA256_DIGEST_LENGTH)) + CC_SHA256_Init(&keysHashCtx) + res = sharedSecret.withUnsafeBytes { + return CC_SHA256_Update(&keysHashCtx, $0.baseAddress, CC_LONG(sharedSecret.count)) + } + guard res == 1 else { throw SecurityError.DigestFailedError } + res = keysHashValue.withUnsafeMutableBytes { + return CC_SHA256_Final($0.bindMemory(to: UInt8.self).baseAddress, &keysHashCtx) + } + guard res == 1 else { throw SecurityError.DigestFailedError } + + // Form the keys + let aesKey = keysHashValue[.. Data { + // Get our ephemeral secrets that will de disposed at the end of this function + let (cachedExportPublicKey, cachedAESKey, cachedMacKey, nonce) = try keyCacheQueue.sync { () -> (Data?, Data?, Data?, Data) in + if Crypto.keyGenTime <= Int64(Date().timeIntervalSince1970) - KEY_GEN_TIME_DELTA || Crypto.counter >= 65535 { + (Crypto.cachedExportPublicKey, Crypto.cachedAesKey, Crypto.cachedMacKey) = try getEphemeralSecrets(serverPublicKey) + Crypto.keyGenTime = Int64(Date().timeIntervalSince1970) + Crypto.counter = 0 + } else { + Crypto.counter += 1 + } + let nonce = withUnsafeBytes(of: Crypto.counter.bigEndian) { Data($0) } + return (Crypto.cachedExportPublicKey, Crypto.cachedAesKey, Crypto.cachedMacKey, nonce) + } + guard let exportPublicKey = cachedExportPublicKey, let aesKey = cachedAESKey, let macKey = cachedMacKey else { + throw SecurityError.UnexpectedNilKeys + } + + + // AES ENCRYPT DATA + // IV = AES(ctr, iv=null), AES(plaintext, iv=IV) === AES(ctr_with_padding || plaintext, iv=null) + // Using the latter construction to reduce key expansions + + // Under PKCS#7 padding, we pad out to a complete blocksize but if the input is an exact multiple of blocksize, + // then we add an extra block on. So in both cases it's 16 bytes + (dataLen/16 + 1) * 16 bytes long + let outputLen = ((plaintext.count / kCCBlockSizeAES128) + 2) * kCCBlockSizeAES128 + + let nullIV = Data(count: 16) + var plaintextWithIV = Data(capacity: plaintext.count + 16) + plaintextWithIV.append(nonce) + plaintextWithIV.append(NONCE_PADDING) + plaintextWithIV.append(plaintext) + + var ciphertextWithIV = Data(count: outputLen) + var dataWrittenLen = 0 + let status = ciphertextWithIV.withUnsafeMutableBytes { ciphertextPtr in + plaintextWithIV.withUnsafeBytes { plaintextPtr in + nullIV.withUnsafeBytes { ivPtr in + aesKey.withUnsafeBytes { aesKeyPtr in + return CCCrypt(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionPKCS7Padding), + aesKeyPtr.baseAddress, kCCKeySizeAES128, ivPtr.baseAddress, + plaintextPtr.baseAddress, plaintextWithIV.count, + ciphertextPtr.baseAddress, outputLen, + &dataWrittenLen) + } + } + } + } + guard status == kCCSuccess else { + throw SecurityError.EncryptionFailedError(status) + } + guard outputLen == dataWrittenLen else { + throw SecurityError.EncryptionLengthError + } + + let ciphertext = ciphertextWithIV[16...] + + // HMAC + var macValue = Data(count: Int(CC_SHA256_DIGEST_LENGTH)) + var hmacContext = CCHmacContext() + macKey.withUnsafeBytes { CCHmacInit(&hmacContext, CCHmacAlgorithm(kCCHmacAlgSHA256), $0.baseAddress, macKey.count) } + exportPublicKey.withUnsafeBytes { CCHmacUpdate(&hmacContext, $0.baseAddress, exportPublicKey.count) } + nonce.withUnsafeBytes { CCHmacUpdate(&hmacContext, $0.baseAddress, nonce.count) } + ciphertext.withUnsafeBytes { CCHmacUpdate(&hmacContext, $0.baseAddress, ciphertext.count) } + macValue.withUnsafeMutableBytes { CCHmacFinal(&hmacContext, $0.bindMemory(to: UInt8.self).baseAddress) } + + // Build the final payload: ephemeral public key || nonce || encrypted data || HMAC + var finalData = Data(capacity: exportPublicKey.count + ciphertext.count + 18) + finalData.append(exportPublicKey) + finalData.append(nonce) + finalData.append(ciphertext) + finalData.append(macValue[..<16]) + + return finalData + } + + public static func encrypt(dataToEncrypt: Data) throws -> String { + guard let publicKey = publicKey else { + throw SecurityError.PublicKeyCopyError + } + let encryptedData = try buildSecretData(publicKey, dataToEncrypt) + return encryptedData.base64EncodedString() + } +} diff --git a/CovidSafe/Debug.storyboard b/CovidSafe/Debug.storyboard index 56a0809..4ce8ec6 100644 --- a/CovidSafe/Debug.storyboard +++ b/CovidSafe/Debug.storyboard @@ -117,12 +117,12 @@ - diff --git a/CovidSafe/Encounter+CoreDataProperties.swift b/CovidSafe/Encounter+CoreDataProperties.swift index 89760fd..6702644 100644 --- a/CovidSafe/Encounter+CoreDataProperties.swift +++ b/CovidSafe/Encounter+CoreDataProperties.swift @@ -15,39 +15,18 @@ extension Encounter { enum CodingKeys: String, CodingKey { case timestamp - case msg - case modelC - case modelP - case rssi - case txPower case org case v + case localBlob + case remoteBlob } - + @nonobjc public class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "Encounter") } - + @nonobjc public class func fetchRequestForRecords() -> NSFetchRequest { let fetchRequest = NSFetchRequest(entityName: "Encounter") - let predicateString = Encounter.Event - .allCases - .map { "msg != '\($0.rawValue)'" } - .joined(separator: " and ") - - fetchRequest.predicate = NSPredicate(format: predicateString) - fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)] - return fetchRequest - } - - @nonobjc public class func fetchRequestForEvents() -> NSFetchRequest { - let fetchRequest = NSFetchRequest(entityName: "Encounter") - let predicateString = Encounter.Event - .allCases - .map { "msg = '\($0.rawValue)'" } - .joined(separator: " or ") - - fetchRequest.predicate = NSPredicate(format: predicateString) fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)] return fetchRequest } @@ -70,39 +49,33 @@ extension Encounter { } @NSManaged public var timestamp: Date? - @NSManaged public var msg: String? - @NSManaged public var modelC: String? - @NSManaged public var modelP: String? - @NSManaged public var rssi: NSNumber? - @NSManaged public var txPower: NSNumber? @NSManaged public var org: String? @NSManaged public var v: NSNumber? + @NSManaged public var localBlob: String? + @NSManaged public var remoteBlob: String? - func set(encounterStruct: EncounterRecord) { + func set(encounterStruct: EncounterRecord, remoteBlob: String, localBlob: String) { setValue(encounterStruct.timestamp, forKeyPath: "timestamp") - setValue(encounterStruct.msg, forKeyPath: "msg") - setValue(encounterStruct.modelC, forKeyPath: "modelC") - setValue(encounterStruct.modelP, forKeyPath: "modelP") - setValue(encounterStruct.rssi, forKeyPath: "rssi") - setValue(encounterStruct.txPower, forKeyPath: "txPower") setValue(encounterStruct.org, forKeyPath: "org") - setValue(encounterStruct.v, forKeyPath: "v") + // when we save locally we've already converted v1 messages to encrypted v2 spec, so we save the record as a v2 record + if (encounterStruct.v == 1) { + setValue(BluetraceConfig.ProtocolVersion, forKeyPath: "v") + } else { + setValue(encounterStruct.v, forKeyPath: "v") + } + setValue(remoteBlob, forKey: "remoteBlob") + setValue(localBlob, forKey: "localBlob") + } // MARK: - Encodable public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(Int(timestamp!.timeIntervalSince1970), forKey: .timestamp) - try container.encode(msg, forKey: .msg) - - if let modelC = modelC, let modelP = modelP { - try container.encode(modelC, forKey: .modelC) - try container.encode(modelP, forKey: .modelP) - try container.encode(rssi?.doubleValue, forKey: .rssi) - try container.encode(txPower?.doubleValue, forKey: .txPower) - try container.encode(org, forKey: .org) - try container.encode(v?.intValue, forKey: .v) - } + try container.encode(localBlob, forKey: .localBlob) + try container.encode(remoteBlob, forKey: .remoteBlob) + try container.encode(org, forKey: .org) + try container.encode(v?.intValue, forKey: .v) } } diff --git a/CovidSafe/Encounter+EncounterRecord.swift b/CovidSafe/Encounter+EncounterRecord.swift index 5d9f379..02b7c62 100644 --- a/CovidSafe/Encounter+EncounterRecord.swift +++ b/CovidSafe/Encounter+EncounterRecord.swift @@ -12,9 +12,8 @@ extension EncounterRecord { func saveToCoreData() { DispatchQueue.main.async { - guard let appDelegate = - UIApplication.shared.delegate as? AppDelegate else { - return + guard let persistentContainer = EncounterDB.shared.persistentContainer else { + return } let managedContext = appDelegate.persistentContainer.viewContext let entity = NSEntityDescription.entity(forEntityName: "Encounter", in: managedContext)! diff --git a/CovidSafe/Encounter+Util.swift b/CovidSafe/Encounter+Util.swift index eaa9971..2ed195f 100644 --- a/CovidSafe/Encounter+Util.swift +++ b/CovidSafe/Encounter+Util.swift @@ -10,10 +10,7 @@ import CoreData extension Encounter { @nonobjc public class func deleteAll() { - guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { - return - } - appDelegate.persistentContainer.performBackgroundTask { (backgroundContext) in + EncounterDB.shared.persistentContainer?.performBackgroundTask { (backgroundContext) in let oldFetchRequest = NSFetchRequest(entityName: "Encounter") let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: oldFetchRequest) do { diff --git a/CovidSafe/EncounterDB+migration.swift b/CovidSafe/EncounterDB+migration.swift new file mode 100644 index 0000000..c9e3fee --- /dev/null +++ b/CovidSafe/EncounterDB+migration.swift @@ -0,0 +1,110 @@ +// +// EncounterDB+migration.swift +// CovidSafe +// +// Copyright © 2020 Australian Government. All rights reserved. +// + +import CoreData + +protocol EncounterDBMigrationProgress { + func migrationBegun() + func migrationComplete() + func migrationFailed(error: Error) +} + +enum MigrationError: Error { + case unableToRetrieveSourceModel +} + +extension EncounterDB { + + func store(_ storeURL:URL, isCompatibleWithModel model:NSManagedObjectModel) -> Bool { + + do { + let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeURL, options: nil) + if model.isConfiguration(withName: nil, compatibleWithStoreMetadata: metadata) { + return true + } + } catch { + DLog("ERROR getting metadata from \(storeURL) \(error)") + } + DLog("The store is NOT compatible with the current version of the model") + return false + } + + func migrateStoreIfNecessary (storeURL:URL, destinationModel:NSManagedObjectModel) { + + guard FileManager.default.fileExists(atPath: storeURL.path) else { + return + } + + guard store(storeURL, isCompatibleWithModel: destinationModel) == false else { + return + } + registerBackgroundTask() + do { + self.migrationDelegate?.migrationBegun() + let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeURL, options: nil) + if let sourceModel = NSManagedObjectModel.mergedModel(from: [Bundle.main], forStoreMetadata: metadata) { + //do the migration, on the background + DispatchQueue.global(qos: .background).async { + do { + DLog("STARTING MIGRATION") + try self.migrateStore(store: storeURL, sourceModel: sourceModel, destinationModel: destinationModel) + DispatchQueue.main.async { + self.endBackgroundTask() + self.migrationDelegate?.migrationComplete() + } + } catch { + DispatchQueue.main.async { + self.endBackgroundTask() + self.migrationDelegate?.migrationFailed(error: error) + } + DLog("Failed to migrate") + } + } + } else { + self.endBackgroundTask() + self.migrationDelegate?.migrationFailed(error: MigrationError.unableToRetrieveSourceModel) + } + } catch { + endBackgroundTask() + self.migrationDelegate?.migrationFailed(error: error) + print("FAILED to get metadata \(error)") + } + } + + func migrateStore(store:URL, sourceModel:NSManagedObjectModel, destinationModel:NSManagedObjectModel) throws { + let tempdir = store.deletingLastPathComponent() + let tempStore = tempdir.appendingPathComponent("temp.sqlite", isDirectory: false) + let mappingModel = NSMappingModel(from: nil, forSourceModel: sourceModel, destinationModel: destinationModel) + let migrationManager = NSMigrationManager(sourceModel: sourceModel, destinationModel: destinationModel) + let options = [NSSQLitePragmasOption: ["journal_mode":"DELETE"]] + do { + try migrationManager.migrateStore(from: store, + sourceType: NSSQLiteStoreType, + options: options, + with: mappingModel, + toDestinationURL: tempStore, + destinationType: NSSQLiteStoreType, + destinationOptions: nil) + let psc = NSPersistentStoreCoordinator(managedObjectModel: sourceModel) + try psc.replacePersistentStore(at: store, + destinationOptions: nil, + withPersistentStoreFrom: tempStore, + sourceOptions: nil, + ofType: NSSQLiteStoreType) + try psc.destroyPersistentStore(at: tempStore, ofType: NSSQLiteStoreType, options: [NSSQLitePragmasOption: ["secure_delete": true]]) + try FileManager.default.removeItem(at: tempStore) + + DLog("SUCCESSFULLY MIGRATED \(store) to the Current Model") + } catch { + DLog("FAILED MIGRATION: \(error)") + if FileManager.default.fileExists(atPath: tempStore.path) { + try FileManager.default.removeItem(at: tempStore) + } + throw error + } + } +} diff --git a/CovidSafe/EncounterDB.swift b/CovidSafe/EncounterDB.swift new file mode 100644 index 0000000..9d5b3d6 --- /dev/null +++ b/CovidSafe/EncounterDB.swift @@ -0,0 +1,75 @@ +// +// EncounterDB.swift +// CovidSafe +// +// Copyright © 2020 Australian Government. All rights reserved. +// + +import UIKit +import CoreData + +class EncounterDB { + static let shared = EncounterDB() + private let modelName = "tracer" + private var localStoreUrl: URL? + private var _persistentContainer: CovidPersistentContainer? + var migrationDelegate: EncounterDBMigrationProgress? + var backgroundTask: UIBackgroundTaskIdentifier = .invalid + + + private lazy var managedObjectModel: NSManagedObjectModel = { + guard let modelURL = Bundle.main.url(forResource: self.modelName, withExtension: "momd") else { + fatalError("Unable to Find Data Model") + } + + guard let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL) else { + fatalError("Unable to Load Data Model") + } + + return managedObjectModel + }() + + public var persistentContainer: CovidPersistentContainer? { + get { + if let container = _persistentContainer { + return container + } + //check if we need to migrate store first + if let localStoreUrl = self.localStoreUrl { + if FileManager.default.fileExists(atPath: localStoreUrl.path) && + store(localStoreUrl, isCompatibleWithModel: self.managedObjectModel) == false { + return nil // Don't return a store if it's not compatible with the model + } + } + + let container = CovidPersistentContainer(name: self.modelName) + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error as NSError? { + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + _persistentContainer = container + return _persistentContainer + } + } + + func registerBackgroundTask() { + backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in + self?.endBackgroundTask() + } + assert(backgroundTask != .invalid) + } + + func endBackgroundTask() { + if(backgroundTask != .invalid){ + UIApplication.shared.endBackgroundTask(backgroundTask) + backgroundTask = .invalid + } + } + + func setup(migrationDelegate: EncounterDBMigrationProgress?) { + self.migrationDelegate = migrationDelegate + self.localStoreUrl = CovidPersistentContainer.defaultDirectoryURL().appendingPathComponent(self.modelName, isDirectory: true).appendingPathExtension("sqlite") + migrateStoreIfNecessary(storeURL: self.localStoreUrl!, destinationModel: self.managedObjectModel) + } +} diff --git a/CovidSafe/EncounterMessageManager.swift b/CovidSafe/EncounterMessageManager.swift index f9d6357..ec6b9be 100644 --- a/CovidSafe/EncounterMessageManager.swift +++ b/CovidSafe/EncounterMessageManager.swift @@ -12,7 +12,19 @@ class EncounterMessageManager { } var advertisedPayload: Data? { - return UserDefaults.standard.data(forKey: userDefaultsAdvtKey) + do { + let broadcastPayload = EncounterBlob(modelC: nil, + rssi: nil, + txPower: nil, + modelP: DeviceIdentifier.getModel(), + msg: tempId) + let jsonMsg = try JSONEncoder().encode(broadcastPayload) + let encryptedJsonMsg = try Crypto.encrypt(dataToEncrypt: jsonMsg) + let peripheralCharStruct = PeripheralCharacteristicsData(modelP: BluetraceConfig.DummyModel, msg: encryptedJsonMsg, org: BluetraceConfig.OrgID, v: BluetraceConfig.ProtocolVersion) + return try JSONEncoder().encode(peripheralCharStruct) + } catch { + return nil + } } // This variable stores the expiry date of the broadcast message. At the same time, we will use this expiry date as the expiry date for the encryted advertisement payload @@ -29,7 +41,6 @@ class EncounterMessageManager { DLog("No response, Error: \(String(describing: error))") return } - _ = self.setAdvertisementPayloadIntoUserDefaults(response) UserDefaults.standard.set(response.tempId, forKey: self.userDefaultsTempIdKey) } } @@ -72,8 +83,10 @@ class EncounterMessageManager { onComplete(nil) return } + UserDefaults.standard.set(response.tempId, forKey: self.userDefaultsTempIdKey) + UserDefaults.standard.set(response.expiry, forKey: self.userDefaultsAdvtExpiryKey) - if let newPayload = self.setAdvertisementPayloadIntoUserDefaults(response) { + if let newPayload = self.advertisedPayload { onComplete(newPayload) } onComplete(nil) @@ -122,18 +135,4 @@ class EncounterMessageManager { onComplete?(nil, (tempId, date)) } } - - private func setAdvertisementPayloadIntoUserDefaults(_ response: (tempId: String, expiry: Date)) -> Data? { - let peripheralCharStruct = PeripheralCharacteristicsData(modelP: DeviceIdentifier.getModel(), msg: response.tempId, org: BluetraceConfig.OrgID, v: BluetraceConfig.ProtocolVersion) - do { - let encodedPeriCharStruct = try JSONEncoder().encode(peripheralCharStruct) - UserDefaults.standard.set(encodedPeriCharStruct, forKey: self.userDefaultsAdvtKey) - UserDefaults.standard.set(response.expiry, forKey: self.userDefaultsAdvtExpiryKey) - return encodedPeriCharStruct - } catch { - DLog("Error: \(error)") - } - - return nil - } } diff --git a/CovidSafe/EncounterRecord.swift b/CovidSafe/EncounterRecord.swift index 962e43c..4c0982f 100644 --- a/CovidSafe/EncounterRecord.swift +++ b/CovidSafe/EncounterRecord.swift @@ -7,6 +7,14 @@ import Foundation +struct EncounterBlob: Encodable { + var modelC: String? + var rssi: Double? + var txPower: Double? + var modelP: String? + var msg: String? +} + struct EncounterRecord: Encodable { var timestamp: Date? var msg: String? diff --git a/CovidSafe/EncounterV1toV2.xcmappingmodel/xcmapping.xml b/CovidSafe/EncounterV1toV2.xcmappingmodel/xcmapping.xml new file mode 100644 index 0000000..f8c9a05 --- /dev/null +++ b/CovidSafe/EncounterV1toV2.xcmappingmodel/xcmapping.xml @@ -0,0 +1,91 @@ + + + + + + 134481920 + 3F491C09-5FB7-47A3-B47D-13E2F242CE6B + 108 + + + + NSPersistenceFrameworkVersion + 977 + NSStoreModelVersionHashes + + XDDevAttributeMapping + + 0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc= + + XDDevEntityMapping + + qeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI= + + XDDevMappingModel + + EqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ= + + XDDevPropertyMapping + + XN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA= + + XDDevRelationshipMapping + + akYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs= + + + NSStoreModelVersionHashesDigest + +Hmc2uYZK6og+Pvx5GUJ7oW75UG4V/ksQanTjfTKUnxyGWJRMtB5tIRgVwGsrd7lz/QR57++wbvWsr6nxwyS0A== + NSStoreModelVersionHashesVersion + 3 + NSStoreModelVersionIdentifiers + + + + + + + + + localBlob + + + + org + + + + COVIDSafe.EncounterV2Mapping + Encounter + Undefined + 1 + Encounter + 1 + + + + + + remoteBlob + + + + timestamp + + + + CovidSafe/tracer.xcdatamodeld/Model.xcdatamodel + YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0 +cxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxEBKAALAAwAGwA3ADgAOQBBAEIAXQBeAF8AZQBmAHIAiACJAIoAiwCMAI0AjgCPAJAAkQCqAK0AtAC6AMkA2ADbAOoA+QD8AFwBDAEbAR8BIwEyATgBOQFBAVABWQFvAXABcQFyAXMBdAF1AXYBdwF4AY0BjgGWAZcBmAGkAbgBuQG6AbsBvAG9Ab4BvwHAAc8B3gHtAfECAAIPAhACHwIuAj0CSQJbAlwCXQJeAl8CYAJhAmICcQKAAo8CngKfAq4CvQLMAtQC6QLqAvIC/gMSAyEDMAM/A0MDUgNhA3ADfwOOA5oDrAO7A8oD2QPoA/cEBgQVBCoEKwQzBD8EUwRiBHEEgASEBJMEogSxBMAEzwTbBO0E/AT9BQwFGwUqBSsFOgVJBVgFbQVuBXYFggWWBaUFtAXDBccF1gXlBfQGAwYSBh4GMAY/Bk4GXQZsBnsGigaZBq4Grwa3BsMG1wbmBvUHBAcIBxcHJgc1B0QHUwdfB3EHgAePB54HrQe8B8sH2gfvB/AH+Af5CAUIFwgmCDUIRAhICFcIZgh1CIQIkAiWCJcIpgivCLAItAi8CNEI0gjaCOYI+gkJCRgJJwkrCToJSQlYCWcJdgmCCZQJowmyCcEJ0AnfCe4J/QoSChMKGwonCjsKSgpZCmgKbAp7CooKmQqoCrcKwwrVCuQK5Qr0CwMLEgsTCyILMQtAC1ULVgteC2oLfguNC5wLqwuvC74LzQvcC+sL+gwGDBgMJww2DEUMVAxVDGQMcwyCDIMMhgyPDJMMlwybDKMMpgyqDKtVJG51bGzXAA0ADgAPABAAEQASABMAFAAVABYAFwAYABcAGl8QD194ZF9yb290UGFja2FnZVYkY2xhc3NcX3hkX2NvbW1lbnRzXxAQX3hkX21vZGVsTWFuYWdlcl8QFV9jb25maWd1cmF0aW9uc0J5TmFtZV1feGRfbW9kZWxOYW1lXxAXX21vZGVsVmVyc2lvbklkZW50aWZpZXKAAoEBJ4EBJIAAgQElgACBASbeABwAHQAeAB8AIAAhACIADgAjACQAJQAmACcAKAApACoAKwAJACkAFwAvADAAMQAyADMAKQApABdfEBxYREJ1Y2tldEZvckNsYXNzZXN3YXNFbmNvZGVkXxAaWERCdWNrZXRGb3JQYWNrYWdlc3N0b3JhZ2VfEBxYREJ1Y2tldEZvckludGVyZmFjZXNzdG9yYWdlXxAPX3hkX293bmluZ01vZGVsXxAdWERCdWNrZXRGb3JQYWNrYWdlc3dhc0VuY29kZWRWX293bmVyXxAbWERCdWNrZXRGb3JEYXRhVHlwZXNzdG9yYWdlW192aXNpYmlsaXR5XxAZWERCdWNrZXRGb3JDbGFzc2Vzc3RvcmFnZVVfbmFtZV8QH1hEQnVja2V0Rm9ySW50ZXJmYWNlc3dhc0VuY29kZWRfEB5YREJ1Y2tldEZvckRhdGFUeXBlc3dhc0VuY29kZWRfEBBfdW5pcXVlRWxlbWVudElEgASBASKBASCAAYAEgACBASGBASMQAIAFgAOABIAEgABQU1lFU9MAOgA7AA4APAA+AEBXTlMua2V5c1pOUy5vYmplY3RzoQA9gAahAD+AB4AlWUVuY291bnRlct8QEABDAEQARQBGACEARwBIACMASQBKAA4AJQBLAEwAKABNAE4ATwApACkAFABTAFQAMQApAE4AVwA9AE4AWgBbAFxfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2VfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNkdXBsaWNhdGVzXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3N0b3JhZ2VbX2lzQWJzdHJhY3SACYAsgASABIACgAqBAR2ABIAJgQEfgAaACYEBHoAICBLnsusOV29yZGVyZWTTADoAOwAOAGAAYgBAoQBhgAuhAGOADIAlXlhEX1BTdGVyZW90eXBl2QAhACUAZwAOACgAaAAjAE0AaQA/AGEATgBtABcAKQAxAFwAcV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAHgAuACYArgACABAiADdMAOgA7AA4AcwB9AECpAHQAdQB2AHcAeAB5AHoAewB8gA6AD4AQgBGAEoATgBSAFYAWqQB+AH8AgACBAIIAgwCEAIUAhoAXgBuAHIAegB+AIYAjgCaAKoAlXxATWERQTUNvbXBvdW5kSW5kZXhlc18QEFhEX1BTS19lbGVtZW50SURfEBlYRFBNVW5pcXVlbmVzc0NvbnN0cmFpbnRzXxAaWERfUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBlYRF9QU0tfZmV0Y2hSZXF1ZXN0c0FycmF5XxARWERfUFNLX2lzQWJzdHJhY3RfEA9YRF9QU0tfdXNlckluZm9fEBNYRF9QU0tfY2xhc3NNYXBwaW5nXxAWWERfUFNLX2VudGl0eUNsYXNzTmFtZd8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAJ0AFwBjAFwAXABcADEAXACkAHQAXABcABcAXFVfdHlwZVhfZGVmYXVsdFxfYXNzb2NpYXRpb25bX2lzUmVhZE9ubHlZX2lzU3RhdGljWV9pc1VuaXF1ZVpfaXNEZXJpdmVkWl9pc09yZGVyZWRcX2lzQ29tcG9zaXRlV19pc0xlYWaAAIAYgACADAgICAiAGoAOCAiAAAjSADsADgCrAKyggBnSAK4ArwCwALFaJGNsYXNzbmFtZVgkY2xhc3Nlc15OU011dGFibGVBcnJheaMAsACyALNXTlNBcnJheVhOU09iamVjdNIArgCvALUAtl8QEFhEVU1MUHJvcGVydHlJbXCkALcAuAC5ALNfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwAXABcAYwBcAFwAXAAxAFwApAB1AFwAXAAXAFyAAIAAgACADAgICAiAGoAPCAiAAAjfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwDLABcAYwBcAFwAXAAxAFwApAB2AFwAXAAXAFyAAIAdgACADAgICAiAGoAQCAiAAAjSADsADgDZAKyggBnfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwAXABcAYwBcAFwAXAAxAFwApAB3AFwAXAAXAFyAAIAAgACADAgICAiAGoARCAiAAAjfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwDsABcAYwBcAFwAXAAxAFwApAB4AFwAXAAXAFyAAIAggACADAgICAiAGoASCAiAAAjSADsADgD6AKyggBnfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwD+ABcAYwBcAFwAXAAxAFwApAB5AFwAXAAXAFyAAIAigACADAgICAiAGoATCAiAAAgI3xAPAJIAkwCUACEAlQCWAJcAIwCYAA4AJQCZAJoAKACbABcBDgAXAGMAXABcAFwAMQBcAKQAegBcAFwAFwBcgACAJIAAgAwICAgIgBqAFAgIgAAI0wA6ADsADgEcAR0AQKCggCXSAK4ArwEgASFfEBNOU011dGFibGVEaWN0aW9uYXJ5owEgASIAs1xOU0RpY3Rpb25hcnnfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwElABcAYwBcAFwAXAAxAFwApAB7AFwAXAAXAFyAAIAngACADAgICAiAGoAVCAiAAAjWACUADgAoAE0AIQAjATMBNAAXAFwAFwAxgCiAKYAACIAAXxAUWERHZW5lcmljUmVjb3JkQ2xhc3PSAK4ArwE6ATtdWERVTUxDbGFzc0ltcKYBPAE9AT4BPwFAALNdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwA9ABcAYwBcAFwAXAAxAFwApAB8AFwAXAAXAFyAAIAGgACADAgICAiAGoAWCAiAAAjSAK4ArwFRAVJfEBJYRFVNTFN0ZXJlb3R5cGVJbXCnAVMBVAFVAVYBVwFYALNfEBJYRFVNTFN0ZXJlb3R5cGVJbXBdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADoAOwAOAVoBZABAqQFbAVwBXQFeAV8BYAFhAWIBY4AtgC6AL4AwgDGAMoAzgDSANakBZQFmAWcBaAFpAWoBawFsAW2ANoBhgHiAkYCogL+A1YDsgQEFgCVWbW9kZWxQU29yZ1d0eFBvd2VyVm1vZGVsQ1Ryc3NpXxAPZmV0Y2hlZFByb3BlcnR5U21zZ1F2WXRpbWVzdGFtcN8QEgCSAJMAlAF5ACEAlgCXAXoAIwCVAXsAmAAOACUAmQCaACgAmwAXABcAFwApAD8AXABcAYMAMQBcAE4AXAGHAVsAXABcAYsAXF8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIA4CIAJCIBggC0ICIA3CBK6xAww0wA6ADsADgGPAZIAQKIBkAGRgDmAOqIBkwGUgDuAT4AlXxASWERfUFByb3BTdGVyZW90eXBlXxASWERfUEF0dF9TdGVyZW90eXBl2QAhACUBmQAOACgBmgAjAE0BmwFlAZAATgBtABcAKQAxAFwBo18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYA2gDmACYArgACABAiAPNMAOgA7AA4BpQGuAECoAaYBpwGoAakBqgGrAawBrYA9gD6AP4BAgEGAQoBDgESoAa8BsAGxAbIBswG0AbUBtoBFgEaAR4BJgEqATIBNgE6AJV8QG1hEX1BQU0tfaXNTdG9yZWRJblRydXRoRmlsZV8QG1hEX1BQU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QEFhEX1BQU0tfdXNlckluZm9fEBFYRF9QUFN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0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgJGAOoAJgCuAAIAECICg0wA6ADsADgYfBicAQKcCSwJMAk0CTgJPAlACUYBRgFKAU4BUgFWAVoBXpwYoBikGKgYrBiwGLQYugKGAooCjgKSApYCmgKeAJd8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwV0AFwAXABcADEAXACkAksAXABcABcAXIAAgACAAICfCAgICIAagFEICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAP4AFwV0AFwAXABcADEAXACkAkwAXABcABcAXIAAgCKAAICfCAgICIAagFIICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwV0AFwAXABcADEAXACkAk0AXABcABcAXIAAgACAAICfCAgICIAagFMICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXApEAFwV0AFwAXABcADEAXACkAk4AXABcABcAXIAAgFyAAICfCAgICIAagFQICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwV0AFwAXABcADEAXACkAk8AXABcABcAXIAAgACAAICfCAgICIAagFUICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwV0AFwAXABcADEAXACkAlAAXABcABcAXIAAgACAAICfCAgICIAagFYICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwV0AFwAXABcADEAXACkAlEAXABcABcAXIAAgACAAICfCAgICIAagFcICIAACN8QEgCSAJMAlAaaACEAlgCXBpsAIwCVBpwAmAAOACUAmQCaACgAmwAXABcAFwApAD8AXABcBqQAMQBcAE4AXAGHAV8AXABcBqwAXF8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3J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JbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNIAOwAODKQArKCAGdMAOgA7AA4MpwyoAECgoIAlUNIArgCvDKwMrVlYRFBNTW9kZWyjDKwMrgCzV1hETW9kZWwACAAZACIALAAxADoAPwBRAFYAWwBdArECtwLUAuYC7QL6Aw0DJQMzA00DTwNSA1UDVwNaA1wDXwOYA7cD1APzBAUEJQQsBEoEVgRyBHgEmgS7BM4E0ATTBNYE2ATaBNwE3wTiBOQE5gToBOoE7ATuBO8E8wUABQgFEwUWBRgFGwUdBR8FKQVsBZAFtAXXBf4GHgZFBmwGjAawBtQG4AbiBuQG5gboBuoG7AbvBvEG8wb2BvgG+gb9Bv8HAAcFBw0HGgcdBx8HIgckByYHNQdaB34HpQfJB8sHzQfPB9EH0wfVB9YH2AflB/gH+gf8B/4IAAgCCAQIBggICAoIHQgfCCEIIwglCCcIKQgrCC0ILwgxCEcIWgh2CJMIrwjDCNUI6wkECUMJSQlSCV8Jawl1CX8JigmVCaIJqgmsCa4JsAmyCbMJtAm1CbYJuAm6CbsJvAm+Cb8JyAnJCcsJ1AnfCegJ9wn+CgYKDwoYCisKNApHCl4KcAqvCrEKswq1CrcKuAq5CroKuwq9Cr8KwArBCsMKxAsDCwULBwsJCwsLDAsNCw4LDwsRCxMLFAsVCxcLGAshCyILJAtjC2ULZwtpC2sLbAttC24LbwtxC3MLdAt1C3cLeAu3C7kLuwu9C78LwAvBC8ILwwvFC8cLyAvJC8sLzAvVC9YL2AwXDBkMGwwdDB8MIAwhDCIMIwwlDCcMKAwpDCsMLAwtDGwMbgxwDHIMdAx1DHYMdwx4DHoMfAx9DH4MgAyBDI4MjwyQDJIMmwyxDLgMxQ0EDQYNCA0KDQwNDQ0ODQ8NEA0SDRQNFQ0WDRgNGQ0yDTQNNg04DTkNOw1SDVsNaQ12DYQNmQ2tDcQN1g4VDhcOGQ4bDh0OHg4fDiAOIQ4jDiUOJg4nDikOKg4zDkgOVw5sDnoOjw6jDroOzA7ZDuwO7g7wDvIO9A72DvgO+g78Dv4PEQ8TDxUPFw8ZDxsPHQ8fDyEPJA8mDy0PMQ85D0APRQ9XD1sPXQ9nD7IP1Q/1EBUQFxAZEBsQHRAfECAQIRAjECQQJhAnECkQKxAsEC0QLxAwEDUQQhBHEEkQSxBQEFIQVBBWEGsQgBClEMkQ8BEUERYRGBEaERwRHhEgESERIxEwEUERQxFFEUcRSRFLEU0RTxFREWIRZBFmEWgRahFsEW4RcBFyEXQRkhGwEcMR1xHsEgkSHRIzEnISdBJ2EngSehJ7EnwSfRJ+EoASghKDEoQShhKHEsYSyBLKEswSzhLPEtAS0RLSEtQS1hLXEtgS2hLbExoTHBMeEyATIhMjEyQTJRMmEygTKhMrEywTLhMvEzwTPRM+E0ATfxOBE4MThROHE4gTiROKE4sTjROPE5ATkROTE5QT0xPVE9cT2RPbE9wT3RPeE98T4RPjE+QT5RPnE+gT6RQoFCoULBQuFDAUMRQyFDMUNBQ2FDgUORQ6FDwUPRR8FH4UgBSCFIQUhRSGFIcUiBSKFIwUjRSOFJAUkRTQFNIU1BTWFNgU2RTaFNsU3BTeFOAU4RTiFOQU5RUKFS4VVRV5FXsVfRV/FYEVgxWFFYYViBWVFaQVphWoFaoVrBWuFbAVshXBFcMVxRXHFckVyxXNFc8V0RXxFhwWNhZPFmkWiRasFusW7RbvFvEW8xb0FvUW9hb3FvkW+xb8Fv0W/xcAFz8XQRdDF0UXRxdIF0kXShdLF00XTxdQF1EXUxdUF5MXlReXF5kXmxecF50XnhefF6EXoxekF6UXpxeoF+cX6RfrF+0X7xfwF/EX8hfzF/UX9xf4F/kX+xf8F/8YPhhAGEIYRBhGGEcYSBhJGEoYTBhOGE8YUBhSGFMYkhiUGJYYmBiaGJsYnBidGJ4YoBiiGKMYpBimGKcY5hjoGOoY7BjuGO8Y8BjxGPIY9Bj2GPcY+Bj6GPsZBBkSGR8ZLRk6GU0ZZBl2GcEZ5BoEGiQaJhooGioaLBouGi8aMBoyGjMaNRo2GjgaOho7GjwaPho/GkQaURpWGlgaWhpfGmEaYxplGooarhrVGvka+xr9Gv8bARsDGwUbBhsIGxUbJhsoGyobLBsuGzAbMhs0GzYbRxtJG0sbTRtPG1EbUxtVG1cbWRuYG5obnBueG6AboRuiG6MbpBumG6gbqRuqG6wbrRvsG+4b8BvyG/Qb9Rv2G/cb+Bv6G/wb/Rv+HAAcARxAHEIcRBxGHEgcSRxKHEscTBxOHFAcURxSHFQcVRxiHGMcZBxmHKUcpxypHKscrRyuHK8csByxHLMctRy2HLccuRy6HPkc+xz9HP8dAR0CHQMdBB0FHQcdCR0KHQsdDR0OHU0dTx1RHVMdVR1WHVcdWB1ZHVsdXR1eHV8dYR1iHaEdox2lHacdqR2qHasdrB2tHa8dsR2yHbMdtR22HfUd9x35Hfsd/R3+Hf8eAB4BHgMeBR4GHgceCR4KHi8eUx56Hp4eoB6iHqQeph6oHqoeqx6tHroeyR7LHs0ezx7RHtMe1R7XHuYe6B7qHuwe7h7wHvIe9B72HzUfNx85HzsfPR8+Hz8fQB9BH0MfRR9GH0cfSR9KH4kfix+NH48fkR+SH5MflB+VH5cfmR+aH5sfnR+eH90f3x/hH+Mf5R/mH+cf6B/pH+sf7R/uH+8f8R/yIDEgMyA1IDcgOSA6IDsgPCA9ID8gQSBCIEMgRSBGIIUghyCJIIsgjSCOII8gkCCRIJMglSCWIJcgmSCaINkg2yDdIN8g4SDiIOMg5CDlIOcg6SDqIOsg7SDuIS0hLyExITMhNSE2ITchOCE5ITshPSE+IT8hQSFCIY0hsCHQIfAh8iH0IfYh+CH6Ifsh/CH+If8iASICIgQiBiIHIggiCiILIhQiISImIigiKiIvIjEiMyI1IloifiKlIskiyyLNIs8i0SLTItUi1iLYIuUi9iL4Ivoi/CL+IwAjAiMEIwYjFyMZIxsjHSMfIyEjIyMlIycjKSNoI2ojbCNuI3AjcSNyI3MjdCN2I3gjeSN6I3wjfSO8I74jwCPCI8QjxSPGI8cjyCPKI8wjzSPOI9Aj0SQQJBIkFCQWJBgkGSQaJBskHCQeJCAkISQiJCQkJSQyJDMkNCQ2JHUkdyR5JHskfSR+JH8kgCSBJIMkhSSGJIckiSSKJMkkyyTNJM8k0STSJNMk1CTVJNck2STaJNsk3STeJR0lHyUhJSMlJSUmJSclKCUpJSslLSUuJS8lMSUyJXElcyV1JXcleSV6JXslfCV9JX8lgSWCJYMlhSWGJcUlxyXJJcslzSXOJc8l0CXRJdMl1SXWJdcl2SXaJf8mIyZKJm4mcCZyJnQmdiZ4JnomeyZ9JoommSabJp0mnyahJqMmpSanJrYmuCa6JrwmvibAJsImxCbGJwUnBycJJwsnDScOJw8nECcRJxMnFScWJxcnGScaJx4nXSdfJ2EnYydlJ2YnZydoJ2knaydtJ24nbydxJ3InsSezJ7U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Jo0mkSaZJp0nmSehJ6knsSe5J70nwSfFJ8kn0SfZJ90n4SfpJ+0oISglKCkoMSktKTUpPSlFKU0pUSlVKVkpXSllKW0pcSl1KX0pgSp9KoUqjSqVKp0qoSqlKqkqrSq1Kr0qwSrFKs0q0SvNK9Ur3SvlK+0r8Sv1K/kr/SwFLA0sESwVLB0sIS0dLSUtLS01LT0tQS1FLUktTS1VLV0tYS1lLW0tcS5tLnUufS6FLo0ukS6VLpkunS6lLq0usS61Lr0uwS9VL+UwgTERMRkxITEpMTExOTFBMUUxTTGBMb0xxTHNMdUx3THlMe0x9TIxMjkyQTJJMlUyYTJtMnkygTN9M4UzjTOVM50zoTOlM6kzrTO1M70zwTPFM80z0TPZNNU03TTlNO009TT5NP01ATUFNQ01FTUZNR01JTUpNiU2LTY1Nj02RTZJNk02UTZVNl02ZTZpNm02dTZ5N3U3fTeJN5E3mTedN6E3pTepN7E3uTe9N8E3yTfNN9k41TjdOOU47Tj1OPk4/TkBOQU5DTkVORk5HTklOSk6JTotOjU6PTpFOkk6TTpROlU6XTplOmk6bTp1Onk7dTt9O4U7jTuVO5k7nTuhO6U7rTu1O7k7vTvFO8k89T2BPgE+gT6JPpE+mT6hPqk+rT6xPr0+wT7JPs0+1T7dPuE+5T7xPvU/GT9NP2E/aT9xP4U/kT+dP6VAOUDJQWVB9UIBQglCEUIZQiFCKUItQjlCbUKxQrlCwULJQtFC2ULhQulC8UM1Q0FDTUNZQ2VDcUN9Q4lDlUOdRJlEoUSpRLFEvUTBRMVEyUTNRNVE3UThROVE7UTxRe1F9UX9RgVGEUYVRhlGHUYhRilGMUY1RjlGQUZFR0FHSUdVR11HaUdtR3FHdUd5R4FHiUeNR5FHmUedR9FH1UfZR+FI3UjlSO1I9UkBSQVJCUkNSRFJGUkhSSVJKUkxSTVKMUo5SkFKSUpVSllKXUphSmVKbUp1SnlKfUqFSolLhUuNS5VLnUupS61LsUu1S7lLwUvJS81L0UvZS91M2UzhTOlM8Uz9TQFNBU0JTQ1NFU0dTSFNJU0tTTFOLU41Tj1ORU5RTlVOWU5dTmFOaU5xTnVOeU6BToVPGU+pUEVQ1VDhUOlQ8VD5UQFRCVENURlRTVGJUZFRmVGhUalRsVG5UcFR/VIJUhVSIVItUjlSRVJRUllTVVNdU2VTbVN5U31TgVOFU4lTkVOZU51ToVOpU61UqVSxVLlUwVTNVNFU1VTZVN1U5VTtVPFU9VT9VQFV/VYFVg1WFVYhViVWKVYtVjFWOVZBVkVWSVZRVlVXUVdZV2VXbVd5V31XgVeFV4lXkVeZV51XoVepV61XuVi1WL1YxVjNWNlY3VjhWOVY6VjxWPlY/VkBWQlZDVoJWhFaGVohWi1aMVo1WjlaPVpFWk1aUVpVWl1aYVtdW2VbbVt1W4FbhVuJW41bkVuZW6FbpVupW7FbtVvhXAVcCVwRXDVcYVydXMldAV1VXaVeAV5JXn1egV6FXo1ewV7FXsle0V8FXwlfDV8VXzlfdV+pX+VgLWB9YNlhIWFFYUlhUWGFYYlhjWGVYZlhvWHlYgAAAAAAAAAICAAAAAAAADK8AAAAAAAAAAAAAAAAAAFiI + + CovidSafe/tracer.xcdatamodeld/ModelV2.xcdatamodel + YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0 +cxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxDeAAsADAAbADcAOAA5AEEAQgBdAF4AXwBlAGYAcgCIAIkAigCLAIwAjQCOAI8AkACRAKoArQC0ALoAyQDYANsA6gD5APwAXAEMARsBHwEjATIBOAE5AUEBUAFZAWkBagFrAWwBbQFuAW8BhAGFAY0BjgGPAZsBrQGuAa8BsAGxAbIBswG0AcMB0gHhAeUB9AIDAhICIQIiAi4CNAI1AkQCTQJOAlICWgJvAnACeAJ5AoUCmQKaAqkCuALHAssC2gLpAvgDBwMWAyIDNAM1AzYDNwM4AzkDOgM7A0oDWQNoA3cDeAOHA5YDpQOtA8IDwwPLA9cD6wP6BAkEGAQcBCsEOgRJBFgEZwRzBIUElASjBLIEwQTQBN8E7gUDBQQFDAUYBSwFOwVKBVkFXQVsBXsFigWZBagFtAXGBdUF5AXzBgIGEQYgBi8GRAZFBk0GWQZtBnwGiwaaBp4GrQa8BssG2gbpBvUHBwcWByUHNAdDB0QHUwdiB3EHhgeHB48HmwevB74HzQfcB+AH7wf+CA0IHAgrCDcISQhYCFkIaAh3CIYIhwiWCKUItAi1CLgIwQjFCMkIzQjVCNgI3AjdVSRudWxs1wANAA4ADwAQABEAEgATABQAFQAWABcAGAAXABpfEA9feGRfcm9vdFBhY2thZ2VWJGNsYXNzXF94ZF9jb21tZW50c18QEF94ZF9tb2RlbE1hbmFnZXJfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVdX3hkX21vZGVsTmFtZV8QF19tb2RlbFZlcnNpb25JZGVudGlmaWVygAKA3YDagACA24AAgNzeABwAHQAeAB8AIAAhACIADgAjACQAJQAmACcAKAApACoAKwAJACkAFwAvADAAMQAyADMAKQApABdfEBxYREJ1Y2tldEZvckNsYXNzZXN3YXNFbmNvZGVkXxAaWERCdWNrZXRGb3JQYWNrYWdlc3N0b3JhZ2VfEBxYREJ1Y2tldEZvckludGVyZmFjZXNzdG9yYWdlXxAPX3hkX293bmluZ01vZGVsXxAdWERCdWNrZXRGb3JQYWNrYWdlc3dhc0VuY29kZWRWX293bmVyXxAbWERCdWNrZXRGb3JEYXRhVHlwZXNzdG9yYWdlW192aXNpYmlsaXR5XxAZWERCdWNrZXRGb3JDbGFzc2Vzc3RvcmFnZVVfbmFtZV8QH1hEQnVja2V0Rm9ySW50ZXJmYWNlc3dhc0VuY29kZWRfEB5YREJ1Y2tldEZvckRhdGFUeXBlc3dhc0VuY29kZWRfEBBfdW5pcXVlRWxlbWVudElEgASA2IDWgAGABIAAgNeA2RAAgAWAA4AEgASAAFBTWUVT0wA6ADsADgA8AD4AQFdOUy5rZXlzWk5TLm9iamVjdHOhAD2ABqEAP4AHgCVZRW5jb3VudGVy3xAQAEMARABFAEYAIQBHAEgAIwBJAEoADgAlAEsATAAoAE0ATgBPACkAKQAUAFMAVAAxACkATgBXAD0ATgBaAFsAXF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZV8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc2R1cGxpY2F0ZXNfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zc3RvcmFnZVtfaXNBYnN0cmFjdIAJgCyABIAEgAKACoDTgASACYDVgAaACYDUgAgIEik2wVdXb3JkZXJlZNMAOgA7AA4AYABiAEChAGGAC6EAY4AMgCVeWERfUFN0ZXJlb3R5cGXZACEAJQBnAA4AKABoACMATQBpAD8AYQBOAG0AFwApADEAXABxXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgAeAC4AJgCuAAIAECIAN0wA6ADsADgBzAH0AQKkAdAB1AHYAdwB4AHkAegB7AHyADoAPgBCAEYASgBOAFIAVgBapAH4AfwCAAIEAggCDAIQAhQCGgBeAG4AcgB6AH4AhgCOAJoAqgCVfEBNYRFBNQ29tcG91bmRJbmRleGVzXxAQWERfUFNLX2VsZW1lbnRJRF8QGVhEUE1VbmlxdWVuZXNzQ29uc3RyYWludHNfEBpYRF9QU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QGVhEX1BTS19mZXRjaFJlcXVlc3RzQXJyYXlfEBFYRF9QU0tfaXNBYnN0cmFjdF8QD1hEX1BTS191c2VySW5mb18QE1hEX1BTS19jbGFzc01hcHBpbmdfEBZYRF9QU0tfZW50aXR5Q2xhc3NOYW1l3xAPAJIAkwCUACEAlQCWAJcAIwCYAA4AJQCZAJoAKACbABcAnQAXAGMAXABcAFwAMQBcAKQAdABcAFwAFwBcVV90eXBlWF9kZWZhdWx0XF9hc3NvY2lhdGlvbltfaXNSZWFkT25seVlfaXNTdGF0aWNZX2lzVW5pcXVlWl9pc0Rlcml2ZWRaX2lzT3JkZXJlZFxfaXNDb21wb3NpdGVXX2lzTGVhZoAAgBiAAIAMCAgICIAagA4ICIAACNIAOwAOAKsArKCAGdIArgCvALAAsVokY2xhc3NuYW1lWCRjbGFzc2VzXk5TTXV0YWJsZUFycmF5owCwALIAs1dOU0FycmF5WE5TT2JqZWN00gCuAK8AtQC2XxAQWERVTUxQcm9wZXJ0eUltcKQAtwC4ALkAs18QEFhEVU1MUHJvcGVydHlJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwBjAFwAXABcADEAXACkAHUAXABcABcAXIAAgACAAIAMCAgICIAagA8ICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAMsAFwBjAFwAXABcADEAXACkAHYAXABcABcAXIAAgB2AAIAMCAgICIAagBAICIAACNIAOwAOANkArKCAGd8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXABcAFwBjAFwAXABcADEAXACkAHcAXABcABcAXIAAgACAAIAMCAgICIAagBEICIAACN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAOwAFwBjAFwAXABcADEAXACkAHgAXABcABcAXIAAgCCAAIAMCAgICIAagBIICIAACNIAOwAOAPoArKCAGd8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAP4AFwBjAFwAXABcADEAXACkAHkAXABcABcAXIAAgCKAAIAMCAgICIAagBMICIAACAjfEA8AkgCTAJQAIQCVAJYAlwAjAJgADgAlAJkAmgAoAJsAFwEOABcAYwBcAFwAXAAxAFwApAB6AFwAXAAXAFyAAIAkgACADAgICAiAGoAUCAiAAAjTADoAOwAOARwBHQBAoKCAJdIArgCvASABIV8QE05TTXV0YWJsZURpY3Rpb25hcnmjASABIgCzXE5TRGljdGlvbmFyed8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXASUAFwBjAFwAXABcADEAXACkAHsAXABcABcAXIAAgCeAAIAMCAgICIAagBUICIAACNYAJQAOACgATQAhACMBMwE0ABcAXAAXADGAKIApgAAIgABfEBRYREdlbmVyaWNSZWNvcmRDbGFzc9IArgCvAToBO11YRFVNTENsYXNzSW1wpgE8AT0BPgE/AUAAs11YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QDwCSAJMAlAAhAJUAlgCXACMAmAAOACUAmQCaACgAmwAXAD0AFwBjAFwAXABcADEAXACkAHwAXABcABcAXIAAgAaAAIAMCAgICIAagBYICIAACNIArgCvAVEBUl8QElhEVU1MU3RlcmVvdHlwZUltcKcBUwFUAVUBVgFXAVgAs18QElhEVU1MU3RlcmVvdHlwZUltcF1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNMAOgA7AA4BWgFhAECmAVsBXAFdAV4BXwFggC2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QCNEI0gjTCNQAs15YRE1vZGVsUGFja2FnZV8QD1hEVU1MUGFja2FnZUltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDSADsADgjWAKyggBnTADoAOwAOCNkI2gBAoKCAJVDSAK4ArwjeCN9ZWERQTU1vZGVsowjeCOAAs1dYRE1vZGVsAAgAGQAiACwAMQA6AD8AUQBWAFsAXQIcAiICPwJRAlgCZQJ4ApACngK4AroCvAK+AsACwgLEAsYC/wMeAzsDWgNsA4wDkwOxA70D2QPfBAEEIgQ1BDcEOQQ7BD0EPwRBBEMERQRHBEkESwRNBE8EUQRSBFYEYwRrBHYEeQR7BH4EgASCBIwEzwTzBRcFOgVhBYEFqAXPBe8GEwY3BkMGRQZHBkkGSwZNBk8GUQZTBlUGVwZZBlsGXQZfBmAGZQZtBnoGfQZ/BoIGhAaGBpUGugbeBwUHKQcrBy0HLwcxBzMHNQc2BzgHRQdYB1oHXAdeB2AHYgdkB2YHaAdqB30HfweBB4MHhQeHB4kHiweNB48HkQenB7oH1gfzCA8IIwg1CEsIZAijCKkIsgi/CMsI1QjfCOoI9QkCCQoJDAkOCRAJEgkTCRQJFQkWCRgJGgkbCRwJHgkfCSgJKQkrCTQJPwlICVcJXglmCW8JeAmLCZQJpwm+CdAKDwoRChMKFQoXChgKGQoaChsKHQofCiAKIQojCiQKYwplCmcKaQprCmwKbQpuCm8KcQpzCnQKdQp3CngKgQqCCoQKwwrFCscKyQrLCswKzQrOCs8K0QrTCtQK1QrXCtgLFwsZCxsLHQsfCyALIQsiCyMLJQsnCygLKQsrCywLNQs2CzgLdwt5C3sLfQt/C4ALgQuCC4MLhQuHC4gLiQuLC4wLjQvMC84L0AvSC9QL1QvWC9cL2AvaC9wL3QveC+AL4QvuC+8L8AvyC/sMEQwYDCUMZAxmDGgMagxsDG0MbgxvDHAMcgx0DHUMdgx4DHkMkgyUDJYMmAyZDJsMsgy7DMkM1gzkDPkNDQ0kDTYNdQ13DXkNew19DX4Nfw2ADYENgw2FDYYNhw2JDYoNkw2oDbcNzA3aDe8OAw4aDiwOOQ5GDkgOSg5MDk4OUA5SDl8OYQ5jDmUOZw5pDmsObQ5/DoMOjQ6YDqIOpA7vDxIPMg9SD1QPVg9YD1oPXA9dD14PYA9hD2MPZA9mD2gPaQ9qD2wPbQ9yD38PhA+GD4gPjQ+PD5EPkw+oD7wP4RAFECwQUBBSEFQQVhBYEFoQXBBdEF8QbBB7EH0QfxCBEIMQhRCHEIkQmBCaEJwQnhCgEKIQpBCmEKgQxhDkEPcRCxEoETwRUhGREZMRlRGXEZkRmhGbEZwRnRGfEaERohGjEaURphHlEecR6RHrEe0R7hHvEfAR8RHzEfUR9hH3EfkR+hI5EjsSPRI/EkESQhJDEkQSRRJHEkkSShJLEk0SThJbElwSXRJfEp4SoBKiEqQSphKnEqgSqRKqEqwSrhKvErASshKzEvIS9BL2EvgS+hL7EvwS/RL+EwATAhMDEwQTBhMHE0YTSBNKE0wTThNPE1ATURNSE1QTVhNXE1gTWhNbE5oTnBOeE6ATohOjE6QTpROmE6gTqhOrE6wTrhOvE7AT1RP5FCAURBRGFEgUShRMFE4UUBRRFFMUYBRjFGUUaBRqFGwUhRTEFMYUyBTKFMwUzRTOFM8U0BTSFNQU1RTWFNgU2RTuFQEVFBUcFR4VIBUiFSQVJhVEFU0VYBVlFXgVgRWXFaQVuhXHFdoV8RYDFk4WcRaRFrEWsxa1FrcWuRa7FrwWvRa/FsAWwhbDFsUWxxbIFskWyxbMFtEW3hbjFuUW5xbsFu4W8BbyFwcXLBdQF3cXmxedF58XoRejF6UXpxeoF6oXtxfIF8oXzBfOF9AX0hfUF9YX2BfpF+sX7RfvF/EX8xf1F/cX+Rf7GBAYTxhRGFMYVRhXGFgYWRhaGFsYXRhfGGAYYRhjGGQYoxilGKcYqRirGKwYrRiuGK8YsRizGLQYtRi3GLgY9xj5GPsY/Rj/GQAZARkCGQMZBRkHGQgZCRkLGQwZGRkaGRsZHRlcGV4ZYBliGWQZZRlmGWcZaBlqGWwZbRluGXAZcRmwGbIZtBm2GbgZuRm6GbsZvBm+GcAZwRnCGcQZxRoEGgYaCBoKGgwaDRoOGg8aEBoSGhQaFRoWGhgaGRpYGloaXBpeGmAaYRpiGmMaZBpmGmgaaRpqGmwabRqsGq4asBqyGrQatRq2GrcauBq6GrwavRq+GsAawRrmGwobMRtVG1cbWRtbG10bXxthG2IbZBtxG4AbghuEG4YbiBuKG4wbjhudG58boRujG6UbpxupG6sbrRvNG/gcEhwrHEUcZRyIHMccyRzLHM0czxzQHNEc0hzTHNUc1xzYHNkc2xzcHRsdHR0fHSEdIx0kHSUdJh0nHSkdKx0sHS0dLx0wHW8dcR1zHXUddx14HXkdeh17HX0dfx2AHYEdgx2EHcMdxR3HHckdyx3MHc0dzh3PHdEd0x3UHdUd1x3YHdseGh4cHh4eIB4iHiMeJB4lHiYeKB4qHiseLB4uHi8ebh5wHnIedB52HnceeB55HnoefB5+Hn8egB6CHoMewh7EHsYeyB7KHssezB7NHs4e0B7SHtMe1B7WHtce4B7uHvsfCR8WHykfQB9SH50fwB/gIAAgAiAEIAYgCCAKIAsgDCAOIA8gESASIBQgFiAXIBggGiAbICAgLSAyIDQgNiA7ID0gPyBBIGYgiiCxINUg1yDZINsg3SDfIOEg4iDkIPEhAiEEIQYhCCEKIQwhDiEQIRIhIyElISchKSErIS0hLyExITMhNSF0IXYheCF6IXwhfSF+IX8hgCGCIYQhhSGGIYghiSHIIcohzCHOIdAh0SHSIdMh1CHWIdgh2SHaIdwh3SIcIh4iICIiIiQiJSImIiciKCIqIiwiLSIuIjAiMSI+Ij8iQCJCIoEigyKFIociiSKKIosijCKNIo8ikSKSIpMilSKWItUi1yLZItsi3SLeIt8i4CLhIuMi5SLmIuci6SLqIykjKyMtIy8jMSMyIzMjNCM1IzcjOSM6IzsjPSM+I30jfyOBI4MjhSOGI4cjiCOJI4sjjSOOI48jkSOSI9Ej0yPVI9cj2SPaI9sj3CPdI98j4SPiI+Mj5SPmJAskLyRWJHokfCR+JIAkgiSEJIYkhySJJJYkpSSnJKkkqyStJK8ksSSzJMIkxCTGJMgkyiTMJM4k0CTSJRElEyUVJRclGSUaJRslHCUdJR8lISUiJSMlJSUmJWUlZyVpJWslbSVuJW8lcCVxJXMldSV2JXcleSV6JbkluyW9Jb8lwSXCJcMlxCXFJcclySXKJcslzSXOJg0mDyYRJhMmFSYWJhcmGCYZJhsmHSYeJh8mISYiJmEmYyZlJmcmaSZqJmsmbCZtJm8mcSZyJnMmdSZ2JrUmtya5JrsmvSa+Jr8mwCbBJsMmxSbGJscmySbKJwknCycNJw8nEScSJxMnFCcVJxcnGScaJxsnHSceJ2knjCe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 + + + + + v + + + \ No newline at end of file diff --git a/CovidSafe/EncounterV2Mapping.swift b/CovidSafe/EncounterV2Mapping.swift new file mode 100644 index 0000000..15e67e1 --- /dev/null +++ b/CovidSafe/EncounterV2Mapping.swift @@ -0,0 +1,102 @@ +// +// EncounterV2Mapping.swift +// CovidSafe +// +// Copyright © 2020 Australian Government. All rights reserved. +// + +import CoreData + +struct MigrationRemoteBlob: Encodable { + enum CodingKeys: String, CodingKey { + case msg + case modelC + case modelP + case rssi + case txPower + } + var msg: String? + var modelC: String? + var modelP: String? + var rssi: NSNumber? + var txPower: NSNumber? + + func encode(to encoder: Encoder) throws { + + var container = encoder.container(keyedBy: CodingKeys.self) + if let msg = msg { + try container.encode(msg, forKey: .msg) + } + + if let modelC = modelC, modelC != BluetraceConfig.DummyModel{ + try container.encode(modelC, forKey: .modelC) + } + if let modelP = modelP, modelP != BluetraceConfig.DummyModel { + try container.encode(modelP, forKey: .modelP) + } + if let rssi = rssi, rssi.intValue != BluetraceConfig.DummyRSSI { + try container.encode(rssi.doubleValue, forKey: .rssi) + } + if let txPower = txPower, txPower.intValue != BluetraceConfig.DummyTxPower { + try container.encode(txPower.doubleValue, forKey: .txPower) + } + } + +} + + +class EncounterV2Mapping: NSEntityMigrationPolicy { + var localEmptyBlob: String = "" + override func begin(_ mapping: NSEntityMapping, with manager: NSMigrationManager) throws { + try super.begin(mapping, with: manager) + let emptyLocal = try JSONEncoder().encode(MigrationRemoteBlob()) + localEmptyBlob = try Crypto.encrypt(dataToEncrypt: emptyLocal) + } + override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws { + + if (sInstance.entity.name == "Encounter") { + guard let version = sInstance.primitiveValue(forKey: "v") as? Int64 else { + return + } + let encounterV2 = NSEntityDescription.insertNewObject(forEntityName: "Encounter", into: manager.destinationContext) + let msg = sInstance.primitiveValue(forKey: "msg") as? String + let modelC = sInstance.primitiveValue(forKey: "modelC") as? String + let modelP = sInstance.primitiveValue(forKey: "modelP") as? String + let rssi = sInstance.primitiveValue(forKey: "rssi") as? NSNumber + let txPower = sInstance.primitiveValue(forKey: "txPower") as? NSNumber + let org = sInstance.primitiveValue(forKey: "org") as? String + let timestamp = sInstance.primitiveValue(forKey: "timestamp") as? Date + + + encounterV2.setPrimitiveValue(org, forKey: "org") + encounterV2.setPrimitiveValue(BluetraceConfig.ProtocolVersion, forKey: "v") + encounterV2.setPrimitiveValue(timestamp, forKey: "timestamp") + do { + try autoreleasepool { + if version == 1 { + //convert a regular old v1 entry + encounterV2.setPrimitiveValue(localEmptyBlob, forKey: "localBlob") + let remoteBlob = MigrationRemoteBlob(msg: msg, modelC: modelC, modelP: modelP, rssi: rssi, txPower: txPower) + + let blobJson = try JSONEncoder().encode(remoteBlob) + let remoteBlobEncrypted = try Crypto.encrypt(dataToEncrypt: blobJson) + encounterV2.setPrimitiveValue(remoteBlobEncrypted, forKey: "remoteBlob") + manager.associate(sourceInstance: sInstance, withDestinationInstance: encounterV2, for: mapping) + + } else if version == 2 { + //convert an entry that was recieved from an already updated app (the msg will already be encrypted) + let localMigrationBlob = MigrationRemoteBlob(msg: nil, modelC: modelC, modelP: modelP, rssi: rssi, txPower: txPower) + let jsonLocal = try JSONEncoder().encode(localMigrationBlob) + let localBlob = try Crypto.encrypt(dataToEncrypt: jsonLocal) + encounterV2.setPrimitiveValue(localBlob, forKey: "localBlob") + encounterV2.setPrimitiveValue(msg, forKey: "remoteBlob") + manager.associate(sourceInstance: sInstance, withDestinationInstance: encounterV2, for: mapping) + } + } + } catch { + throw(error) + } + // we're dropping all the debug messages save to the db during the migration as they're unnessecary + } + } +} diff --git a/CovidSafe/Feedback/README.md b/CovidSafe/Feedback/README.md new file mode 100644 index 0000000..b576f8b --- /dev/null +++ b/CovidSafe/Feedback/README.md @@ -0,0 +1,94 @@ +# Feedback + +Provides a way for users or testers to send feedback into your JIRA instance from within the app along with a screenshot. + +## Configuration + +#### JIRA + +JIRA Mobile Connect needs to be enabled on a per project basis, otherwise it will not work with your app. Remember, if you are hosting your own JIRA instance, you will need to install the JIRA Mobile Connect plugin on your server before you can enable it. + +To enable JIRA Mobile Connect for a project: + +1. Navigate to the desired project > **Project Settings**. +2. Find the **Settings** section on the page and click **Enable** for the **JIRA Mobile Connect** setting. + This will enable the JIRA Mobile Connect plugin for the project, as well as create a user ('jiraconnectuser') in JIRA that is used to create all feedback and crash reports. +3. To enable the user to create tickets, you must grant it permission to create issues in the project. To do this, grant the 'Create Issues' permission to the 'jiraconnectuser' user. You can do this by adding the user to a group or project role that has the 'Create Issues' permission or grant the permission to the user directly (see [Managing project permissions](https://confluence.atlassian.com/display/AdminJIRACloud/Managing+project+permissions) for help). + +#### iOS + +Before you can use JIRA Mobile Connect with iOS, you need to identify the JIRA instance that feedback will be sent to. This is done via a JSON file named **JMCTarget**. You'll need to create this **JMCTarget.json** file in your Xcode project and include it in the iOS app's target so it gets bundled with the app. + +1. Right-click your project in Xcode and click **New File...** +2. Select **Other** in the **iOS** section and click **Next**. +3. In the **Save As** field, type 'JMCTarget.JSON' then select the desired **Targets**. +4. Click **Create**. +5. Add the following code to your new** JMCTarget.JSON** file. Make sure to replace the values with your own: +``` +{ + "host": "example-dev.atlassian.net", + "projectKey": "EXAMPLEKEY", + "apiKey": "myApiKey" +} +``` + +_**Note:** Do not prefix the host with `https://`._ + +## Using JIRA Mobile Connect in your app + +### Trigger from the shake-motion event + +The shake-motion event is one of the [motion events](https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/motion_event_basics/motion_event_basics.html#//apple_ref/doc/uid/TP40009541-CH6-SW14) that are detected by the device when it is moved. The following instructions will show you how to trigger a feedback flow (i.e. create a JIRA issue) in your app when the user shakes the device. + +_Note that as of iOS 10, `motionEnded` is no longer called on the `AppDelegate`, so you must override `motionEnded` on a `UIResponder` (subclass `UIWindow` or use a top-level view controller)._ + +Wherever you choose to override `motionEnded`, add the following: + +```swift +override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent?) { + do { + let settings = try FeedbackSettings() + .presentFeedbackIfShakeMotion(motion, promptUser: false, settings: settings) + } catch { + preconditionFailure("Error retrieving feedback settings: \(error.localizedDescription)") + } +} +``` + +### Programmatic trigger + +You can also trigger a feedback flow in your app from other events, for example, a button that is tapped in the app. This section shows you how to implement this. + +Call `presentFeedback()` on any view controller to present feedback: + +```swift +do { + let feedbackSettings = try FeedbackSettings() + currentViewController.presentFeedback(true, settings: feedbackSettings) +} catch { + preconditionFailure("Error retrieving feedback settings: \(error.localizedDescription)") +} +``` + +### Additional Configuration + +##### Action sheet prompt + +To present an action sheet prompt before taking the user to the feedback flow, set **`promptUser`** to true. + +```swift +// Shake Motion +.presentFeedbackIfShakeMotion(motion, promptUser: true, settings: settings) + +// Programmatic +currentViewController.presentFeedback(true, settings: feedbackSettings) +``` + +##### Reporter details + +To pass a string to identify who is sending the feedback, set **`reporterUsernameOrEmail`** to the reporter's Id string. +_**Important**: The current version does not let the user know their ID is being sent. You may want to only use this feature for non-Appstore builds. To do this, just set reporterUsernameOrEmail to `nil` for release builds._ + +```swift +let settings = try FeedbackSettings(reporterUsernameOrEmail: "someone@example.com") +``` diff --git a/CovidSafe/HomeViewController.swift b/CovidSafe/HomeViewController.swift index 4e13275..f5e0d42 100644 --- a/CovidSafe/HomeViewController.swift +++ b/CovidSafe/HomeViewController.swift @@ -14,7 +14,6 @@ class HomeViewController: UIViewController { @IBOutlet weak var homeHeaderInfoText: UILabel! @IBOutlet weak var homeHeaderPermissionsOffImage: UIImageView! @IBOutlet weak var shareView: UIView! - @IBOutlet weak var thanksForTheHelp: UIView! @IBOutlet weak var appPermissionsLabel: UIView! @IBOutlet weak var animatedBluetoothHeader: UIView! @IBOutlet weak var versionNumberLabel: UILabel! @@ -25,6 +24,7 @@ class HomeViewController: UIViewController { @IBOutlet weak var pushNotificationStatusTitle: UILabel! @IBOutlet weak var pushNotificationStatusIcon: UIImageView! @IBOutlet weak var pushNotificationStatusLabel: UILabel! + @IBOutlet weak var uploadDateLabel: UILabel! var lottieBluetoothView: AnimationView! @@ -35,13 +35,31 @@ class HomeViewController: UIViewController { var didUploadData: Bool { let uploadTimestamp = UserDefaults.standard.double(forKey: "uploadDataDate") let lastUpload = Date(timeIntervalSince1970: uploadTimestamp) - return Date().timeIntervalSince(lastUpload) < 86400 * 21 + return Date().timeIntervalSince(lastUpload) < 86400 * 14 } - var shouldShowEndOfIsolationScreen: Bool { - let uploadTimestamp = UserDefaults.standard.double(forKey: "firstUploadDataDate") + var dataUploadedAttributedString: NSAttributedString? { + let uploadTimestamp = UserDefaults.standard.double(forKey: "uploadDataDate") if(uploadTimestamp > 0){ let lastUpload = Date(timeIntervalSince1970: uploadTimestamp) - return Date().timeIntervalSince(lastUpload) >= 86400 * 21 + let dateFormatterPrint = DateFormatter() + dateFormatterPrint.dateFormat = "dd MMM yyyy" + let formattedDate = dateFormatterPrint.string(from: lastUpload) + let newAttributedString = NSMutableAttributedString(string: "Your information was uploaded on \(formattedDate).") + + guard let dateRange = newAttributedString.string.range(of: formattedDate) else { return nil } + let nsRange = NSRange(dateRange, in: newAttributedString.string) + newAttributedString.addAttribute(.font, + value: UIFont.boldSystemFont(ofSize: 18), + range: nsRange) + return newAttributedString + } + return nil + } + var shouldShowUploadDate: Bool { + let uploadTimestamp = UserDefaults.standard.double(forKey: "uploadDataDate") + if(uploadTimestamp > 0){ + let lastUpload = Date(timeIntervalSince1970: uploadTimestamp) + return Date().timeIntervalSince(lastUpload) <= 86400 * 14 } return false } @@ -67,6 +85,7 @@ class HomeViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(enableUserInteraction(_:)), name: .enableUserInteraction, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) + if let versionNumber = Bundle.main.versionShort, let buildNumber = Bundle.main.version { self.versionNumberLabel.text = "Version number: \(versionNumber) Build: \(buildNumber)" } else { @@ -89,10 +108,6 @@ class HomeViewController: UIViewController { super.viewDidAppear(animated) self.lottieBluetoothView?.play() self.becomeFirstResponder() - - if(shouldShowEndOfIsolationScreen){ - self.performSegue(withIdentifier: "IsolationSuccessSegue", sender: self) - } } override func viewWillDisappear(_ animated: Bool) { @@ -133,11 +148,21 @@ class HomeViewController: UIViewController { self?.toggleBluetoothPermissionStatusView() self?.toggleHeaderView() self?.toggleUploadView() + self?.toggleUploadDateView() } }) } } + fileprivate func toggleUploadDateView() { + if shouldShowUploadDate, let lastUploadText = self.dataUploadedAttributedString { + uploadDateLabel.attributedText = lastUploadText + uploadDateLabel.isHidden = false + } else { + uploadDateLabel.isHidden = true + } + } + fileprivate func readPermissions(notificationSettings: UNNotificationSettings) { self.bluetoothStatusOn = BluetraceManager.shared.isBluetoothOn() self.bluetoothPermissionOn = BluetraceManager.shared.isBluetoothAuthorized() @@ -169,15 +194,9 @@ class HomeViewController: UIViewController { fileprivate func toggleHeaderView() { self.allPermissionOn ? self.lottieBluetoothView?.play() : self.lottieBluetoothView?.stop() toggleViewVisibility(view: appPermissionsLabel, isVisible: !self.allPermissionOn) - toggleViewVisibility(view: thanksForTheHelp, isVisible: self.allPermissionOn && self.didUploadData) toggleViewVisibility(view: homeHeaderPermissionsOffImage, isVisible: !self.allPermissionOn) toggleViewVisibility(view: lottieBluetoothView, isVisible: self.allPermissionOn) - if (self.allPermissionOn && self.didUploadData) { - self.homeHeaderInfoText.textColor = UIColor.white - } else { - self.homeHeaderInfoText.textColor = UIColor(0x131313) - } self.helpButton.setImage(UIImage(named: "ic-help-selected"), for: .normal) self.helpButton.setTitleColor(UIColor.black, for: .normal) @@ -187,12 +206,6 @@ class HomeViewController: UIViewController { if (!self.allPermissionOn) { self.homeHeaderInfoText.text = "COVIDSafe is not active.\nCheck your permissions." self.homeHeaderView.backgroundColor = UIColor.covidHomePermissionErrorColor - } else if (self.didUploadData) { - self.helpButton.setImage(UIImage(named: "ic-help"), for: .normal) - self.helpButton.setTitleColor(UIColor.white, for: .normal) - self.homeHeaderInfoText.font = UIFont.systemFont(ofSize: 18, weight: .bold) - self.homeHeaderView.backgroundColor = UIColor.covidSafeButtonDarkerColor - updateAnimationViewWithAnimationName(name: "Spinner_home_upload_complete") } else { self.homeHeaderView.backgroundColor = UIColor.covidHomeActiveColor updateAnimationViewWithAnimationName(name: "Spinner_home") @@ -279,6 +292,15 @@ class HomeViewController: UIViewController { present(safariVC, animated: true, completion: nil) } + @IBAction func bluetoothPairingTapped(_ sender: Any) { + guard let url = URL(string: "https://www.covidsafe.gov.au/help-topics.html#bluetooth-pairing-request") else { + return + } + + let safariVC = SFSafariViewController(url: url) + present(safariVC, animated: true, completion: nil) + } + @IBAction func onPositiveButtonTapped(_ sender: UITapGestureRecognizer) { guard let helpVC = UIStoryboard(name: "UploadData", bundle: nil).instantiateInitialViewController() else { return diff --git a/CovidSafe/InfoViewController.swift b/CovidSafe/InfoViewController.swift index 69974d1..6fdb0ee 100644 --- a/CovidSafe/InfoViewController.swift +++ b/CovidSafe/InfoViewController.swift @@ -54,15 +54,16 @@ final class InfoViewController: UIViewController { } func fetchDevicesEncounteredCount() { - guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { - return + guard let persistentContainer = + EncounterDB.shared.persistentContainer else { + return } - let managedContext = appDelegate.persistentContainer.viewContext + let managedContext = persistentContainer.viewContext let fetchRequest = Encounter.fetchRequestForRecords() do { let devicesEncountered = try managedContext.fetch(fetchRequest) - let uniqueIDs = Set(devicesEncountered.map { $0.msg }) + let uniqueIDs = Set(devicesEncountered.map { $0.timestamp }) self.devicesEncounteredLabel.text = String(uniqueIDs.count) } catch let error as NSError { print("Could not fetch. \(error), \(error.userInfo)") @@ -79,12 +80,29 @@ final class InfoViewController: UIViewController { BluetraceManager.shared.toggleScanning(mySwitch.isOn) } + @IBAction func dumpDBpressed(_ sender: UIButton) { + var activityItems: [Any] = [] + let localStoreUrl = CovidPersistentContainer.defaultDirectoryURL().appendingPathComponent("tracer", isDirectory: false).appendingPathExtension("sqlite") + activityItems.append(localStoreUrl) + let localStoreUrlshm = CovidPersistentContainer.defaultDirectoryURL().appendingPathComponent("tracer", isDirectory: false).appendingPathExtension("sqlite-shm") + if FileManager.default.fileExists(atPath: localStoreUrlshm.path) { + activityItems.append(localStoreUrlshm) + } + let localStoreUrlwal = CovidPersistentContainer.defaultDirectoryURL().appendingPathComponent("tracer", isDirectory: false).appendingPathExtension("sqlite-wal") + if FileManager.default.fileExists(atPath: localStoreUrlwal.path) { + activityItems.append(localStoreUrlwal) + } + let activity = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + present(activity, animated: true, completion: nil) + } + @objc func clearLogsButtonClicked() { - guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { - return + guard let persistentContainer = + EncounterDB.shared.persistentContainer else { + return } - let managedContext = appDelegate.persistentContainer.viewContext + let managedContext = persistentContainer.viewContext let fetchRequest = NSFetchRequest(entityName: "Encounter") fetchRequest.includesPropertyValues = false do { diff --git a/CovidSafe/InitialScreenViewController.swift b/CovidSafe/InitialScreenViewController.swift index 2e98cd0..621e4a4 100644 --- a/CovidSafe/InitialScreenViewController.swift +++ b/CovidSafe/InitialScreenViewController.swift @@ -8,14 +8,17 @@ import UIKit import KeychainSwift -class InitialScreenViewController: UIViewController { +class InitialScreenViewController: UIViewController, EncounterDBMigrationProgress { - let displayTimeSeconds: Int = 4 + let displayTimeSeconds = 4.0 let giveupTimeSeconds = 8.0 + var migrationStart: Date? var isKeychainAvailable = false var isDisplayTimeElapsed = false let keychain = KeychainSwift() var giveupTimer: Timer? + var initialDelayTimer: Timer? + var migrationViewController: UIViewController? override func viewDidLoad() { super.viewDidLoad() @@ -27,23 +30,26 @@ class InitialScreenViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(setKeychainAvailable(_:)), name: UIApplication.protectedDataDidBecomeAvailableNotification, object: nil) break } - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) + view.window?.tintColor = .covidSafeColor - let showAppDelay = DispatchTime.now() + .seconds(displayTimeSeconds) - DispatchQueue.main.asyncAfter(deadline: showAppDelay, execute: { - self.isDisplayTimeElapsed = true - if(self.proceedWithChecks()) { - self.performCheck() - } - }) + continueAfterDelay(delay: displayTimeSeconds) // add give up action in case the keychain notification in not received after 8 seconds giveupTimer = Timer.scheduledTimer(withTimeInterval: giveupTimeSeconds, repeats: false) { timer in self.performSegue(withIdentifier: "initialPersonalDetailsSegue", sender: self) } + EncounterDB.shared.setup(migrationDelegate: self) + } + + func continueAfterDelay(delay: TimeInterval) { + initialDelayTimer = Timer.scheduledTimer(withTimeInterval: delay, + repeats: false, + block: { (_) in + self.isDisplayTimeElapsed = true + if(self.proceedWithChecks()) { + self.performCheck() + } + }) } @objc @@ -61,9 +67,12 @@ class InitialScreenViewController: UIViewController { private func performCheck() { giveupTimer?.invalidate() + initialDelayTimer?.invalidate() + if let migrationVc = migrationViewController { + migrationVc.dismiss(animated: true, completion: nil) + } let isLoggedIn: Bool = (keychain.get("JWT_TOKEN") != nil) if !UserDefaults.standard.bool(forKey: "completedIWantToHelp") { - // old app signed out here keychain.delete("JWT_TOKEN") self.performSegue(withIdentifier: "initialScreenToIWantToHelpSegue", sender: self) } else if !UserDefaults.standard.bool(forKey: "hasConsented") { @@ -72,10 +81,40 @@ class InitialScreenViewController: UIViewController { self.performSegue(withIdentifier: "initialPersonalDetailsSegue", sender: self) } else if !UserDefaults.standard.bool(forKey: "allowedPermissions") { self.performSegue(withIdentifier: "initialScreenToAllowPermissionsSegue", sender: self) - } else if !UserDefaults.standard.bool(forKey: "turnedOnBluetooth") { - self.performSegue(withIdentifier: "initialScreenToTurnOnBtSegue", sender: self) } else { self.performSegue(withIdentifier: "initialScreenToHomeSegue", sender: self) } } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == "presentMigrationSegue" { + migrationViewController = segue.destination + } + } + + func migrationBegun() { + DLog("MIGRATION BEGUN") + giveupTimer?.invalidate() + initialDelayTimer?.invalidate() + migrationStart = Date() + performSegue(withIdentifier: "presentMigrationSegue", sender: nil) + } + + func migrationComplete() { + DLog("MIGRATION COMPLETE") + if let migrationStart = migrationStart { + let migrationDuration = abs(migrationStart.timeIntervalSinceNow) + if migrationDuration > displayTimeSeconds { + performCheck() + } else { + // migration was quick, still need to delay minimum 4 seconds + DLog("Migration too quick, waiting \(displayTimeSeconds - migrationDuration) seconds") + continueAfterDelay(delay: displayTimeSeconds - migrationDuration) + } + } + } + + func migrationFailed(error: Error) { + fatalError("Migration Failed \(error)") + } } diff --git a/CovidSafe/LogViewController.swift b/CovidSafe/LogViewController.swift index 3a31a65..11d6909 100644 --- a/CovidSafe/LogViewController.swift +++ b/CovidSafe/LogViewController.swift @@ -23,10 +23,11 @@ final class LogViewController: UIViewController { } func fetchEncounters() { - guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { - return + guard let persistentContainer = + EncounterDB.shared.persistentContainer else { + return } - let managedContext = appDelegate.persistentContainer.viewContext + let managedContext = persistentContainer.viewContext let fetchRequest = NSFetchRequest(entityName: "Encounter") let sortByDate = NSSortDescriptor(key: "timestamp", ascending: false) fetchRequest.sortDescriptors = [sortByDate] @@ -62,26 +63,23 @@ extension LogViewController: UITableViewDataSource { return } let datetime = encounter.timestamp - let msg = encounter.msg +// let msg = encounter.msg let dateFormatter = DateFormatter() dateFormatter.timeStyle = .medium let timeString = "\(datetime == nil ? "" : dateFormatter.string(from: datetime!))" - if let rawMessage = encounter.msg, let event = Encounter.Event(rawValue: rawMessage) { - cell.textLabel?.text = "\(timeString) - \(event.rawValue)" - } else { +// if let rawMessage = encounter.msg, let event = Encounter.Event(rawValue: rawMessage) { +// cell.textLabel?.text = "\(timeString) - \(event.rawValue)" +// } else { cell.textLabel?.text = """ \(timeString) - msg: \(msg ?? "") - modelC: \(encounter.modelC != nil ? "\(encounter.modelC!)" : "nil") - modelP: \(encounter.modelP != nil ? "\(encounter.modelP!)" : "nil") - RSSI: \(encounter.rssi != nil ? "\(encounter.rssi!)" : "nil") - txPower: \(encounter.txPower != nil ? "\(encounter.txPower!)" : "nil") org: \(encounter.org != nil ? "\(encounter.org!)" : "nil") v: \(encounter.v != nil ? "\(encounter.v!)" : "nil") + remoteBlob: \(encounter.remoteBlob ?? "nil") + localBlob: \(encounter.localBlob ?? "nil") """ cell.textLabel?.numberOfLines = 0 - } +// } } } diff --git a/CovidSafe/MigrationViewController.swift b/CovidSafe/MigrationViewController.swift new file mode 100644 index 0000000..1a2d60d --- /dev/null +++ b/CovidSafe/MigrationViewController.swift @@ -0,0 +1,22 @@ +// +// MigrationViewController.swift +// CovidSafe +// +// Copyright © 2020 Australian Government. All rights reserved. +// + +import UIKit +import Lottie + +class MigrationViewController: UIViewController { + @IBOutlet weak var animationContainer: UIView! + + override func viewDidLoad() { + super.viewDidLoad() + let migrationAnimation = AnimationView(name: "spinner_migrating_db") + migrationAnimation.loopMode = .loop + migrationAnimation.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: self.animationContainer.frame.size) + self.animationContainer.addSubview(migrationAnimation) + migrationAnimation.play() + } +} diff --git a/CovidSafe/OTPViewController.swift b/CovidSafe/OTPViewController.swift index 3c95344..0c5390c 100644 --- a/CovidSafe/OTPViewController.swift +++ b/CovidSafe/OTPViewController.swift @@ -237,8 +237,6 @@ class OTPViewController: UIViewController, RegistrationHandler { } if !UserDefaults.standard.bool(forKey: "allowedPermissions") { viewController.performSegue(withIdentifier: "showAllowPermissionsFromOTPSegue", sender: self) - } else if !UserDefaults.standard.bool(forKey: "turnedOnBluetooth") { - self.performSegue(withIdentifier: "OTPToTurnOnBtSegue", sender: self) } else { self.performSegue(withIdentifier: "OTPToHomeSegue", sender: self) } diff --git a/CovidSafe/PeripheralController.swift b/CovidSafe/PeripheralController.swift index a7fce5f..e2a158f 100644 --- a/CovidSafe/PeripheralController.swift +++ b/CovidSafe/PeripheralController.swift @@ -20,8 +20,10 @@ public class PeripheralController: NSObject { } var didUpdateState: ((String) -> Void)? + private let encounteredCentralExpiryTime:TimeInterval = 1800.0 // 30 minutes private let restoreIdentifierKey = "com.joelkek.tracer.peripheral" private let peripheralName: String + private var encounteredCentrals = [UUID: (EncounterRecord)]() private var characteristicData: PeripheralCharacteristicsData @@ -99,7 +101,13 @@ extension PeripheralController: CBPeripheralManagerDelegate { if let characteristicValue = request.value { let dataFromCentral = try JSONDecoder().decode(CentralWriteData.self, from: characteristicValue) let encounter = EncounterRecord(from: dataFromCentral) - encounter.saveToCoreData() + if (shouldRecordEncounterWithCentral(request.central)) { + try encounter.saveRemoteCentralToCoreData() + encounteredCentrals.updateValue(encounter, forKey: request.central.identifier) + removeOldEncounters() + } else { + DLog("Encounterd central too recently, not recording") + } } } peripheral.respond(to: requests[0], withResult: .success) @@ -107,6 +115,28 @@ extension PeripheralController: CBPeripheralManagerDelegate { DLog("Error: \(error)") peripheral.respond(to: requests[0], withResult: .unlikelyError) } + } + + private func removeOldEncounters() { + encounteredCentrals = encounteredCentrals.filter { (uuid, encounter) -> Bool in + guard let encDate = encounter.timestamp else { + return true + } + return abs(encDate.timeIntervalSinceNow) < encounteredCentralExpiryTime + } + } + + private func shouldRecordEncounterWithCentral(_ central: CBCentral) -> Bool { + guard let previousEncounter = encounteredCentrals[central.identifier] else { + return true + } + guard let lastEncDate = previousEncounter.timestamp else { + return true + } + if abs(lastEncDate.timeIntervalSinceNow) > BluetraceConfig.CentralScanInterval { + return true + } + return false } } diff --git a/CovidSafe/PushNotificationConstants.swift b/CovidSafe/PushNotificationConstants.swift index f7c0368..fbccade 100644 --- a/CovidSafe/PushNotificationConstants.swift +++ b/CovidSafe/PushNotificationConstants.swift @@ -9,8 +9,8 @@ struct PushNotificationConstants { // Bluetooth Status static let btStatusPushNotifContents = [ [ - "contentTitle": "Turned Bluetooth off by mistake?", - "contentBody": "Help stop the spread of COVID-19 by keeping your phone’s Bluetooth on until the outbreak is over." + "contentTitle": "COVIDSafe is currently inactive", + "contentBody": "Make sure it's active before you leave home and when in public places by enabling Bluetooth®" ] ] diff --git a/CovidSafe/SecKey+CovidSafe.swift b/CovidSafe/SecKey+CovidSafe.swift new file mode 100644 index 0000000..bc696d4 --- /dev/null +++ b/CovidSafe/SecKey+CovidSafe.swift @@ -0,0 +1,67 @@ +// +// SecKey+CovidSafe.swift +// CovidSafe +// +// Copyright © 2020 Australian Government. All rights reserved. +// + +import Foundation + +extension SecKey { + enum SecKeyError: Error { + case UnableToInspectAttributesError + case UnsupportedKeyTypeError + case InvalidUncompressedRepresentationError + } + + /** + Copy the compressed elliptic-curve public key representation as defined in + section 4.3.6 of ANSI X9.62. + + - Throws: + - `SecKeyError.UnableToInspectAttributesError`: if `SecKeyCopyAttributes()` fails on the key + - `SecKeyError.UnsupportedKeyTypeError`: if the key isn't of type `kSecAttrKeyTypeECSECPrimeRandom` + - `SecKeyError.InvalidUncompressedRepresentationError`: if parsing the uncompressed representation + of the key failed (from `SecKeyCopyExternalRepresentation()`) + + - Returns: Data with 1 + key size bytes, representing the public key + */ + func CopyCompressedECPublicKey() throws -> Data { + // Validate the key is of EC type + guard let attributes = SecKeyCopyAttributes(self) as? [CFString: AnyObject] else { + throw SecKeyError.UnableToInspectAttributesError + } + guard attributes[kSecAttrKeyType] as! CFNumber == kSecAttrKeyTypeECSECPrimeRandom else { + throw SecKeyError.UnsupportedKeyTypeError + } + // Get key size in bytes + let keySize = Int(truncating: attributes[kSecAttrKeySizeInBits] as! CFNumber) / 8 + + // Get the uncompressed key representation + var err: Unmanaged? + guard let uncompressed = SecKeyCopyExternalRepresentation(self, &err) as Data? else { + throw err!.takeRetainedValue() as Error + } + // Uncompressed begins with 0x04 per section 4.3.6 of X9.62 + guard uncompressed[0] == 4 else { + throw SecKeyError.InvalidUncompressedRepresentationError + } + // Uncompressed public key will be 1 + keysize * 2, private has K appended to public + guard uncompressed.count >= 1 + keySize * 2 else { + throw SecKeyError.InvalidUncompressedRepresentationError + } + + // To compress, take the X coordinate + let x = uncompressed[1...keySize] + // And determine whether Y coordinate is odd or even + let y_lsb = uncompressed[keySize*2] & 1 + + // Compressed format is PC || X_1 + var compressed = Data(capacity: 1 + keySize) + // PC: 0x02 if even, 0x03 if odd + compressed.append(2 | y_lsb) + compressed.append(x) + + return compressed + } +} diff --git a/CovidSafe/UploadHelper.swift b/CovidSafe/UploadHelper.swift index 105f054..1e8fa35 100644 --- a/CovidSafe/UploadHelper.swift +++ b/CovidSafe/UploadHelper.swift @@ -13,11 +13,12 @@ final class UploadHelper { public static func uploadEncounterData(pin: String?, _ result: @escaping (UploadResult) -> Void) { let keychain = KeychainSwift() - guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { + + guard let managedContext = EncounterDB.shared.persistentContainer?.viewContext else { + result(.Failed) return } - let managedContext = appDelegate.persistentContainer.viewContext let recordsFetchRequest: NSFetchRequest = Encounter.fetchRequestForRecords() diff --git a/CovidSafe/spinner_migrating_db.json b/CovidSafe/spinner_migrating_db.json new file mode 100644 index 0000000..c58ba42 --- /dev/null +++ b/CovidSafe/spinner_migrating_db.json @@ -0,0 +1,5514 @@ +{ + "v": "5.6.9", + "fr": 30, + "ip": 0, + "op": 600, + "w": 260, + "h": 260, + "nm": "Logo_Inside", + "ddd": 0, + "assets": [ + { + "id": "comp_0", + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Logo_Outside Outlines 3", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 599, + "s": [ + -360 + ] + } + ], + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 228.5, + 120, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 228.5, + 120, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 1.779, + 0 + ], + [ + 0, + -1.693 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + -1.647 + ], + [ + -1.685, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1.731, + 0 + ], + [ + 0, + 1.693 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1.647 + ], + [ + 1.685, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + -1.778, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1.732, + 0 + ], + [ + 0, + 1.692 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1.739 + ], + [ + 1.779, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.731, + 0 + ], + [ + 0, + -1.693 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + -1.693 + ] + ], + "v": [ + [ + 108.391, + -11.164 + ], + [ + 105.208, + -8.053 + ], + [ + 105.208, + -2.975 + ], + [ + 99.871, + -2.975 + ], + [ + 96.783, + 0.045 + ], + [ + 99.871, + 3.065 + ], + [ + 105.208, + 3.065 + ], + [ + 105.208, + 8.142 + ], + [ + 108.391, + 11.253 + ], + [ + 111.575, + 8.142 + ], + [ + 111.575, + 3.065 + ], + [ + 116.911, + 3.065 + ], + [ + 120, + 0.045 + ], + [ + 116.911, + -2.975 + ], + [ + 111.575, + -2.975 + ], + [ + 111.575, + -8.053 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 1, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.3137254901960784, + 0.3176470588235294, + 0.3176470588235294, + 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": 0, + "k": [ + 120, + 120 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "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": "Group 1", + "np": 5, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 600, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Logo_Outside Outlines 2", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 599, + "s": [ + -360 + ] + } + ], + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 11.75, + 120, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 11.75, + 120, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -1.732, + 0 + ], + [ + 0, + 1.692 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 1.647 + ], + [ + 1.685, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.732, + 0 + ], + [ + 0, + -1.739 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + -1.647 + ], + [ + -1.685, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 1.737 + ], + [ + 1.733, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.732, + 0 + ], + [ + 0, + -1.692 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + -1.739 + ], + [ + -1.732, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1.732, + 0 + ], + [ + 0, + 1.693 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -111.574, + 8.189 + ], + [ + -108.391, + 11.299 + ], + [ + -105.207, + 8.189 + ], + [ + -105.207, + 3.11 + ], + [ + -99.871, + 3.11 + ], + [ + -96.781, + 0.09 + ], + [ + -99.871, + -2.929 + ], + [ + -105.161, + -2.929 + ], + [ + -105.161, + -8.007 + ], + [ + -108.344, + -11.118 + ], + [ + -111.574, + -8.007 + ], + [ + -111.574, + -2.929 + ], + [ + -116.91, + -2.929 + ], + [ + -120, + 0.09 + ], + [ + -116.91, + 3.11 + ], + [ + -111.574, + 3.11 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 2", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 1, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.3137254901960784, + 0.3176470588235294, + 0.3176470588235294, + 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": 0, + "k": [ + 120, + 120 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "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": "Group 1", + "np": 5, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 600, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Logo_Outside Outlines", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 120, + 120, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 120, + 120, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0.327, + 0.823 + ], + [ + 0, + 0 + ], + [ + 0.935, + -0.32 + ], + [ + 0, + 0 + ], + [ + -0.328, + -0.87 + ], + [ + 0, + 0 + ], + [ + -0.89, + 0.275 + ], + [ + 0.282, + 0.869 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.891, + 0.275 + ], + [ + 0.281, + 0.869 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.889, + 0.275 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + -0.328, + -0.869 + ], + [ + 0, + 0 + ], + [ + -0.936, + 0.32 + ], + [ + 0, + 0 + ], + [ + 0.328, + 0.823 + ], + [ + 0.843, + -0.32 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.328, + 0.823 + ], + [ + 0.795, + -0.274 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.328, + 0.823 + ], + [ + 0.703, + -0.366 + ] + ], + "v": [ + [ + 119.439, + -31.705 + ], + [ + 112.652, + -49.685 + ], + [ + 110.358, + -50.691 + ], + [ + 84.143, + -41.221 + ], + [ + 83.067, + -38.979 + ], + [ + 89.948, + -20.771 + ], + [ + 92.054, + -19.811 + ], + [ + 93.037, + -21.869 + ], + [ + 86.811, + -38.385 + ], + [ + 97.157, + -42.136 + ], + [ + 102.633, + -27.679 + ], + [ + 104.741, + -26.719 + ], + [ + 105.723, + -28.777 + ], + [ + 100.246, + -43.234 + ], + [ + 110.358, + -46.894 + ], + [ + 116.537, + -30.561 + ], + [ + 118.642, + -29.601 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 3", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -0.561, + -0.778 + ], + [ + -0.842, + 0.549 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.702, + 0.503 + ], + [ + 0.515, + 0.687 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.749, + 0.503 + ], + [ + 0.516, + 0.686 + ], + [ + 0, + 0 + ], + [ + 0.795, + -0.595 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0.563, + 0.778 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.516, + 0.732 + ], + [ + 0.749, + -0.504 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.516, + 0.732 + ], + [ + 0.749, + -0.504 + ], + [ + 0, + 0 + ], + [ + -0.563, + -0.732 + ], + [ + 0, + 0 + ], + [ + -0.843, + 0.458 + ] + ], + "v": [ + [ + 69.678, + -58.423 + ], + [ + 72.206, + -58.011 + ], + [ + 82.13, + -64.919 + ], + [ + 91.258, + -52.43 + ], + [ + 93.505, + -52.063 + ], + [ + 93.879, + -54.26 + ], + [ + 84.751, + -66.749 + ], + [ + 93.926, + -73.154 + ], + [ + 104.225, + -59.063 + ], + [ + 106.518, + -58.697 + ], + [ + 106.893, + -60.893 + ], + [ + 95.566, + -76.402 + ], + [ + 93.084, + -76.768 + ], + [ + 70.147, + -60.756 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 4", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 2, + "ty": "sh", + "ix": 3, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 54.606, + -85.781 + ], + [ + 66.402, + -77.5 + ], + [ + 69.959, + -94.427 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 5", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 3, + "ty": "sh", + "ix": 4, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -0.374, + 0.183 + ], + [ + 0, + 0 + ], + [ + -0.936, + -0.641 + ], + [ + 0, + 0 + ], + [ + 0.235, + -1.007 + ], + [ + 0, + 0 + ], + [ + 0.14, + -0.184 + ], + [ + 0.749, + 0.549 + ], + [ + -0.189, + 0.777 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.655, + 0.458 + ], + [ + -0.516, + 0.686 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0.938, + -0.549 + ], + [ + 0, + 0 + ], + [ + 0.888, + 0.641 + ], + [ + 0, + 0 + ], + [ + -0.094, + 0.321 + ], + [ + -0.562, + 0.731 + ], + [ + -0.702, + -0.504 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.702, + 0.412 + ], + [ + -0.749, + -0.503 + ], + [ + 0.093, + -0.183 + ] + ], + "v": [ + [ + 42.856, + -82.944 + ], + [ + 69.631, + -97.813 + ], + [ + 72.393, + -97.767 + ], + [ + 72.535, + -97.676 + ], + [ + 73.423, + -95.159 + ], + [ + 67.478, + -65.697 + ], + [ + 67.151, + -64.873 + ], + [ + 64.763, + -64.507 + ], + [ + 64.109, + -66.566 + ], + [ + 65.7, + -74.206 + ], + [ + 51.657, + -84.088 + ], + [ + 44.681, + -80.154 + ], + [ + 42.576, + -80.154 + ], + [ + 42.201, + -82.35 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 6", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 4, + "ty": "sh", + "ix": 5, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -4.401, + -1.327 + ], + [ + -1.637, + 5.032 + ], + [ + 0, + 0 + ], + [ + 6.227, + 3.523 + ], + [ + -0.89, + 2.791 + ], + [ + 0, + 0 + ], + [ + -3.933, + -1.19 + ], + [ + -1.967, + -2.562 + ], + [ + -0.375, + -0.137 + ], + [ + -0.281, + 0.869 + ], + [ + 0.234, + 0.366 + ], + [ + 3.978, + 1.236 + ], + [ + 1.451, + -4.621 + ], + [ + 0, + 0 + ], + [ + -6.46, + -3.568 + ], + [ + 0.889, + -2.745 + ], + [ + 0, + 0 + ], + [ + 4.074, + 1.236 + ], + [ + 2.059, + 3.202 + ], + [ + 0.421, + 0.137 + ], + [ + 0.28, + -0.915 + ], + [ + -0.28, + -0.366 + ] + ], + "o": [ + [ + 5.992, + 1.83 + ], + [ + 0, + 0 + ], + [ + 1.451, + -4.484 + ], + [ + -6.179, + -3.431 + ], + [ + 0, + 0 + ], + [ + 0.841, + -2.654 + ], + [ + 2.807, + 0.869 + ], + [ + 0.235, + 0.32 + ], + [ + 0.89, + 0.275 + ], + [ + 0.188, + -0.641 + ], + [ + -2.152, + -2.79 + ], + [ + -5.711, + -1.738 + ], + [ + 0, + 0 + ], + [ + -1.545, + 4.85 + ], + [ + 5.899, + 3.248 + ], + [ + 0, + 0 + ], + [ + -0.936, + 2.882 + ], + [ + -3.837, + -1.189 + ], + [ + -0.141, + -0.275 + ], + [ + -0.891, + -0.275 + ], + [ + -0.189, + 0.549 + ], + [ + 2.388, + 3.706 + ] + ], + "v": [ + [ + 28.532, + -84.454 + ], + [ + 41.498, + -89.623 + ], + [ + 41.545, + -89.715 + ], + [ + 34.429, + -101.061 + ], + [ + 28.111, + -109.296 + ], + [ + 28.158, + -109.387 + ], + [ + 36.116, + -112.178 + ], + [ + 43.09, + -107.237 + ], + [ + 43.979, + -106.643 + ], + [ + 46.132, + -107.74 + ], + [ + 45.851, + -109.296 + ], + [ + 37.192, + -115.152 + ], + [ + 24.741, + -110.165 + ], + [ + 24.694, + -110.074 + ], + [ + 32.089, + -98.499 + ], + [ + 38.128, + -90.493 + ], + [ + 38.081, + -90.401 + ], + [ + 29.748, + -87.474 + ], + [ + 21.37, + -93.878 + ], + [ + 20.481, + -94.564 + ], + [ + 18.328, + -93.421 + ], + [ + 18.561, + -91.957 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 7", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 5, + "ty": "sh", + "ix": 6, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0.094, + 5.49 + ], + [ + 0, + 0 + ], + [ + 5.664, + -0.137 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + -0.094, + -5.444 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 5.664, + -0.092 + ] + ], + "v": [ + [ + 6.39, + -104.675 + ], + [ + 6.39, + -104.767 + ], + [ + -3.299, + -113.871 + ], + [ + -8.683, + -113.779 + ], + [ + -8.309, + -95.205 + ], + [ + -2.925, + -95.296 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 8", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 6, + "ty": "sh", + "ix": 7, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -0.187, + -8.646 + ], + [ + 0, + 0 + ], + [ + 9.924, + -0.183 + ], + [ + 0, + 0 + ], + [ + 0.046, + 1.922 + ], + [ + 0, + 0 + ], + [ + -1.966, + 0.046 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0.188, + 8.693 + ], + [ + 0, + 0 + ], + [ + -1.92, + 0.046 + ], + [ + 0, + 0 + ], + [ + -0.047, + -1.876 + ], + [ + 0, + 0 + ], + [ + 9.925, + -0.183 + ] + ], + "v": [ + [ + 13.645, + -104.996 + ], + [ + 13.645, + -104.904 + ], + [ + -2.832, + -89.166 + ], + [ + -11.679, + -88.983 + ], + [ + -15.236, + -92.323 + ], + [ + -15.705, + -116.387 + ], + [ + -12.288, + -119.864 + ], + [ + -3.441, + -120.001 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 9", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 7, + "ty": "sh", + "ix": 8, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 1.872, + -0.549 + ], + [ + -0.561, + -1.83 + ], + [ + 0, + 0 + ], + [ + -1.872, + 0.549 + ], + [ + 0.562, + 1.83 + ] + ], + "o": [ + [ + -0.562, + -1.83 + ], + [ + -1.872, + 0.549 + ], + [ + 0, + 0 + ], + [ + 0.562, + 1.83 + ], + [ + 1.873, + -0.549 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -29.888, + -113.185 + ], + [ + -34.195, + -115.472 + ], + [ + -36.536, + -111.263 + ], + [ + -29.467, + -87.657 + ], + [ + -25.161, + -85.369 + ], + [ + -22.82, + -89.578 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 10", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 8, + "ty": "sh", + "ix": 9, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -0.467, + -0.32 + ], + [ + 0, + 0 + ], + [ + -1.638, + 0.961 + ], + [ + 0, + 0 + ], + [ + 0.188, + 1.83 + ], + [ + 0, + 0 + ], + [ + 0.281, + 0.458 + ], + [ + 1.638, + -0.915 + ], + [ + -0.188, + -1.143 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.545, + -0.869 + ], + [ + -0.983, + -1.647 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 1.497, + 1.099 + ], + [ + 0, + 0 + ], + [ + 1.639, + -0.915 + ], + [ + 0, + 0 + ], + [ + -0.047, + -0.411 + ], + [ + -0.936, + -1.601 + ], + [ + -1.452, + 0.823 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1.171, + -0.915 + ], + [ + -1.685, + 0.961 + ], + [ + 0.281, + 0.595 + ] + ], + "v": [ + [ + -70.521, + -93.238 + ], + [ + -49.502, + -77.409 + ], + [ + -44.728, + -76.997 + ], + [ + -44.353, + -77.226 + ], + [ + -42.294, + -81.48 + ], + [ + -45.57, + -107.466 + ], + [ + -46.038, + -108.93 + ], + [ + -50.672, + -110.119 + ], + [ + -52.451, + -106.643 + ], + [ + -49.128, + -85.232 + ], + [ + -66.12, + -98.728 + ], + [ + -70.38, + -99.094 + ], + [ + -71.691, + -94.519 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 11", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 9, + "ty": "sh", + "ix": 10, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 4.026, + 3.523 + ], + [ + 0, + 0 + ], + [ + 3.698, + -4.071 + ], + [ + -4.025, + -3.522 + ], + [ + 0, + 0 + ], + [ + -3.698, + 4.072 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + -4.026, + -3.523 + ], + [ + -3.698, + 4.072 + ], + [ + 0, + 0 + ], + [ + 4.026, + 3.523 + ], + [ + 3.745, + -4.117 + ] + ], + "v": [ + [ + -73.095, + -76.448 + ], + [ + -73.142, + -76.494 + ], + [ + -86.904, + -75.991 + ], + [ + -85.875, + -62.678 + ], + [ + -85.828, + -62.632 + ], + [ + -72.065, + -63.135 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 12", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 10, + "ty": "sh", + "ix": 11, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 6.506, + -7.137 + ], + [ + 6.741, + 5.856 + ], + [ + 0, + 0 + ], + [ + -6.507, + 7.091 + ], + [ + -6.741, + -5.856 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + -6.46, + 7.091 + ], + [ + 0, + 0 + ], + [ + -6.694, + -5.856 + ], + [ + 6.506, + -7.091 + ], + [ + 0, + 0 + ], + [ + 6.694, + 5.81 + ] + ], + "v": [ + [ + -67.337, + -58.926 + ], + [ + -90.602, + -57.279 + ], + [ + -90.649, + -57.325 + ], + [ + -91.632, + -80.199 + ], + [ + -68.367, + -81.846 + ], + [ + -68.32, + -81.801 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 13", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 11, + "ty": "sh", + "ix": 12, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -8.332, + -3.248 + ], + [ + 0, + 0 + ], + [ + -3.418, + 8.463 + ], + [ + 1.217, + 3.477 + ], + [ + 0.982, + 0.412 + ], + [ + 0.656, + -1.601 + ], + [ + -0.235, + -0.64 + ], + [ + 1.077, + -2.699 + ], + [ + 5.009, + 1.922 + ], + [ + 0, + 0 + ], + [ + -1.966, + 4.85 + ], + [ + -2.06, + 1.236 + ], + [ + -0.375, + 0.869 + ], + [ + 1.732, + 0.686 + ], + [ + 0.749, + -0.458 + ], + [ + 1.779, + -4.301 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 8.426, + 3.248 + ], + [ + 1.872, + -4.575 + ], + [ + -0.281, + -0.778 + ], + [ + -1.639, + -0.64 + ], + [ + -0.281, + 0.732 + ], + [ + 0.749, + 2.379 + ], + [ + -2.012, + 4.896 + ], + [ + 0, + 0 + ], + [ + -5.009, + -1.968 + ], + [ + 0.936, + -2.241 + ], + [ + 0.468, + -0.32 + ], + [ + 0.702, + -1.739 + ], + [ + -1.124, + -0.457 + ], + [ + -2.762, + 1.692 + ], + [ + -3.651, + 8.829 + ] + ], + "v": [ + [ + -104.365, + -25.575 + ], + [ + -104.271, + -25.529 + ], + [ + -83.206, + -34.404 + ], + [ + -82.738, + -46.025 + ], + [ + -84.61, + -47.992 + ], + [ + -88.777, + -46.208 + ], + [ + -88.823, + -44.058 + ], + [ + -89.058, + -36.921 + ], + [ + -101.65, + -32.163 + ], + [ + -101.743, + -32.208 + ], + [ + -107.501, + -44.058 + ], + [ + -103.007, + -49.09 + ], + [ + -101.603, + -50.737 + ], + [ + -103.475, + -55.083 + ], + [ + -106.518, + -54.854 + ], + [ + -113.399, + -46.436 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 14", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 12, + "ty": "sh", + "ix": 13, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 1.59, + 0.595 + ], + [ + 0.609, + -1.555 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.592, + 0.641 + ], + [ + 0.608, + -1.556 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.592, + 0.64 + ], + [ + 0.607, + -1.556 + ], + [ + 0, + 0 + ], + [ + -1.778, + -0.686 + ], + [ + 0, + 0 + ], + [ + -0.702, + 1.738 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + -1.592, + -0.595 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.608, + -1.556 + ], + [ + -1.59, + -0.594 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.608, + -1.555 + ], + [ + -1.591, + -0.594 + ], + [ + 0, + 0 + ], + [ + -0.702, + 1.784 + ], + [ + 0, + 0 + ], + [ + 1.826, + 0.686 + ], + [ + 0, + 0 + ], + [ + 0.655, + -1.601 + ] + ], + "v": [ + [ + 116.865, + 30.651 + ], + [ + 112.838, + 32.389 + ], + [ + 107.642, + 45.428 + ], + [ + 101.463, + 43.095 + ], + [ + 105.817, + 32.115 + ], + [ + 104.084, + 28.18 + ], + [ + 100.059, + 29.919 + ], + [ + 95.705, + 40.899 + ], + [ + 89.76, + 38.611 + ], + [ + 94.863, + 25.801 + ], + [ + 93.13, + 21.867 + ], + [ + 89.106, + 23.606 + ], + [ + 82.739, + 39.526 + ], + [ + 84.704, + 43.918 + ], + [ + 107.642, + 52.656 + ], + [ + 112.136, + 50.735 + ], + [ + 118.549, + 34.585 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 15", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 13, + "ty": "sh", + "ix": 14, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 1.359, + 1.052 + ], + [ + 1.124, + -1.327 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.358, + 1.098 + ], + [ + 1.124, + -1.326 + ], + [ + 0, + 0 + ], + [ + -1.498, + -1.191 + ], + [ + 0, + 0 + ], + [ + -1.217, + 1.464 + ], + [ + 1.498, + 1.19 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + -1.357, + -1.098 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.124, + -1.327 + ], + [ + -1.357, + -1.098 + ], + [ + 0, + 0 + ], + [ + -1.218, + 1.464 + ], + [ + 0, + 0 + ], + [ + 1.498, + 1.19 + ], + [ + 1.216, + -1.464 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.123, + -1.327 + ] + ], + "v": [ + [ + 91.164, + 57.506 + ], + [ + 86.717, + 57.963 + ], + [ + 79.04, + 67.204 + ], + [ + 73.798, + 63.087 + ], + [ + 82.739, + 52.29 + ], + [ + 82.27, + 47.944 + ], + [ + 77.824, + 48.401 + ], + [ + 66.777, + 61.806 + ], + [ + 67.291, + 66.565 + ], + [ + 86.624, + 81.844 + ], + [ + 91.493, + 81.341 + ], + [ + 90.977, + 76.583 + ], + [ + 83.956, + 71.047 + ], + [ + 91.633, + 61.806 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 16", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 14, + "ty": "sh", + "ix": 15, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 65.607, + 86.373 + ], + [ + 56.151, + 80.38 + ], + [ + 58.398, + 91.223 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 17", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 15, + "ty": "sh", + "ix": 16, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -0.233, + -0.366 + ], + [ + 1.545, + -1.007 + ], + [ + 1.358, + 0.824 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.216, + -0.823 + ], + [ + 1.031, + 1.464 + ], + [ + 0.093, + 0.503 + ], + [ + 0, + 0 + ], + [ + -1.593, + 1.007 + ], + [ + 0, + 0 + ], + [ + -1.592, + -0.961 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 1.03, + 1.51 + ], + [ + -1.357, + 0.869 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.28, + 1.419 + ], + [ + -1.499, + 1.007 + ], + [ + -0.281, + -0.412 + ], + [ + 0, + 0 + ], + [ + -0.329, + -1.784 + ], + [ + 0, + 0 + ], + [ + 1.546, + -1.053 + ], + [ + 0, + 0 + ], + [ + 0.516, + 0.275 + ] + ], + "v": [ + [ + 80.632, + 88.34 + ], + [ + 79.789, + 92.824 + ], + [ + 75.67, + 92.732 + ], + [ + 71.129, + 89.897 + ], + [ + 59.662, + 97.536 + ], + [ + 60.738, + 102.934 + ], + [ + 59.287, + 106.457 + ], + [ + 54.792, + 105.588 + ], + [ + 54.278, + 104.17 + ], + [ + 49.41, + 78.047 + ], + [ + 51.236, + 73.609 + ], + [ + 51.562, + 73.381 + ], + [ + 56.431, + 73.381 + ], + [ + 79.508, + 87.425 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 18", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 16, + "ty": "sh", + "ix": 17, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 6.6, + -0.321 + ], + [ + 0.608, + 1.922 + ], + [ + 0, + 0 + ], + [ + -2.527, + 0.778 + ], + [ + -2.621, + -0.595 + ], + [ + -0.657, + 0.183 + ], + [ + 0.516, + 1.693 + ], + [ + 0.891, + 0.183 + ], + [ + 3.652, + -1.144 + ], + [ + -1.686, + -5.353 + ], + [ + 0, + 0 + ], + [ + -6.554, + 0.366 + ], + [ + -0.516, + -1.601 + ], + [ + 0, + 0 + ], + [ + 2.714, + -0.823 + ], + [ + 2.81, + 0.961 + ], + [ + 0.841, + -0.275 + ], + [ + -0.514, + -1.692 + ], + [ + -0.889, + -0.275 + ], + [ + -3.978, + 1.19 + ], + [ + 1.872, + 5.856 + ] + ], + "o": [ + [ + -1.639, + -5.124 + ], + [ + -5.617, + 0.274 + ], + [ + 0, + 0 + ], + [ + -0.468, + -1.418 + ], + [ + 2.06, + -0.64 + ], + [ + 0.61, + 0.137 + ], + [ + 1.731, + -0.503 + ], + [ + -0.421, + -1.281 + ], + [ + -3.229, + -0.87 + ], + [ + -6.131, + 1.875 + ], + [ + 0, + 0 + ], + [ + 1.873, + 5.856 + ], + [ + 5.429, + -0.274 + ], + [ + 0, + 0 + ], + [ + 0.561, + 1.738 + ], + [ + -2.95, + 0.869 + ], + [ + -0.514, + -0.183 + ], + [ + -1.733, + 0.503 + ], + [ + 0.328, + 1.007 + ], + [ + 4.12, + 1.327 + ], + [ + 6.507, + -1.967 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 45.899, + 102.34 + ], + [ + 33.54, + 96.439 + ], + [ + 25.817, + 94.562 + ], + [ + 25.769, + 94.471 + ], + [ + 28.812, + 90.719 + ], + [ + 35.787, + 90.765 + ], + [ + 37.614, + 90.719 + ], + [ + 39.766, + 86.785 + ], + [ + 37.472, + 84.681 + ], + [ + 27.08, + 85.001 + ], + [ + 19.357, + 97.079 + ], + [ + 19.404, + 97.17 + ], + [ + 32.277, + 103.117 + ], + [ + 39.533, + 104.993 + ], + [ + 39.579, + 105.085 + ], + [ + 36.116, + 109.202 + ], + [ + 27.548, + 108.882 + ], + [ + 25.49, + 108.882 + ], + [ + 23.335, + 112.816 + ], + [ + 25.348, + 114.875 + ], + [ + 37.8, + 114.966 + ], + [ + 45.946, + 102.477 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 19", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 17, + "ty": "sh", + "ix": 18, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 7.959, + 0.183 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.187, + 6.817 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 7.958, + 0.183 + ], + [ + 0, + 0 + ], + [ + 0.141, + -6.817 + ] + ], + "v": [ + [ + -4.143, + 92.412 + ], + [ + -11.539, + 92.229 + ], + [ + -12.147, + 116.613 + ], + [ + -4.751, + 116.796 + ], + [ + 8.637, + 104.993 + ], + [ + 8.637, + 104.902 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 20", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 18, + "ty": "sh", + "ix": 19, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0.235, + -8.647 + ], + [ + 0, + 0 + ], + [ + 9.925, + 0.229 + ], + [ + 0, + 0 + ], + [ + 0, + 0.961 + ], + [ + 0, + 0 + ], + [ + -0.983, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + -0.234, + 8.692 + ], + [ + 0, + 0 + ], + [ + -0.936, + -0.046 + ], + [ + 0, + 0 + ], + [ + 0.048, + -0.961 + ], + [ + 0, + 0 + ], + [ + 9.924, + 0.275 + ] + ], + "v": [ + [ + 12.288, + 104.902 + ], + [ + 12.288, + 104.993 + ], + [ + -4.892, + 119.999 + ], + [ + -14.067, + 119.77 + ], + [ + -15.798, + 117.986 + ], + [ + -15.097, + 90.674 + ], + [ + -13.271, + 88.935 + ], + [ + -4.096, + 89.164 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 21", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 19, + "ty": "sh", + "ix": 20, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0.936, + 0.275 + ], + [ + 0.28, + -0.961 + ], + [ + 0, + 0 + ], + [ + -0.936, + -0.229 + ], + [ + -0.281, + 0.961 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + -0.89, + -0.274 + ], + [ + 0, + 0 + ], + [ + -0.281, + 0.915 + ], + [ + 0.936, + 0.275 + ], + [ + 0, + 0 + ], + [ + 0.233, + -0.869 + ] + ], + "v": [ + [ + -24.458, + 85.641 + ], + [ + -26.658, + 86.877 + ], + [ + -34.429, + 113.594 + ], + [ + -33.212, + 115.744 + ], + [ + -30.965, + 114.509 + ], + [ + -23.194, + 87.791 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 22", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 20, + "ty": "sh", + "ix": 21, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -0.141, + 0.229 + ], + [ + 0.843, + 0.457 + ], + [ + 0.608, + -0.412 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.796, + 0.457 + ], + [ + 0.421, + -0.732 + ], + [ + 0, + -0.274 + ], + [ + 0, + 0 + ], + [ + -0.936, + -0.503 + ], + [ + 0, + 0 + ], + [ + -0.796, + 0.549 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0.467, + -0.777 + ], + [ + -0.702, + -0.412 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.093, + -0.823 + ], + [ + -0.842, + -0.504 + ], + [ + -0.14, + 0.275 + ], + [ + 0, + 0 + ], + [ + -0.14, + 0.961 + ], + [ + 0, + 0 + ], + [ + 0.889, + 0.503 + ], + [ + 0, + 0 + ], + [ + 0.14, + -0.275 + ] + ], + "v": [ + [ + -33.071, + 85.824 + ], + [ + -33.727, + 83.537 + ], + [ + -35.88, + 83.766 + ], + [ + -58.443, + 100.693 + ], + [ + -54.885, + 73.243 + ], + [ + -55.822, + 71.185 + ], + [ + -58.256, + 71.779 + ], + [ + -58.49, + 72.557 + ], + [ + -61.907, + 102.294 + ], + [ + -60.831, + 104.581 + ], + [ + -60.69, + 104.673 + ], + [ + -58.162, + 104.444 + ], + [ + -33.493, + 86.465 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 23", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 21, + "ty": "sh", + "ix": 22, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 4.775, + 5.307 + ], + [ + 5.383, + -4.62 + ], + [ + 0, + 0 + ], + [ + -4.775, + -5.261 + ], + [ + -5.384, + 4.621 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + -4.775, + -5.307 + ], + [ + 0, + 0 + ], + [ + -5.384, + 4.621 + ], + [ + 4.775, + 5.307 + ], + [ + 0, + 0 + ], + [ + 5.384, + -4.621 + ] + ], + "v": [ + [ + -70.146, + 60.754 + ], + [ + -88.028, + 60.067 + ], + [ + -88.074, + 60.113 + ], + [ + -89.619, + 77.635 + ], + [ + -71.737, + 78.321 + ], + [ + -71.691, + 78.276 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 24", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 22, + "ty": "sh", + "ix": 23, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -6.366, + -7.045 + ], + [ + 6.507, + -5.581 + ], + [ + 0, + 0 + ], + [ + 6.366, + 7.045 + ], + [ + -6.507, + 5.627 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 6.366, + 7.045 + ], + [ + 0, + 0 + ], + [ + -6.506, + 5.627 + ], + [ + -6.366, + -7.045 + ], + [ + 0, + 0 + ], + [ + 6.553, + -5.581 + ] + ], + "v": [ + [ + -67.618, + 58.695 + ], + [ + -69.163, + 80.929 + ], + [ + -69.21, + 80.975 + ], + [ + -92.147, + 79.694 + ], + [ + -90.602, + 57.414 + ], + [ + -90.555, + 57.368 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 25", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 23, + "ty": "sh", + "ix": 24, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -2.528, + -6.314 + ], + [ + -3.324, + -1.555 + ], + [ + -0.187, + -0.412 + ], + [ + 0.843, + -0.32 + ], + [ + 0.374, + 0.182 + ], + [ + 1.873, + 4.712 + ], + [ + -8.426, + 3.249 + ], + [ + 0, + 0 + ], + [ + -3.418, + -8.463 + ], + [ + 1.17, + -3.524 + ], + [ + 0.608, + -0.228 + ], + [ + 0.327, + 0.869 + ], + [ + -0.094, + 0.274 + ], + [ + 1.451, + 3.523 + ], + [ + 6.741, + -2.608 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 1.451, + 3.568 + ], + [ + 0.327, + 0.138 + ], + [ + 0.327, + 0.823 + ], + [ + -0.515, + 0.183 + ], + [ + -3.792, + -1.831 + ], + [ + -3.37, + -8.281 + ], + [ + 0, + 0 + ], + [ + 8.285, + -3.203 + ], + [ + 1.872, + 4.621 + ], + [ + -0.14, + 0.456 + ], + [ + -0.89, + 0.321 + ], + [ + -0.187, + -0.458 + ], + [ + 1.123, + -2.974 + ], + [ + -2.575, + -6.359 + ], + [ + 0, + 0 + ], + [ + -6.788, + 2.562 + ] + ], + "v": [ + [ + -110.45, + 45.291 + ], + [ + -103.428, + 52.427 + ], + [ + -102.539, + 53.297 + ], + [ + -103.522, + 55.447 + ], + [ + -104.926, + 55.356 + ], + [ + -113.54, + 46.389 + ], + [ + -104.271, + 26.167 + ], + [ + -104.177, + 26.122 + ], + [ + -83.112, + 34.768 + ], + [ + -82.785, + 46.481 + ], + [ + -83.861, + 47.669 + ], + [ + -86.155, + 46.663 + ], + [ + -86.202, + 45.474 + ], + [ + -86.202, + 35.958 + ], + [ + -102.726, + 29.507 + ], + [ + -102.82, + 29.553 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 26", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 1, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.3137254901960784, + 0.3176470588235294, + 0.3176470588235294, + 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": 0, + "k": [ + 120, + 120 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "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": "Group 1", + "np": 28, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 600, + "st": 0, + "bm": 0 + } + ] + } + ], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Logo_Inside Outlines", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 130, + 130, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 120, + 120, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 1.685, + 0 + ], + [ + 0, + 0 + ], + [ + 1.17, + 1.236 + ], + [ + 0, + 0 + ], + [ + -2.574, + 2.333 + ], + [ + -2.387, + -2.516 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -2.481, + -2.379 + ], + [ + 2.434, + -2.425 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + -1.732, + -0.046 + ], + [ + 0, + 0 + ], + [ + -2.387, + -2.469 + ], + [ + 2.528, + -2.333 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 2.434, + -2.425 + ], + [ + 2.481, + 2.379 + ], + [ + 0, + 0 + ], + [ + -1.217, + 1.143 + ] + ], + "v": [ + [ + -21.112, + 38.727 + ], + [ + -21.205, + 38.727 + ], + [ + -25.699, + 36.759 + ], + [ + -48.918, + 12.466 + ], + [ + -48.59, + 3.774 + ], + [ + -39.696, + 4.095 + ], + [ + -20.924, + 23.721 + ], + [ + 39.93, + -36.302 + ], + [ + 48.824, + -36.348 + ], + [ + 48.871, + -27.655 + ], + [ + -16.618, + 36.943 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.3137254901960784, + 0.3176470588235294, + 0.3176470588235294, + 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": 0, + "k": [ + 138.055, + 111.631 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "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": "Group 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + -1.732, + 2.974 + ], + [ + 0, + 0 + ], + [ + 3.511, + 0 + ], + [ + 0, + 0 + ], + [ + -1.733, + -2.928 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 1.731, + -2.974 + ], + [ + 0, + 0 + ], + [ + -3.463, + 0 + ], + [ + 0, + 0 + ], + [ + 1.779, + 2.974 + ] + ], + "v": [ + [ + 42.528, + 58.17 + ], + [ + 45.945, + 52.36 + ], + [ + 42.012, + 45.726 + ], + [ + 35.178, + 45.726 + ], + [ + 31.247, + 52.36 + ], + [ + 34.664, + 58.17 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -4.072, + 3.705 + ], + [ + -3.792, + -3.934 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.654, + -2.607 + ], + [ + 0, + 0 + ], + [ + 1.639, + 0.412 + ], + [ + 0, + 0 + ], + [ + -0.421, + 1.601 + ], + [ + 0, + 0 + ], + [ + 1.966, + 0 + ], + [ + 0, + 0 + ], + [ + 0.562, + -0.457 + ], + [ + 0, + 0 + ], + [ + -1.03, + -2.333 + ], + [ + 0, + 0 + ], + [ + -4.728, + 2.837 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + -3.791, + -3.98 + ], + [ + 4.073, + -3.706 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1.31, + -2.379 + ], + [ + 0, + 0 + ], + [ + -0.421, + 1.602 + ], + [ + 0, + 0 + ], + [ + -1.638, + -0.412 + ], + [ + 0, + 0 + ], + [ + 0.469, + -1.876 + ], + [ + 0, + 0 + ], + [ + -0.701, + 0 + ], + [ + 0, + 0 + ], + [ + -2.012, + 1.693 + ], + [ + 0, + 0 + ], + [ + 2.153, + 4.986 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -33.4, + 6.656 + ], + [ + -32.885, + -7.251 + ], + [ + -18.654, + -6.794 + ], + [ + -2.598, + 10.042 + ], + [ + 47.021, + -38.91 + ], + [ + 35.974, + -58.765 + ], + [ + 30.404, + -58.079 + ], + [ + 25.957, + -40.557 + ], + [ + 22.305, + -38.407 + ], + [ + 6.764, + -42.158 + ], + [ + 4.563, + -45.726 + ], + [ + 6.061, + -51.674 + ], + [ + 3.113, + -55.334 + ], + [ + -12.242, + -55.334 + ], + [ + -14.207, + -54.602 + ], + [ + -66.402, + -10.866 + ], + [ + -68.04, + -4.095 + ], + [ + -54.371, + 27.381 + ], + [ + -41.311, + 31.452 + ], + [ + -21.229, + 19.42 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 2", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 2, + "ty": "sh", + "ix": 3, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0.844, + -1.693 + ], + [ + 0, + 0 + ], + [ + 2.294, + 0 + ], + [ + 0, + 0 + ], + [ + 1.124, + 1.19 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0.935, + 1.647 + ], + [ + 0, + 0 + ], + [ + -0.982, + 2.058 + ], + [ + 0, + 0 + ], + [ + -1.686, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 68.087, + -0.984 + ], + [ + 68.226, + 4.369 + ], + [ + 51.515, + 38.681 + ], + [ + 46.086, + 42.066 + ], + [ + 30.545, + 42.066 + ], + [ + 26.143, + 40.19 + ], + [ + 11.165, + 24.498 + ], + [ + 57.086, + -20.793 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 3", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 1, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.3137254901960784, + 0.3176470588235294, + 0.3176470588235294, + 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": 0, + "k": [ + 119.776, + 119.958 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "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": "Group 2", + "np": 5, + "cix": 2, + "bm": 0, + "ix": 2, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 600, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 0, + "nm": "Pre-comp 1", + "refId": "comp_0", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 599, + "s": [ + 360 + ] + } + ], + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 130, + 130, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 120, + 120, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "w": 240, + "h": 240, + "ip": 0, + "op": 600, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} \ No newline at end of file diff --git a/CovidSafe/tracer.xcdatamodeld/ModelV2.xcdatamodel/contents b/CovidSafe/tracer.xcdatamodeld/ModelV2.xcdatamodel/contents new file mode 100644 index 0000000..ddfa3b8 --- /dev/null +++ b/CovidSafe/tracer.xcdatamodeld/ModelV2.xcdatamodel/contents @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Podfile.lock b/Podfile.lock index 1b23818..0c42236 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -19,6 +19,6 @@ SPEC CHECKSUMS: KeychainSwift: a06190cf933ad46b1e0abc3d77d29c06331715c7 lottie-ios: 85ce835dd8c53e02509f20729fc7d6a4e6645a0a -PODFILE CHECKSUM: 8bc87fadafd4dd4f9d03ad3edbfbd84d778c1b19 +PODFILE CHECKSUM: 267f7fcda5f8a86683b76b362dfc6c6703e3033f COCOAPODS: 1.9.1