COVIDSafe code from version 2.0 (#37)

This commit is contained in:
COVIDSafe Support 2020-12-19 16:13:44 +11:00 committed by GitHub
parent cf93ea43c0
commit 8b75c1fc6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 4624 additions and 1117 deletions

View file

@ -36,6 +36,38 @@
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 */; };
5905460C2543E0F5009B82AD /* BLESensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545EE2543E0F5009B82AD /* BLESensor.swift */; };
5905460D2543E0F5009B82AD /* BLESensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545EE2543E0F5009B82AD /* BLESensor.swift */; };
5905460E2543E0F5009B82AD /* BLEDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545EF2543E0F5009B82AD /* BLEDatabase.swift */; };
5905460F2543E0F5009B82AD /* BLEDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545EF2543E0F5009B82AD /* BLEDatabase.swift */; };
590546102543E0F5009B82AD /* BLEUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545F02543E0F5009B82AD /* BLEUtilities.swift */; };
590546112543E0F5009B82AD /* BLEUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545F02543E0F5009B82AD /* BLEUtilities.swift */; };
590546122543E0F6009B82AD /* BLETransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545F12543E0F5009B82AD /* BLETransmitter.swift */; };
590546132543E0F6009B82AD /* BLETransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545F12543E0F5009B82AD /* BLETransmitter.swift */; };
590546142543E0F6009B82AD /* BLEReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545F22543E0F5009B82AD /* BLEReceiver.swift */; };
590546152543E0F6009B82AD /* BLEReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545F22543E0F5009B82AD /* BLEReceiver.swift */; };
590546162543E0F6009B82AD /* AwakeSensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545F42543E0F5009B82AD /* AwakeSensor.swift */; };
590546172543E0F6009B82AD /* AwakeSensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545F42543E0F5009B82AD /* AwakeSensor.swift */; };
590546182543E0F6009B82AD /* Sensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545F52543E0F5009B82AD /* Sensor.swift */; };
590546192543E0F6009B82AD /* Sensor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590545F52543E0F5009B82AD /* Sensor.swift */; };
590546282543E0F6009B82AD /* SensorArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546012543E0F5009B82AD /* SensorArray.swift */; };
590546292543E0F6009B82AD /* SensorArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546012543E0F5009B82AD /* SensorArray.swift */; };
5905462A2543E0F6009B82AD /* PayloadDataSupplier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546022543E0F5009B82AD /* PayloadDataSupplier.swift */; };
5905462B2543E0F6009B82AD /* PayloadDataSupplier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546022543E0F5009B82AD /* PayloadDataSupplier.swift */; };
5905462C2543E0F6009B82AD /* TextFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546042543E0F5009B82AD /* TextFile.swift */; };
5905462D2543E0F6009B82AD /* TextFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546042543E0F5009B82AD /* TextFile.swift */; };
5905462E2543E0F6009B82AD /* BatteryLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546052543E0F5009B82AD /* BatteryLog.swift */; };
5905462F2543E0F6009B82AD /* BatteryLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546052543E0F5009B82AD /* BatteryLog.swift */; };
590546302543E0F6009B82AD /* DetectionLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546062543E0F5009B82AD /* DetectionLog.swift */; };
590546312543E0F6009B82AD /* DetectionLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546062543E0F5009B82AD /* DetectionLog.swift */; };
590546322543E0F6009B82AD /* SensorLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546072543E0F5009B82AD /* SensorLogger.swift */; };
590546332543E0F6009B82AD /* SensorLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546072543E0F5009B82AD /* SensorLogger.swift */; };
590546342543E0F6009B82AD /* ContactLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546082543E0F5009B82AD /* ContactLog.swift */; };
590546352543E0F6009B82AD /* ContactLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546082543E0F5009B82AD /* ContactLog.swift */; };
590546362543E0F6009B82AD /* StatisticsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546092543E0F5009B82AD /* StatisticsLog.swift */; };
590546372543E0F6009B82AD /* StatisticsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590546092543E0F5009B82AD /* StatisticsLog.swift */; };
590546382543E0F6009B82AD /* SensorDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5905460A2543E0F5009B82AD /* SensorDelegate.swift */; };
590546392543E0F6009B82AD /* SensorDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5905460A2543E0F5009B82AD /* SensorDelegate.swift */; };
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 */; };
@ -96,6 +128,7 @@
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 */; };
59F25D6F245BED80002A7ED8 /* Debug.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 59F25D6E245BED80002A7ED8 /* Debug.storyboard */; };
5ADBDDED2579A7DC0078872F /* EncounterMessageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5F83AC23F023F600770DEF /* EncounterMessageManager.swift */; };
5B04074124BC0CEA00FAAFD0 /* MessageAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B04074024BC0CEA00FAAFD0 /* MessageAPI.swift */; };
5B04074224BC0CEA00FAAFD0 /* MessageAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B04074024BC0CEA00FAAFD0 /* MessageAPI.swift */; };
5B110C10248F275B00B68291 /* SelectCountryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B110C0F248F275A00B68291 /* SelectCountryViewController.swift */; };
@ -167,12 +200,11 @@
5B92D67F243018040049877B /* PogoInstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F19B4DC23F565850071A11E /* PogoInstructionsViewController.swift */; };
5B92D680243018040049877B /* CodeInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E91BEA23EFEA0B002D592A /* CodeInputView.swift */; };
5B92D681243018040049877B /* EncounterRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BE1CAE23F1349F005DCE4F /* EncounterRecord.swift */; };
5B92D683243018040049877B /* OnboardingStep2bViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58D817623F169DB00345771 /* OnboardingStep2bViewController.swift */; };
5B92D683243018040049877B /* RegistrationSuccessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58D817623F169DB00345771 /* RegistrationSuccessViewController.swift */; };
5B92D684243018040049877B /* AsyncAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D2F9242DF1B000DC9E2A /* AsyncAction.swift */; };
5B92D685243018040049877B /* UILabelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60F8BE3242659810007A641 /* UILabelExtension.swift */; };
5B92D686243018040049877B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DD41D3A23DCB03B00FD4AB0 /* AppDelegate.swift */; };
5B92D687243018040049877B /* PhoneNumberViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8B248A23F146C500DBB74D /* PhoneNumberViewController.swift */; };
5B92D688243018040049877B /* BluetraceUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DC7B8D2242B8D87008E1715 /* BluetraceUtils.swift */; };
5B92D689243018040049877B /* NewFeedbackFlowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D302242DF1B000DC9E2A /* NewFeedbackFlowController.swift */; };
5B92D68A243018040049877B /* Outcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D2EE242DF1B000DC9E2A /* Outcome.swift */; };
5B92D68B243018040049877B /* Encounter+EncounterRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BE1CB023F134D0005DCE4F /* Encounter+EncounterRecord.swift */; };
@ -184,9 +216,8 @@
5B92D691243018040049877B /* RespondToAuthChallengeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12C4C0242F047F007E893B /* RespondToAuthChallengeAPI.swift */; };
5B92D692243018040049877B /* BluetraceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D269C0C23E2958F00ADF2DE /* BluetraceManager.swift */; };
5B92D693243018040049877B /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D2F0242DF1B000DC9E2A /* UIColor+Hex.swift */; };
5B92D694243018040049877B /* OnboardingStep1ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5046D5B23EF18600046E96D /* OnboardingStep1ViewController.swift */; };
5B92D694243018040049877B /* RegistrationIntroViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5046D5B23EF18600046E96D /* RegistrationIntroViewController.swift */; };
5B92D695243018040049877B /* DeviceInfoExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D2F5242DF1B000DC9E2A /* DeviceInfoExtension.swift */; };
5B92D697243018040049877B /* PeripheralController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DD41D5223DD4CA400FD4AB0 /* PeripheralController.swift */; };
5B92D698243018040049877B /* SendFeedbackAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D2FA242DF1B000DC9E2A /* SendFeedbackAction.swift */; };
5B92D699243018040049877B /* UITextViewFixed.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D209FB23F4476F007233BE /* UITextViewFixed.swift */; };
5B92D69A243018040049877B /* GradientButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C585C83A23EEB99B0061B7C6 /* GradientButton.swift */; };
@ -198,9 +229,8 @@
5B92D6A1243018040049877B /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D2F6242DF1B000DC9E2A /* AlertController.swift */; };
5B92D6A2243018040049877B /* GetTempIdAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12C4C2242F0FE9007E893B /* GetTempIdAPI.swift */; };
5B92D6A3243018040049877B /* PhoneValidationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFFD94A242EC120003AEF4F /* PhoneValidationAPI.swift */; };
5B92D6A4243018040049877B /* CentralController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DD41D4E23DCB05600FD4AB0 /* CentralController.swift */; };
5B92D6A5243018040049877B /* UIWindow+TopMost.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D305242DF1B000DC9E2A /* UIWindow+TopMost.swift */; };
5B92D6A6243018040049877B /* OnboardingStep1bViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DEB6812423AE2E00D99925 /* OnboardingStep1bViewController.swift */; };
5B92D6A6243018040049877B /* HowItWorksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DEB6812423AE2E00D99925 /* HowItWorksViewController.swift */; };
5B92D6A7243018040049877B /* OnboardingStep4ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56CF43E23F18A15006B05B0 /* OnboardingStep4ViewController.swift */; };
5B92D6A8243018040049877B /* PresentFeedbackExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D2FD242DF1B000DC9E2A /* PresentFeedbackExtensions.swift */; };
5B92D6A9243018040049877B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D304242DF1B000DC9E2A /* Errors.swift */; };
@ -210,7 +240,7 @@
5B92D6AD243018040049877B /* FeedbackSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D301242DF1B000DC9E2A /* FeedbackSettings.swift */; };
5B92D6AE243018040049877B /* JMCTargetJSONFromDisk.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D2EF242DF1B000DC9E2A /* JMCTargetJSONFromDisk.swift */; };
5B92D6B0243018040049877B /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D309242DF1B000DC9E2A /* Action.swift */; };
5B92D6B1243018040049877B /* OnboardingStep2ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2F0BA123EFFF75006D7404 /* OnboardingStep2ViewController.swift */; };
5B92D6B1243018040049877B /* AppSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2F0BA123EFFF75006D7404 /* AppSettingsViewController.swift */; };
5B92D6B2243018040049877B /* HTTPPostFeedbackAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A767D2F4242DF1B000DC9E2A /* HTTPPostFeedbackAction.swift */; };
5B92D6B3243018040049877B /* PushNotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F36305E23F7F81400CC6E1D /* PushNotificationConstants.swift */; };
5B92D6B4243018040049877B /* UIViewController + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B615C5A823FA403400345969 /* UIViewController + Extension.swift */; };
@ -242,6 +272,11 @@
5BA33A7E24B55E7D00D12515 /* BLELogRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA33A7D24B55E7D00D12515 /* BLELogRecord.swift */; };
5BA33A8024B55EF200D12515 /* BLELogDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA33A7F24B55EF200D12515 /* BLELogDB.swift */; };
5BA33A8224B5602500D12515 /* BLELogRecord+BLELogSave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA33A8124B5602500D12515 /* BLELogRecord+BLELogSave.swift */; };
5BBC571D25526F99005E90AA /* staging-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BBC571C25526F99005E90AA /* staging-Info.plist */; };
5BBE61B125633B6D00B8C983 /* CSGenericContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BBE61B025633B6D00B8C983 /* CSGenericContentView.xib */; };
5BBE61B225633B6D00B8C983 /* CSGenericContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BBE61B025633B6D00B8C983 /* CSGenericContentView.xib */; };
5BBE61B725633E8D00B8C983 /* CSGenericViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBE61B625633E8D00B8C983 /* CSGenericViewController.swift */; };
5BBE61B825633E8D00B8C983 /* CSGenericViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBE61B625633E8D00B8C983 /* CSGenericViewController.swift */; };
5BD3EE8324330E1A0004A007 /* UploadDataStep2VC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD3EE8224330E1A0004A007 /* UploadDataStep2VC.swift */; };
5BD3EE84243313450004A007 /* UploadDataStep2VC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD3EE8224330E1A0004A007 /* UploadDataStep2VC.swift */; };
5BED1E3A24A96D1C0066C4D2 /* LaunchScreen_ar.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BED1E3924A96D1C0066C4D2 /* LaunchScreen_ar.storyboard */; };
@ -257,22 +292,18 @@
5BFFD94B242EC120003AEF4F /* PhoneValidationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFFD94A242EC120003AEF4F /* PhoneValidationAPI.swift */; };
5D269C0B23E22CC400ADF2DE /* DeviceIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D269C0A23E22CC400ADF2DE /* DeviceIdentifier.swift */; };
5D269C0D23E2958F00ADF2DE /* BluetraceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D269C0C23E2958F00ADF2DE /* BluetraceManager.swift */; };
5D5F83AD23F023F600770DEF /* EncounterMessageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5F83AC23F023F600770DEF /* EncounterMessageManager.swift */; };
5D5F83B023F045A800770DEF /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5F83AF23F045A800770DEF /* HomeViewController.swift */; };
5D8B248B23F146C500DBB74D /* PhoneNumberViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8B248A23F146C500DBB74D /* PhoneNumberViewController.swift */; };
5D8DD06123E319B300E097EF /* Encounter+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8DD05F23E319B300E097EF /* Encounter+CoreDataClass.swift */; };
5D8DD06223E319B300E097EF /* Encounter+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8DD06023E319B300E097EF /* Encounter+CoreDataProperties.swift */; };
5D8DD06723E329A200E097EF /* tracer.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 5DD41D7723DE141700FD4AB0 /* tracer.xcdatamodeld */; };
5DC7B8D1242B8536008E1715 /* BluetraceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DC7B8D0242B8536008E1715 /* BluetraceConfig.swift */; };
5DC7B8D3242B8D87008E1715 /* BluetraceUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DC7B8D2242B8D87008E1715 /* BluetraceUtils.swift */; };
5DD41D3B23DCB03B00FD4AB0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DD41D3A23DCB03B00FD4AB0 /* AppDelegate.swift */; };
5DD41D4223DCB03B00FD4AB0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5DD41D4023DCB03B00FD4AB0 /* Main.storyboard */; };
5DD41D4423DCB03D00FD4AB0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5DD41D4323DCB03D00FD4AB0 /* Assets.xcassets */; };
5DD41D4723DCB03D00FD4AB0 /* LaunchScreen_en.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5DD41D4523DCB03D00FD4AB0 /* LaunchScreen_en.storyboard */; };
5DD41D4F23DCB05600FD4AB0 /* CentralController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DD41D4E23DCB05600FD4AB0 /* CentralController.swift */; };
5DD41D5323DD4CA400FD4AB0 /* PeripheralController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DD41D5223DD4CA400FD4AB0 /* PeripheralController.swift */; };
7F19B4DD23F565850071A11E /* PogoInstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F19B4DC23F565850071A11E /* PogoInstructionsViewController.swift */; };
7F2F0BA223EFFF75006D7404 /* OnboardingStep2ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2F0BA123EFFF75006D7404 /* OnboardingStep2ViewController.swift */; };
7F2F0BA223EFFF75006D7404 /* AppSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2F0BA123EFFF75006D7404 /* AppSettingsViewController.swift */; };
7F36305F23F7F81400CC6E1D /* PushNotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F36305E23F7F81400CC6E1D /* PushNotificationConstants.swift */; };
7FACD53A23F25A9A0042A33A /* InitialScreenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FACD53923F25A9A0042A33A /* InitialScreenViewController.swift */; };
7FEC361523F16A1E00127AFB /* UIViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FEC361423F16A1E00127AFB /* UIViewExtension.swift */; };
@ -307,19 +338,19 @@
B60F8BE4242659810007A641 /* UILabelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60F8BE3242659810007A641 /* UILabelExtension.swift */; };
B615C5A723F8EB1700345969 /* UIProgressView + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B615C5A623F8EB1700345969 /* UIProgressView + Extension.swift */; };
B615C5A923FA403500345969 /* UIViewController + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B615C5A823FA403400345969 /* UIViewController + Extension.swift */; };
C5046D5C23EF18600046E96D /* OnboardingStep1ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5046D5B23EF18600046E96D /* OnboardingStep1ViewController.swift */; };
C5046D5C23EF18600046E96D /* RegistrationIntroViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5046D5B23EF18600046E96D /* RegistrationIntroViewController.swift */; };
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 */; };
C58D817723F169DB00345771 /* RegistrationSuccessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58D817623F169DB00345771 /* RegistrationSuccessViewController.swift */; };
C5D209FC23F4476F007233BE /* UITextViewFixed.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5D209FB23F4476F007233BE /* UITextViewFixed.swift */; };
D8DEB6822423AE2E00D99925 /* OnboardingStep1bViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DEB6812423AE2E00D99925 /* OnboardingStep1bViewController.swift */; };
D8DEB6822423AE2E00D99925 /* HowItWorksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DEB6812423AE2E00D99925 /* HowItWorksViewController.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 */; };
FBBBFCE82430A933002B174D /* OnboardingStep1aViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12C4C424304AF0007E893B /* OnboardingStep1aViewController.swift */; };
FB12C4C524304AF0007E893B /* PrivacyPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12C4C424304AF0007E893B /* PrivacyPolicyViewController.swift */; };
FBBBFCE82430A933002B174D /* PrivacyPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12C4C424304AF0007E893B /* PrivacyPolicyViewController.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -353,6 +384,23 @@
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 = "<group>"; };
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 = "<group>"; };
5904A5C12462471A008C8012 /* EncounterV1toV2.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = EncounterV1toV2.xcmappingmodel; sourceTree = "<group>"; };
590545EE2543E0F5009B82AD /* BLESensor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BLESensor.swift; sourceTree = "<group>"; };
590545EF2543E0F5009B82AD /* BLEDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BLEDatabase.swift; sourceTree = "<group>"; };
590545F02543E0F5009B82AD /* BLEUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BLEUtilities.swift; sourceTree = "<group>"; };
590545F12543E0F5009B82AD /* BLETransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BLETransmitter.swift; sourceTree = "<group>"; };
590545F22543E0F5009B82AD /* BLEReceiver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BLEReceiver.swift; sourceTree = "<group>"; };
590545F42543E0F5009B82AD /* AwakeSensor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AwakeSensor.swift; sourceTree = "<group>"; };
590545F52543E0F5009B82AD /* Sensor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sensor.swift; sourceTree = "<group>"; };
590546012543E0F5009B82AD /* SensorArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorArray.swift; sourceTree = "<group>"; };
590546022543E0F5009B82AD /* PayloadDataSupplier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayloadDataSupplier.swift; sourceTree = "<group>"; };
590546042543E0F5009B82AD /* TextFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFile.swift; sourceTree = "<group>"; };
590546052543E0F5009B82AD /* BatteryLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryLog.swift; sourceTree = "<group>"; };
590546062543E0F5009B82AD /* DetectionLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetectionLog.swift; sourceTree = "<group>"; };
590546072543E0F5009B82AD /* SensorLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorLogger.swift; sourceTree = "<group>"; };
590546082543E0F5009B82AD /* ContactLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactLog.swift; sourceTree = "<group>"; };
590546092543E0F5009B82AD /* StatisticsLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatisticsLog.swift; sourceTree = "<group>"; };
5905460A2543E0F5009B82AD /* SensorDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorDelegate.swift; sourceTree = "<group>"; };
5905460B2543E0F5009B82AD /* herald.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = herald.h; sourceTree = "<group>"; };
5909E4AA245043C400D41C26 /* CovidPersistentContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CovidPersistentContainer.swift; sourceTree = "<group>"; };
592CBB7F2441A583001FFCE9 /* PersonalDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalDetailsViewController.swift; sourceTree = "<group>"; };
59490B61245FE22C00C9802B /* ModelV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ModelV2.xcdatamodel; sourceTree = "<group>"; };
@ -432,6 +480,9 @@
5BA33A7D24B55E7D00D12515 /* BLELogRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BLELogRecord.swift; sourceTree = "<group>"; };
5BA33A7F24B55EF200D12515 /* BLELogDB.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BLELogDB.swift; sourceTree = "<group>"; };
5BA33A8124B5602500D12515 /* BLELogRecord+BLELogSave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BLELogRecord+BLELogSave.swift"; sourceTree = "<group>"; };
5BBC571C25526F99005E90AA /* staging-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "staging-Info.plist"; sourceTree = "<group>"; };
5BBE61B025633B6D00B8C983 /* CSGenericContentView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CSGenericContentView.xib; sourceTree = "<group>"; };
5BBE61B625633E8D00B8C983 /* CSGenericViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CSGenericViewController.swift; sourceTree = "<group>"; };
5BD3EE8224330E1A0004A007 /* UploadDataStep2VC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadDataStep2VC.swift; sourceTree = "<group>"; };
5BED1E2F24A95ECB0066C4D2 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
5BED1E3024A95ECB0066C4D2 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@ -460,17 +511,14 @@
5D8DD05F23E319B300E097EF /* Encounter+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Encounter+CoreDataClass.swift"; sourceTree = "<group>"; };
5D8DD06023E319B300E097EF /* Encounter+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Encounter+CoreDataProperties.swift"; sourceTree = "<group>"; };
5DC7B8D0242B8536008E1715 /* BluetraceConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetraceConfig.swift; sourceTree = "<group>"; };
5DC7B8D2242B8D87008E1715 /* BluetraceUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetraceUtils.swift; sourceTree = "<group>"; };
5DD41D3723DCB03B00FD4AB0 /* COVIDSafe.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = COVIDSafe.app; sourceTree = BUILT_PRODUCTS_DIR; };
5DD41D3A23DCB03B00FD4AB0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
5DD41D4323DCB03D00FD4AB0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
5DD41D4823DCB03D00FD4AB0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5DD41D4E23DCB05600FD4AB0 /* CentralController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CentralController.swift; sourceTree = "<group>"; };
5DD41D5223DD4CA400FD4AB0 /* PeripheralController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralController.swift; sourceTree = "<group>"; };
5DD41D7823DE141700FD4AB0 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
5DDB85EA23EE39C000B186BC /* Project Bluetrace.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Project Bluetrace.entitlements"; sourceTree = "<group>"; };
7F19B4DC23F565850071A11E /* PogoInstructionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PogoInstructionsViewController.swift; sourceTree = "<group>"; };
7F2F0BA123EFFF75006D7404 /* OnboardingStep2ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep2ViewController.swift; sourceTree = "<group>"; };
7F2F0BA123EFFF75006D7404 /* AppSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsViewController.swift; sourceTree = "<group>"; };
7F36305E23F7F81400CC6E1D /* PushNotificationConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationConstants.swift; sourceTree = "<group>"; };
7FACD53923F25A9A0042A33A /* InitialScreenViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialScreenViewController.swift; sourceTree = "<group>"; };
7FEC361423F16A1E00127AFB /* UIViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtension.swift; sourceTree = "<group>"; };
@ -506,18 +554,18 @@
B60F8BE3242659810007A641 /* UILabelExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UILabelExtension.swift; sourceTree = "<group>"; };
B615C5A623F8EB1700345969 /* UIProgressView + Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIProgressView + Extension.swift"; sourceTree = "<group>"; };
B615C5A823FA403400345969 /* UIViewController + Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController + Extension.swift"; sourceTree = "<group>"; };
C5046D5B23EF18600046E96D /* OnboardingStep1ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep1ViewController.swift; sourceTree = "<group>"; };
C5046D5B23EF18600046E96D /* RegistrationIntroViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationIntroViewController.swift; sourceTree = "<group>"; };
C56CF43E23F18A15006B05B0 /* OnboardingStep4ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep4ViewController.swift; sourceTree = "<group>"; };
C585C83A23EEB99B0061B7C6 /* GradientButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButton.swift; sourceTree = "<group>"; };
C58D817623F169DB00345771 /* OnboardingStep2bViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep2bViewController.swift; sourceTree = "<group>"; };
C58D817623F169DB00345771 /* RegistrationSuccessViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationSuccessViewController.swift; sourceTree = "<group>"; };
C5D209FB23F4476F007233BE /* UITextViewFixed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextViewFixed.swift; sourceTree = "<group>"; };
D8DEB6812423AE2E00D99925 /* OnboardingStep1bViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStep1bViewController.swift; sourceTree = "<group>"; };
D8DEB6812423AE2E00D99925 /* HowItWorksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HowItWorksViewController.swift; sourceTree = "<group>"; };
D8EB201A23FA722D001C60EC /* HelpNavController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpNavController.swift; sourceTree = "<group>"; };
D8EB201C23FBE216001C60EC /* help_center_article_style.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = help_center_article_style.css; sourceTree = "<group>"; };
DC24373E23F51531007BDBDF /* covid-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "covid-Bridging-Header.h"; sourceTree = "<group>"; };
FB12C4C0242F047F007E893B /* RespondToAuthChallengeAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RespondToAuthChallengeAPI.swift; sourceTree = "<group>"; };
FB12C4C2242F0FE9007E893B /* GetTempIdAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetTempIdAPI.swift; sourceTree = "<group>"; };
FB12C4C424304AF0007E893B /* OnboardingStep1aViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingStep1aViewController.swift; sourceTree = "<group>"; };
FB12C4C424304AF0007E893B /* PrivacyPolicyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyViewController.swift; sourceTree = "<group>"; };
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 = "<group>"; };
FFB86126400FA51506E1B416 /* Pods_CovidSafe_staging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CovidSafe_staging.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
@ -586,34 +634,22 @@
30E91BEC23EFEA14002D592A /* View Controllers */ = {
isa = PBXGroup;
children = (
5BBE61D625637BB100B8C983 /* Debug */,
5BBE61D525637B7600B8C983 /* Registration */,
1B86118F24303ED400EA4B6B /* Questions */,
5B728B4624B5667000654ABC /* BLELogViewController.swift */,
5D8DD05923E2F08400E097EF /* ContactViewController.swift */,
5B92B0BF24F4DC3A0069C57D /* CovidStatisticsViewController.swift */,
5B9E41112521688700434B25 /* CSAlertViewController.swift */,
5BBE61B625633E8D00B8C983 /* CSGenericViewController.swift */,
D8EB201A23FA722D001C60EC /* HelpNavController.swift */,
5D5F83AF23F045A800770DEF /* HomeViewController.swift */,
5B92B0AD24EFADB00069C57D /* SettingsViewController.swift */,
5D8DD05D23E2F0BA00E097EF /* InfoViewController.swift */,
7FACD53923F25A9A0042A33A /* InitialScreenViewController.swift */,
5B92B09324D14AE00069C57D /* InternetConnectionViewController.swift */,
5B9E41112521688700434B25 /* CSAlertViewController.swift */,
5B7ABF24244D3BC600BB249B /* IsolationSuccessViewController.swift */,
5D8DD05B23E2F0A700E097EF /* LogViewController.swift */,
5961ABE92474E358004040DF /* MigrationViewController.swift */,
FB12C4C424304AF0007E893B /* OnboardingStep1aViewController.swift */,
D8DEB6812423AE2E00D99925 /* OnboardingStep1bViewController.swift */,
C5046D5B23EF18600046E96D /* OnboardingStep1ViewController.swift */,
C58D817623F169DB00345771 /* OnboardingStep2bViewController.swift */,
7F2F0BA123EFFF75006D7404 /* OnboardingStep2ViewController.swift */,
C56CF43E23F18A15006B05B0 /* OnboardingStep4ViewController.swift */,
30BE1CB423F15D47005DCE4F /* OTPViewController.swift */,
592CBB7F2441A583001FFCE9 /* PersonalDetailsViewController.swift */,
5D8B248A23F146C500DBB74D /* PhoneNumberViewController.swift */,
7F19B4DC23F565850071A11E /* PogoInstructionsViewController.swift */,
59B7416F24514126006E1EEA /* RegistrationConsentViewController.swift */,
5B110C0F248F275A00B68291 /* SelectCountryViewController.swift */,
5B92B0AD24EFADB00069C57D /* SettingsViewController.swift */,
B615C5A823FA403400345969 /* UIViewController + Extension.swift */,
5B7ABF27244D6BE100BB249B /* UnderSixteenViewController.swift */,
1B86118F24303ED400EA4B6B /* Questions */,
);
name = "View Controllers";
sourceTree = "<group>";
@ -622,9 +658,6 @@
isa = PBXGroup;
children = (
5D269C0C23E2958F00ADF2DE /* BluetraceManager.swift */,
5DD41D4E23DCB05600FD4AB0 /* CentralController.swift */,
5DC7B8D2242B8D87008E1715 /* BluetraceUtils.swift */,
5DD41D5223DD4CA400FD4AB0 /* PeripheralController.swift */,
5D5F83AC23F023F600770DEF /* EncounterMessageManager.swift */,
5DC7B8D0242B8536008E1715 /* BluetraceConfig.swift */,
);
@ -636,11 +669,17 @@
children = (
5B9360EC24F6196A008859FC /* Cells */,
30E91BEA23EFEA0B002D592A /* CodeInputView.swift */,
5B9E410C2521683500434B25 /* CSAlertView.xib */,
B615C5A623F8EB1700345969 /* UIProgressView + Extension.swift */,
0BC141AB24305D9C00399FA8 /* NSMutableString + Extensions.swift */,
0BC141AD2430685800399FA8 /* UIColor + Extensions.swift */,
B615C5A623F8EB1700345969 /* UIProgressView + Extension.swift */,
0B55E1912430760500C9E798 /* UITextView + Extensions.swift */,
5B92B0BC24F4DBE20069C57D /* CovidStatisticsView.xib */,
5B9E410C2521683500434B25 /* CSAlertView.xib */,
5BBE61B025633B6D00B8C983 /* CSGenericContentView.xib */,
5B92B0A424EF61480069C57D /* HomeView.xib */,
5B92B0B324F37E3E0069C57D /* InternetConnectionView.xib */,
5B92B0AA24EFA3160069C57D /* SettingsView.xib */,
5DD41D4023DCB03B00FD4AB0 /* Main.storyboard */,
);
name = Views;
sourceTree = "<group>";
@ -660,6 +699,62 @@
name = Utils;
sourceTree = "<group>";
};
590545EB2543E0F5009B82AD /* Herald */ = {
isa = PBXGroup;
children = (
590545EC2543E0F5009B82AD /* Sensor */,
5905460B2543E0F5009B82AD /* herald.h */,
);
path = Herald;
sourceTree = "<group>";
};
590545EC2543E0F5009B82AD /* Sensor */ = {
isa = PBXGroup;
children = (
590545ED2543E0F5009B82AD /* BLE */,
590545F32543E0F5009B82AD /* Location */,
590545F52543E0F5009B82AD /* Sensor.swift */,
590546012543E0F5009B82AD /* SensorArray.swift */,
590546022543E0F5009B82AD /* PayloadDataSupplier.swift */,
590546032543E0F5009B82AD /* Data */,
5905460A2543E0F5009B82AD /* SensorDelegate.swift */,
);
path = Sensor;
sourceTree = "<group>";
};
590545ED2543E0F5009B82AD /* BLE */ = {
isa = PBXGroup;
children = (
590545EE2543E0F5009B82AD /* BLESensor.swift */,
590545EF2543E0F5009B82AD /* BLEDatabase.swift */,
590545F02543E0F5009B82AD /* BLEUtilities.swift */,
590545F12543E0F5009B82AD /* BLETransmitter.swift */,
590545F22543E0F5009B82AD /* BLEReceiver.swift */,
);
path = BLE;
sourceTree = "<group>";
};
590545F32543E0F5009B82AD /* Location */ = {
isa = PBXGroup;
children = (
590545F42543E0F5009B82AD /* AwakeSensor.swift */,
);
path = Location;
sourceTree = "<group>";
};
590546032543E0F5009B82AD /* Data */ = {
isa = PBXGroup;
children = (
590546042543E0F5009B82AD /* TextFile.swift */,
590546052543E0F5009B82AD /* BatteryLog.swift */,
590546062543E0F5009B82AD /* DetectionLog.swift */,
590546072543E0F5009B82AD /* SensorLogger.swift */,
590546082543E0F5009B82AD /* ContactLog.swift */,
590546092543E0F5009B82AD /* StatisticsLog.swift */,
);
path = Data;
sourceTree = "<group>";
};
5909E4A92450438A00D41C26 /* CoreData */ = {
isa = PBXGroup;
children = (
@ -767,6 +862,35 @@
name = Cells;
sourceTree = "<group>";
};
5BBE61D525637B7600B8C983 /* Registration */ = {
isa = PBXGroup;
children = (
7F2F0BA123EFFF75006D7404 /* AppSettingsViewController.swift */,
D8DEB6812423AE2E00D99925 /* HowItWorksViewController.swift */,
C56CF43E23F18A15006B05B0 /* OnboardingStep4ViewController.swift */,
30BE1CB423F15D47005DCE4F /* OTPViewController.swift */,
592CBB7F2441A583001FFCE9 /* PersonalDetailsViewController.swift */,
5D8B248A23F146C500DBB74D /* PhoneNumberViewController.swift */,
FB12C4C424304AF0007E893B /* PrivacyPolicyViewController.swift */,
59B7416F24514126006E1EEA /* RegistrationConsentViewController.swift */,
C5046D5B23EF18600046E96D /* RegistrationIntroViewController.swift */,
C58D817623F169DB00345771 /* RegistrationSuccessViewController.swift */,
5B110C0F248F275A00B68291 /* SelectCountryViewController.swift */,
5B7ABF27244D6BE100BB249B /* UnderSixteenViewController.swift */,
);
name = Registration;
sourceTree = "<group>";
};
5BBE61D625637BB100B8C983 /* Debug */ = {
isa = PBXGroup;
children = (
5D8DD05923E2F08400E097EF /* ContactViewController.swift */,
5D8DD05D23E2F0BA00E097EF /* InfoViewController.swift */,
5D8DD05B23E2F0A700E097EF /* LogViewController.swift */,
);
name = Debug;
sourceTree = "<group>";
};
5BED1E3824A96CF50066C4D2 /* LaunchScreen */ = {
isa = PBXGroup;
children = (
@ -824,6 +948,7 @@
5DD41D3923DCB03B00FD4AB0 /* CovidSafe */ = {
isa = PBXGroup;
children = (
590545EB2543E0F5009B82AD /* Herald */,
597BB7CA245FACE20067A2E2 /* Crypto */,
5949DC522434859600AE76BC /* lottie */,
5B92D661243005B10049877B /* Resources */,
@ -843,14 +968,10 @@
30FADD6C23F520F5006C125F /* Utils */,
5DD41D7723DE141700FD4AB0 /* tracer.xcdatamodeld */,
5B728B4C24B5A26D00654ABC /* debug.xcdatamodeld */,
5B92B0BC24F4DBE20069C57D /* CovidStatisticsView.xib */,
5B92B0A424EF61480069C57D /* HomeView.xib */,
5B92B0B324F37E3E0069C57D /* InternetConnectionView.xib */,
5B92B0AA24EFA3160069C57D /* SettingsView.xib */,
5DD41D4023DCB03B00FD4AB0 /* Main.storyboard */,
D8EB201C23FBE216001C60EC /* help_center_article_style.css */,
5DD41D4323DCB03D00FD4AB0 /* Assets.xcassets */,
5DD41D4823DCB03D00FD4AB0 /* Info.plist */,
5BBC571C25526F99005E90AA /* staging-Info.plist */,
5D269C0A23E22CC400ADF2DE /* DeviceIdentifier.swift */,
DC24373E23F51531007BDBDF /* covid-Bridging-Header.h */,
);
@ -1088,6 +1209,7 @@
5BED1E3B24A96D1C0066C4D2 /* LaunchScreen_ar.storyboard in Resources */,
5BED1E4424A98EB60066C4D2 /* LaunchScreen_vi.storyboard in Resources */,
5B30F78D24B817A100CDED63 /* LaunchScreen_el.storyboard in Resources */,
5BBC571D25526F99005E90AA /* staging-Info.plist in Resources */,
5B9360FE24F73CE6008859FC /* LoadingViewCell.xib in Resources */,
5B92D6C9243018040049877B /* LaunchScreen_en.storyboard in Resources */,
5B92B0A624EF61490069C57D /* HomeView.xib in Resources */,
@ -1096,6 +1218,7 @@
5B92D6CB243018040049877B /* Assets.xcassets in Resources */,
5B92B0A324E0CD190069C57D /* LaunchScreen_tr.storyboard in Resources */,
5BED1E4124A98EAF0066C4D2 /* LaunchScreen_zh-Hant.storyboard in Resources */,
5BBE61B225633B6D00B8C983 /* CSGenericContentView.xib in Resources */,
5B30F78A24B8179600CDED63 /* LaunchScreen_it.storyboard in Resources */,
5B337AB0245AA26300537620 /* Spinner_upload.json in Resources */,
5B92B0B524F37E3E0069C57D /* InternetConnectionView.xib in Resources */,
@ -1129,6 +1252,7 @@
5BED1E3A24A96D1C0066C4D2 /* LaunchScreen_ar.storyboard in Resources */,
5B30F78C24B817A100CDED63 /* LaunchScreen_el.storyboard in Resources */,
5B92B0A124E0CD140069C57D /* LaunchScreen_pa-IN.storyboard in Resources */,
5BBE61B125633B6D00B8C983 /* CSGenericContentView.xib in Resources */,
5B82435E2480DC2100705CB1 /* Localizable.strings in Resources */,
5B92D663243011B40049877B /* CovidSafe-config.plist in Resources */,
5BED1E4324A98EB60066C4D2 /* LaunchScreen_vi.storyboard in Resources */,
@ -1255,32 +1379,36 @@
5B92D67D243018040049877B /* HomeViewController.swift in Sources */,
5B92D67E243018040049877B /* InfoViewController.swift in Sources */,
5B92D67F243018040049877B /* PogoInstructionsViewController.swift in Sources */,
FBBBFCE82430A933002B174D /* OnboardingStep1aViewController.swift in Sources */,
FBBBFCE82430A933002B174D /* PrivacyPolicyViewController.swift in Sources */,
5B51ED512485DF9C008CE722 /* UILocalization.swift in Sources */,
5BA33A8024B55EF200D12515 /* BLELogDB.swift in Sources */,
594E77C3247387B1009B8B34 /* EncounterDB+migration.swift in Sources */,
590546292543E0F6009B82AD /* SensorArray.swift in Sources */,
590C99332432C1C400A5EC71 /* UploadDataHomeViewController.swift in Sources */,
590546112543E0F5009B82AD /* BLEUtilities.swift in Sources */,
59B7417124514126006E1EEA /* RegistrationConsentViewController.swift in Sources */,
5B92B0AF24EFADB00069C57D /* SettingsViewController.swift in Sources */,
5B92B0C124F4DC3A0069C57D /* CovidStatisticsViewController.swift in Sources */,
5B92D680243018040049877B /* CodeInputView.swift in Sources */,
5B92D681243018040049877B /* EncounterRecord.swift in Sources */,
5905462D2543E0F6009B82AD /* TextFile.swift in Sources */,
5B728B4B24B581C100654ABC /* BLELog+CoreDataProperties.swift in Sources */,
5B337AAD245A9EF800537620 /* UploadDataErrorViewController.swift in Sources */,
590888B02431B9E7008C9B9F /* UIColor + Extensions.swift in Sources */,
5B728B4E24B5A26D00654ABC /* debug.xcdatamodeld in Sources */,
5905460F2543E0F5009B82AD /* BLEDatabase.swift in Sources */,
5B92D74F243022EF0049877B /* DataUploadS3.swift in Sources */,
5B92D683243018040049877B /* OnboardingStep2bViewController.swift in Sources */,
5B92D683243018040049877B /* RegistrationSuccessViewController.swift in Sources */,
5BA33A8224B5602500D12515 /* BLELogRecord+BLELogSave.swift in Sources */,
5B92B09524D14AE00069C57D /* InternetConnectionViewController.swift in Sources */,
5B92D684243018040049877B /* AsyncAction.swift in Sources */,
5B92D685243018040049877B /* UILabelExtension.swift in Sources */,
5905462B2543E0F6009B82AD /* PayloadDataSupplier.swift in Sources */,
5B92D686243018040049877B /* AppDelegate.swift in Sources */,
5B92D687243018040049877B /* PhoneNumberViewController.swift in Sources */,
5BA33A7C24B41ECA00D12515 /* UIFont + Traits.swift in Sources */,
5961ABEB2474E358004040DF /* MigrationViewController.swift in Sources */,
5B110C11248F275B00B68291 /* SelectCountryViewController.swift in Sources */,
5B92D688243018040049877B /* BluetraceUtils.swift in Sources */,
5B92D689243018040049877B /* NewFeedbackFlowController.swift in Sources */,
5B92D68A243018040049877B /* Outcome.swift in Sources */,
5B92D68B243018040049877B /* Encounter+EncounterRecord.swift in Sources */,
@ -1302,12 +1430,13 @@
597BB7CD245FAEC00067A2E2 /* SecKey+CovidSafe.swift in Sources */,
597BB7D0245FB1250067A2E2 /* Crypto.swift in Sources */,
5B92D693243018040049877B /* UIColor+Hex.swift in Sources */,
5B92D694243018040049877B /* OnboardingStep1ViewController.swift in Sources */,
590546372543E0F6009B82AD /* StatisticsLog.swift in Sources */,
5B92D694243018040049877B /* RegistrationIntroViewController.swift in Sources */,
5B92D695243018040049877B /* DeviceInfoExtension.swift in Sources */,
5BD3EE84243313450004A007 /* UploadDataStep2VC.swift in Sources */,
5B92D697243018040049877B /* PeripheralController.swift in Sources */,
5B92D698243018040049877B /* SendFeedbackAction.swift in Sources */,
5B92D699243018040049877B /* UITextViewFixed.swift in Sources */,
590546152543E0F6009B82AD /* BLEReceiver.swift in Sources */,
5BA33A7E24B55E7D00D12515 /* BLELogRecord.swift in Sources */,
590888B12431B9EB008C9B9F /* Question3ViewController.swift in Sources */,
5957EB67244E936E002F5388 /* UnderSixteenViewController.swift in Sources */,
@ -1323,6 +1452,7 @@
5B92D6A0243018040049877B /* CountriesData.swift in Sources */,
590888B22431B9EF008C9B9F /* Question2ViewController.swift in Sources */,
0B42D0E02432B3AF00E4F44C /* QuestionUploadDataViewController.swift in Sources */,
590546352543E0F6009B82AD /* ContactLog.swift in Sources */,
5B92D6A1243018040049877B /* AlertController.swift in Sources */,
5B92D750243022F20049877B /* InitiateUploadAPI.swift in Sources */,
5B92D69F243018040049877B /* EncounterMessageManager.swift in Sources */,
@ -1330,29 +1460,37 @@
5B728B4924B5816C00654ABC /* BLELog+CoreDataClass.swift in Sources */,
5B92D6A2243018040049877B /* GetTempIdAPI.swift in Sources */,
5B92D6A3243018040049877B /* PhoneValidationAPI.swift in Sources */,
590546132543E0F6009B82AD /* BLETransmitter.swift in Sources */,
59AF2EB32435A38100ACCAF2 /* CovidRequestRetrier.swift in Sources */,
592CBB812441A583001FFCE9 /* PersonalDetailsViewController.swift in Sources */,
5B92D6A4243018040049877B /* CentralController.swift in Sources */,
5B92D6A5243018040049877B /* UIWindow+TopMost.swift in Sources */,
5B92D6A6243018040049877B /* OnboardingStep1bViewController.swift in Sources */,
5B92D6A6243018040049877B /* HowItWorksViewController.swift in Sources */,
590546312543E0F6009B82AD /* DetectionLog.swift in Sources */,
5B92D6A7243018040049877B /* OnboardingStep4ViewController.swift in Sources */,
5B92D6A8243018040049877B /* PresentFeedbackExtensions.swift in Sources */,
590546392543E0F6009B82AD /* SensorDelegate.swift in Sources */,
5B92D6A9243018040049877B /* Errors.swift in Sources */,
5B92D6AA243018040049877B /* UINavigationBar+Style.swift in Sources */,
590546172543E0F6009B82AD /* AwakeSensor.swift in Sources */,
5B92D6AB243018040049877B /* UploadFileData.swift in Sources */,
590888B52431BA76008C9B9F /* NSMutableString + Extensions.swift in Sources */,
5B92D6AC243018040049877B /* BundleInfoExtension.swift in Sources */,
5905460D2543E0F5009B82AD /* BLESensor.swift in Sources */,
5BBE61B825633E8D00B8C983 /* CSGenericViewController.swift in Sources */,
5909E4AC245043C400D41C26 /* CovidPersistentContainer.swift in Sources */,
5905462F2543E0F6009B82AD /* BatteryLog.swift in Sources */,
5B92D6AD243018040049877B /* FeedbackSettings.swift in Sources */,
5B92D6AE243018040049877B /* JMCTargetJSONFromDisk.swift in Sources */,
5B92D6B0243018040049877B /* Action.swift in Sources */,
5B92D6B1243018040049877B /* OnboardingStep2ViewController.swift in Sources */,
5B92D6B1243018040049877B /* AppSettingsViewController.swift in Sources */,
59AF2E9A2435533A00ACCAF2 /* CovidCertificates.swift in Sources */,
5B92D6B2243018040049877B /* HTTPPostFeedbackAction.swift in Sources */,
5B92B0C424F4DE6F0069C57D /* StatisticsAPI.swift in Sources */,
590546192543E0F6009B82AD /* Sensor.swift in Sources */,
59898604245173C200966E61 /* URLHelper.swift in Sources */,
590888B32431B9F2008C9B9F /* Question1ViewController.swift in Sources */,
590888B62431BA7C008C9B9F /* Question3ErrorViewController.swift in Sources */,
590546332543E0F6009B82AD /* SensorLogger.swift in Sources */,
5B900FC22485C4EE00CAA419 /* String+Localization.swift in Sources */,
59AF2E9D2435581600ACCAF2 /* CovidNetworking.swift in Sources */,
596B189D24499591003E190F /* UploadHelper.swift in Sources */,
@ -1368,14 +1506,17 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5ADBDDED2579A7DC0078872F /* EncounterMessageManager.swift in Sources */,
B615C5A723F8EB1700345969 /* UIProgressView + Extension.swift in Sources */,
5D8DD06723E329A200E097EF /* tracer.xcdatamodeld in Sources */,
30E91BE923EFE514002D592A /* UploadDataVC.swift in Sources */,
0BC141AE2430685800399FA8 /* UIColor + Extensions.swift in Sources */,
7FEC361523F16A1E00127AFB /* UIViewExtension.swift in Sources */,
590546122543E0F6009B82AD /* BLETransmitter.swift in Sources */,
5D269C0B23E22CC400ADF2DE /* DeviceIdentifier.swift in Sources */,
A767D31E242DF1B000DC9E2A /* LanguageInfoExtension.swift in Sources */,
B605A7B12427429D008BA819 /* PlistHelper.swift in Sources */,
5905460E2543E0F5009B82AD /* BLEDatabase.swift in Sources */,
5DC7B8D1242B8536008E1715 /* BluetraceConfig.swift in Sources */,
0B69E7E92430C22E00561DD9 /* UploadDataHomeViewController.swift in Sources */,
1B86119524303F5E00EA4B6B /* Question3ViewController.swift in Sources */,
@ -1389,20 +1530,23 @@
59B7417024514126006E1EEA /* RegistrationConsentViewController.swift in Sources */,
5D5F83B023F045A800770DEF /* HomeViewController.swift in Sources */,
5B337AAB245A9BBC00537620 /* UploadDataErrorViewController.swift in Sources */,
FB12C4C524304AF0007E893B /* OnboardingStep1aViewController.swift in Sources */,
FB12C4C524304AF0007E893B /* PrivacyPolicyViewController.swift in Sources */,
7F19B4DD23F565850071A11E /* PogoInstructionsViewController.swift in Sources */,
30E91BEB23EFEA0B002D592A /* CodeInputView.swift in Sources */,
597BB7CF245FB1250067A2E2 /* Crypto.swift in Sources */,
30BE1CAF23F1349F005DCE4F /* EncounterRecord.swift in Sources */,
590546102543E0F5009B82AD /* BLEUtilities.swift in Sources */,
590546382543E0F6009B82AD /* SensorDelegate.swift in Sources */,
59490B64245FE3DA00C9802B /* EncounterV2Mapping.swift in Sources */,
C58D817723F169DB00345771 /* OnboardingStep2bViewController.swift in Sources */,
590546282543E0F6009B82AD /* SensorArray.swift in Sources */,
C58D817723F169DB00345771 /* RegistrationSuccessViewController.swift in Sources */,
59ACB574242F195A00E63E3C /* InitiateUploadAPI.swift in Sources */,
A767D324242DF1B100DC9E2A /* AsyncAction.swift in Sources */,
B60F8BE4242659810007A641 /* UILabelExtension.swift in Sources */,
5DD41D3B23DCB03B00FD4AB0 /* AppDelegate.swift in Sources */,
5D8B248B23F146C500DBB74D /* PhoneNumberViewController.swift in Sources */,
5DC7B8D3242B8D87008E1715 /* BluetraceUtils.swift in Sources */,
A767D32D242DF1B100DC9E2A /* NewFeedbackFlowController.swift in Sources */,
590546362543E0F6009B82AD /* StatisticsLog.swift in Sources */,
A767D319242DF1B000DC9E2A /* Outcome.swift in Sources */,
30BE1CB123F134D0005DCE4F /* Encounter+EncounterRecord.swift in Sources */,
A767D336242DF1B100DC9E2A /* GetJMCTargetAction.swift in Sources */,
@ -1411,25 +1555,31 @@
D8EB201B23FA722D001C60EC /* HelpNavController.swift in Sources */,
596B189924496D32003E190F /* Encounter+Util.swift in Sources */,
5961ABEA2474E358004040DF /* MigrationViewController.swift in Sources */,
590546182543E0F6009B82AD /* Sensor.swift in Sources */,
590546162543E0F6009B82AD /* AwakeSensor.swift in Sources */,
5B04074124BC0CEA00FAAFD0 /* MessageAPI.swift in Sources */,
30BE1CB523F15D47005DCE4F /* OTPViewController.swift in Sources */,
5905460C2543E0F5009B82AD /* BLESensor.swift in Sources */,
5B900FC12485C4EE00CAA419 /* String+Localization.swift in Sources */,
5D8DD06123E319B300E097EF /* Encounter+CoreDataClass.swift in Sources */,
594E77BF24736B77009B8B34 /* EncounterDB.swift in Sources */,
590546322543E0F6009B82AD /* SensorLogger.swift in Sources */,
5905462E2543E0F6009B82AD /* BatteryLog.swift in Sources */,
FB12C4C1242F0480007E893B /* RespondToAuthChallengeAPI.swift in Sources */,
5D269C0D23E2958F00ADF2DE /* BluetraceManager.swift in Sources */,
1B86119124303EF200EA4B6B /* Question1ViewController.swift in Sources */,
5BD3EE8324330E1A0004A007 /* UploadDataStep2VC.swift in Sources */,
A767D31B242DF1B000DC9E2A /* UIColor+Hex.swift in Sources */,
5BA33A7B24B3FE2700D12515 /* UIFont + Traits.swift in Sources */,
C5046D5C23EF18600046E96D /* OnboardingStep1ViewController.swift in Sources */,
590546142543E0F6009B82AD /* BLEReceiver.swift in Sources */,
C5046D5C23EF18600046E96D /* RegistrationIntroViewController.swift in Sources */,
0B69E7ED2430D72900561DD9 /* UploadDataNavigationController.swift in Sources */,
A767D320242DF1B100DC9E2A /* DeviceInfoExtension.swift in Sources */,
5DD41D5323DD4CA400FD4AB0 /* PeripheralController.swift in Sources */,
5B7ABF28244D6BE100BB249B /* UnderSixteenViewController.swift in Sources */,
A767D325242DF1B100DC9E2A /* SendFeedbackAction.swift in Sources */,
C5D209FC23F4476F007233BE /* UITextViewFixed.swift in Sources */,
C585C83B23EEB99B0061B7C6 /* GradientButton.swift in Sources */,
590546342543E0F6009B82AD /* ContactLog.swift in Sources */,
597BB7CC245FAEC00067A2E2 /* SecKey+CovidSafe.swift in Sources */,
5B92B0AE24EFADB00069C57D /* SettingsViewController.swift in Sources */,
5B7ABF25244D3BC600BB249B /* IsolationSuccessViewController.swift in Sources */,
@ -1438,25 +1588,25 @@
5B577815245A584C0088F111 /* UploadDataPrefaceViewController.swift in Sources */,
0BC141AC24305D9C00399FA8 /* NSMutableString + Extensions.swift in Sources */,
59ACB576242F404500E63E3C /* DataUploadS3.swift in Sources */,
590546302543E0F6009B82AD /* DetectionLog.swift in Sources */,
5B92B0C024F4DC3A0069C57D /* CovidStatisticsViewController.swift in Sources */,
A767D326242DF1B100DC9E2A /* JMCTarget.swift in Sources */,
5D5F83AD23F023F600770DEF /* EncounterMessageManager.swift in Sources */,
7FF75C222429FEE800C11FEA /* CountriesData.swift in Sources */,
0B55E1922430760600C9E798 /* UITextView + Extensions.swift in Sources */,
5B110C10248F275B00B68291 /* SelectCountryViewController.swift in Sources */,
A767D321242DF1B100DC9E2A /* AlertController.swift in Sources */,
5D5F83AD23F023F600770DEF /* EncounterMessageManager.swift in Sources */,
FB12C4C3242F0FE9007E893B /* GetTempIdAPI.swift in Sources */,
5BFFD94B242EC120003AEF4F /* PhoneValidationAPI.swift in Sources */,
59AF2EB22435A38100ACCAF2 /* CovidRequestRetrier.swift in Sources */,
592CBB802441A583001FFCE9 /* PersonalDetailsViewController.swift in Sources */,
5DD41D4F23DCB05600FD4AB0 /* CentralController.swift in Sources */,
A767D330242DF1B100DC9E2A /* UIWindow+TopMost.swift in Sources */,
D8DEB6822423AE2E00D99925 /* OnboardingStep1bViewController.swift in Sources */,
D8DEB6822423AE2E00D99925 /* HowItWorksViewController.swift in Sources */,
5B92B0C324F4DE6F0069C57D /* StatisticsAPI.swift in Sources */,
5905462C2543E0F6009B82AD /* TextFile.swift in Sources */,
C56CF43F23F18A15006B05B0 /* OnboardingStep4ViewController.swift in Sources */,
A767D328242DF1B100DC9E2A /* PresentFeedbackExtensions.swift in Sources */,
A767D32F242DF1B100DC9E2A /* Errors.swift in Sources */,
5905462A2543E0F6009B82AD /* PayloadDataSupplier.swift in Sources */,
0B22A56B242F286900D1FE60 /* UINavigationBar+Style.swift in Sources */,
0B1810122431EE610005D11F /* PhoneNumberParser.swift in Sources */,
1B86119B24303FA200EA4B6B /* Question3ErrorViewController.swift in Sources */,
@ -1465,10 +1615,11 @@
A767D31D242DF1B000DC9E2A /* BundleInfoExtension.swift in Sources */,
A767D32C242DF1B100DC9E2A /* FeedbackSettings.swift in Sources */,
A767D31A242DF1B000DC9E2A /* JMCTargetJSONFromDisk.swift in Sources */,
5BBE61B725633E8D00B8C983 /* CSGenericViewController.swift in Sources */,
5B92B09424D14AE00069C57D /* InternetConnectionViewController.swift in Sources */,
A767D334242DF1B100DC9E2A /* Action.swift in Sources */,
59AF2E992435533A00ACCAF2 /* CovidCertificates.swift in Sources */,
7F2F0BA223EFFF75006D7404 /* OnboardingStep2ViewController.swift in Sources */,
7F2F0BA223EFFF75006D7404 /* AppSettingsViewController.swift in Sources */,
594E77C2247387B1009B8B34 /* EncounterDB+migration.swift in Sources */,
59898603245173C200966E61 /* URLHelper.swift in Sources */,
A767D31F242DF1B000DC9E2A /* HTTPPostFeedbackAction.swift in Sources */,
@ -1627,7 +1778,7 @@
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83;
CURRENT_PROJECT_VERSION = 95;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
@ -1635,7 +1786,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.14;
MARKETING_VERSION = 2.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_NAME = COVIDSafe;
@ -1711,7 +1862,7 @@
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83;
CURRENT_PROJECT_VERSION = 95;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
@ -1719,7 +1870,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.14;
MARKETING_VERSION = 2.0;
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_NAME = COVIDSafe;
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1739,15 +1890,15 @@
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83;
CURRENT_PROJECT_VERSION = 93;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/staging-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.14;
MARKETING_VERSION = 2.0;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
@ -1769,15 +1920,15 @@
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83;
CURRENT_PROJECT_VERSION = 93;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/staging-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.14;
MARKETING_VERSION = 2.0;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
@ -1799,15 +1950,15 @@
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83;
CURRENT_PROJECT_VERSION = 93;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/staging-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.14;
MARKETING_VERSION = 2.0;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_MODULE_NAME = COVIDSafe;
@ -1829,15 +1980,15 @@
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83;
CURRENT_PROJECT_VERSION = 93;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/staging-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.14;
MARKETING_VERSION = 2.0;
OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_MODULE_NAME = COVIDSafe;
@ -1977,7 +2128,7 @@
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83;
CURRENT_PROJECT_VERSION = 95;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
@ -1985,7 +2136,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.14;
MARKETING_VERSION = 2.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_NAME = COVIDSafe;
@ -2005,7 +2156,7 @@
CODE_SIGN_ENTITLEMENTS = "CovidSafe/Project Bluetrace.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 83;
CURRENT_PROJECT_VERSION = 95;
DEVELOPMENT_TEAM = 45792XH5L8;
INFOPLIST_FILE = "$(SRCROOT)/CovidSafe/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
@ -2013,7 +2164,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.14;
MARKETING_VERSION = 2.0;
PRODUCT_BUNDLE_IDENTIFIER = au.gov.health.covidsafe;
PRODUCT_NAME = COVIDSafe;
PROVISIONING_PROFILE_SPECIFIER = "";

View file

@ -29,14 +29,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
UIBarButtonItem.appearance().setTitleTextAttributes([.foregroundColor: UIColor.covidSafeColor], for: .normal)
UINavigationBar.appearance().tintColor = UIColor.covidSafeColor
let hasUserConsent = true
let hasUserCompletedOnboarding = UserDefaults.standard.bool(forKey: "turnedOnBluetooth")
let bluetoothAuthorised = BluetraceManager.shared.isBluetoothAuthorized()
if (hasUserConsent && hasUserCompletedOnboarding && bluetoothAuthorised) {
BluetraceManager.shared.turnOn()
} else {
print("Onboarding not yet done.")
}
EncounterMessageManager.shared.setup()
UIApplication.shared.isIdleTimerDisabled = true

View file

@ -0,0 +1,63 @@
//
// AppSettingsViewController.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import UIKit
import CoreBluetooth
import UserNotifications
class AppSettingsViewController: UIViewController {
private var backupSensorDidUpdateStateCallback: ((SensorState, SensorType?) -> Void)?
@IBOutlet weak var stepCounterLabel: UILabel!
@IBOutlet weak var topContentTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
stepCounterLabel.text = String.localizedStringWithFormat( "stepCounter".localizedString(),
4,
4
)
topContentTextView.addLink("\(URLHelper.getHelpURL())#location-permission-android", enclosedIn: "*")
topContentTextView.addAllBold(enclosedIn: "#")
}
@IBAction func proceedTapped(_ sender: UIButton) {
self.backupSensorDidUpdateStateCallback = BluetraceManager.shared.sensorDidUpdateStateCallback
BluetraceManager.shared.sensorDidUpdateStateCallback = sensorManagerDidUpdateBluetoothCallback
BluetraceManager.shared.turnOnBLE()
UserDefaults.standard.set(true, forKey: "turnedOnBluetooth")
}
func sensorManagerDidUpdateBluetoothCallback(_ state: SensorState, type: SensorType?) {
DLog("Bluetooth state changed in permission request to \(state.rawValue)")
requestPushPermissions()
}
func sensorManagerDidUpdateLocationCallback(_ state: SensorState, type: SensorType?) {
DLog("Location state changed in permission request to \(state.rawValue)")
UserDefaults.standard.set(true, forKey: "allowedPermissions")
BluetraceManager.shared.sensorDidUpdateStateCallback = self.backupSensorDidUpdateStateCallback
DispatchQueue.main.async {
self.performSegue(withIdentifier: "showSuccessSegue", sender: self)
}
}
func requestPushPermissions() {
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) {
granted, error in
print("Permissions granted: \(granted)")
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
BluetraceManager.shared.sensorDidUpdateStateCallback = self.sensorManagerDidUpdateLocationCallback
BluetraceManager.shared.turnOnLocationSensor()
}
}
}
}

View file

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="8X4-AC-7cb">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="8X4-AC-7cb">
<device id="retina5_5" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Onboarding Step 2b View Controller-->
<!--Registration Success View Controller-->
<scene sceneID="PoE-0G-Ttd">
<objects>
<viewController modalTransitionStyle="crossDissolve" modalPresentationStyle="fullScreen" id="GaQ-f5-ei6" userLabel="Onboarding Step 2b View Controller" customClass="OnboardingStep2bViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<viewController modalTransitionStyle="crossDissolve" modalPresentationStyle="fullScreen" id="GaQ-f5-ei6" customClass="RegistrationSuccessViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Dwh-jN-1SX">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -197,7 +197,7 @@
<!--Onboarding Step 1 View Controller-->
<scene sceneID="26D-Zd-57d">
<objects>
<viewController storyboardIdentifier="onboardingStep1" modalTransitionStyle="crossDissolve" modalPresentationStyle="fullScreen" id="8nR-hO-fWt" userLabel="Onboarding Step 1 View Controller" customClass="OnboardingStep1ViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<viewController storyboardIdentifier="onboardingStep1" modalTransitionStyle="crossDissolve" modalPresentationStyle="fullScreen" id="8nR-hO-fWt" userLabel="Onboarding Step 1 View Controller" customClass="RegistrationIntroViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="lOU-q5-7Co">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -215,7 +215,7 @@
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Together we can stop the spread of COVID-19" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="00h-JA-MfK">
<rect key="frame" x="32" y="208" width="308.33333333333331" height="67.666666666666686"/>
<rect key="frame" x="32" y="208" width="309.66666666666669" height="67.666666666666686"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" staticText="YES" header="YES"/>
</accessibility>
@ -322,10 +322,10 @@ Together we can help stop the spread and stay healthy.</string>
</objects>
<point key="canvasLocation" x="-6738" y="-1212"/>
</scene>
<!--Onboarding Step 2 View Controller-->
<!--App Settings View Controller-->
<scene sceneID="d1g-JU-eoU">
<objects>
<viewController storyboardIdentifier="onboardingStep2" modalTransitionStyle="crossDissolve" modalPresentationStyle="fullScreen" id="eME-NJ-Fcz" userLabel="Onboarding Step 2 View Controller" customClass="OnboardingStep2ViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<viewController storyboardIdentifier="onboardingStep2" modalTransitionStyle="crossDissolve" modalPresentationStyle="fullScreen" id="eME-NJ-Fcz" customClass="AppSettingsViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="dJg-Mo-aen">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -334,8 +334,14 @@ Together we can help stop the spread and stay healthy.</string>
<rect key="frame" x="0.0" y="0.0" width="414" height="624"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SpU-i1-aUA" userLabel="ContentView">
<rect key="frame" x="0.0" y="0.0" width="414" height="617.66666666666663"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="635.33333333333337"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Step 4 of 4" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Vco-tH-Zts">
<rect key="frame" x="32" y="60.000000000000007" width="350" height="19.333333333333336"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="AppPermissions1" translatesAutoresizingMaskIntoConstraints="NO" id="cma-Te-Ut1">
<rect key="frame" x="51.666666666666657" y="103.33333333333331" width="311" height="188"/>
<constraints>
@ -355,7 +361,7 @@ Together we can help stop the spread and stay healthy.</string>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="npY-9G-r2u">
<rect key="frame" x="32" y="365" width="350" height="130.33333333333337"/>
<rect key="frame" x="32" y="365" width="350" height="148"/>
<string key="text">COVIDSafe needs Bluetooth® enabled to work. By enabling Notifications, you get updates to remind you when COVIDSafe is not active.
Select Proceed' to enable:</string>
@ -367,8 +373,22 @@ Select Proceed' to enable:</string>
<userDefinedRuntimeAttribute type="string" keyPath="localVOLabelKey" value="permission_content_iOS_VOLabel"/>
</userDefinedRuntimeAttributes>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" editable="NO" textAlignment="natural" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vam-jJ-7TM">
<rect key="frame" x="32" y="365" width="350" height="148"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<string key="text">COVIDSafe needs Bluetooth® enabled to work. By enabling Notifications, you get updates to remind you when COVIDSafe is not active.
Select Proceed' to enable:</string>
<color key="textColor" systemColor="labelColor"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizationKey" value="permission_content_iOS"/>
<userDefinedRuntimeAttribute type="string" keyPath="localVOLabelKey" value="permission_content_iOS_VOLabel"/>
</userDefinedRuntimeAttributes>
</textView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DAg-5r-Sgd">
<rect key="frame" x="32" y="515.33333333333337" width="350" height="86.333333333333371"/>
<rect key="frame" x="32" y="533" width="350" height="86.333333333333371"/>
<string key="text">1. Bluetooth®
2. Notifications
@ -381,23 +401,20 @@ COVIDSafe does not send pairing requests.</string>
<userDefinedRuntimeAttribute type="string" keyPath="localVOLabelKey" value="permission_content_iOS_2_VOLabel"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Step 4 of 4" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Vco-tH-Zts">
<rect key="frame" x="32" y="60.000000000000007" width="350" height="19.333333333333336"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="npY-9G-r2u" firstAttribute="leading" secondItem="SpU-i1-aUA" secondAttribute="leading" constant="32" id="1qx-Y1-2Bz"/>
<constraint firstItem="Vco-tH-Zts" firstAttribute="top" secondItem="SpU-i1-aUA" secondAttribute="top" constant="60" id="A22-Zs-PDI"/>
<constraint firstAttribute="bottom" secondItem="DAg-5r-Sgd" secondAttribute="bottom" constant="16" id="ByQ-uv-A4X"/>
<constraint firstItem="vam-jJ-7TM" firstAttribute="leading" secondItem="SpU-i1-aUA" secondAttribute="leading" constant="32" id="IgC-l4-Hyw"/>
<constraint firstAttribute="trailing" secondItem="Vco-tH-Zts" secondAttribute="trailing" constant="32" id="OUF-2C-mUH"/>
<constraint firstItem="npY-9G-r2u" firstAttribute="top" secondItem="0pO-gl-umK" secondAttribute="bottom" constant="16" id="R2m-S5-4HK"/>
<constraint firstItem="Vco-tH-Zts" firstAttribute="leading" secondItem="SpU-i1-aUA" secondAttribute="leading" constant="32" id="SNT-kH-ef3"/>
<constraint firstAttribute="trailing" secondItem="DAg-5r-Sgd" secondAttribute="trailing" constant="32" id="SkE-oy-hPa"/>
<constraint firstAttribute="trailing" secondItem="vam-jJ-7TM" secondAttribute="trailing" constant="32" id="ZSg-wl-Els"/>
<constraint firstItem="DAg-5r-Sgd" firstAttribute="top" secondItem="npY-9G-r2u" secondAttribute="bottom" constant="20" id="c13-ir-irb"/>
<constraint firstItem="DAg-5r-Sgd" firstAttribute="top" secondItem="vam-jJ-7TM" secondAttribute="bottom" constant="20" id="cEL-Fy-qkJ"/>
<constraint firstItem="cma-Te-Ut1" firstAttribute="centerX" secondItem="SpU-i1-aUA" secondAttribute="centerX" id="cEy-KQ-T2p"/>
<constraint firstAttribute="trailing" secondItem="npY-9G-r2u" secondAttribute="trailing" constant="32" id="eEl-PJ-NbS"/>
<constraint firstItem="0pO-gl-umK" firstAttribute="top" secondItem="cma-Te-Ut1" secondAttribute="bottom" constant="24" id="eqm-MY-nO8"/>
@ -405,6 +422,7 @@ COVIDSafe does not send pairing requests.</string>
<constraint firstItem="DAg-5r-Sgd" firstAttribute="leading" secondItem="SpU-i1-aUA" secondAttribute="leading" constant="32" id="qRs-1i-saU"/>
<constraint firstItem="cma-Te-Ut1" firstAttribute="top" secondItem="Vco-tH-Zts" secondAttribute="bottom" constant="24" id="tAa-dy-jEx"/>
<constraint firstAttribute="trailing" secondItem="0pO-gl-umK" secondAttribute="trailing" constant="32" id="tnK-TI-UxL"/>
<constraint firstItem="vam-jJ-7TM" firstAttribute="top" secondItem="0pO-gl-umK" secondAttribute="bottom" constant="16" id="yHr-8k-q7c"/>
</constraints>
<variation key="heightClass=compact-widthClass=compact">
<mask key="subviews">
@ -460,6 +478,7 @@ COVIDSafe does not send pairing requests.</string>
<nil key="simulatedTopBarMetrics"/>
<connections>
<outlet property="stepCounterLabel" destination="Vco-tH-Zts" id="NsV-Af-g7D"/>
<outlet property="topContentTextView" destination="vam-jJ-7TM" id="gPO-BM-gLt"/>
<segue destination="GaQ-f5-ei6" kind="show" identifier="showSuccessSegue" id="Fee-nc-b99"/>
</connections>
</viewController>
@ -1257,10 +1276,10 @@ and save lives.</string>
</objects>
<point key="canvasLocation" x="-8561" y="1143"/>
</scene>
<!--Onboarding Step 1b View Controller-->
<!--How It Works View Controller-->
<scene sceneID="f3t-QQ-sex">
<objects>
<viewController storyboardIdentifier="onboardingStep1b" modalTransitionStyle="crossDissolve" modalPresentationStyle="fullScreen" useStoryboardIdentifierAsRestorationIdentifier="YES" id="szA-K1-DiB" userLabel="Onboarding Step 1b View Controller" customClass="OnboardingStep1bViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<viewController storyboardIdentifier="onboardingStep1b" modalTransitionStyle="crossDissolve" modalPresentationStyle="fullScreen" useStoryboardIdentifierAsRestorationIdentifier="YES" id="szA-K1-DiB" customClass="HowItWorksViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="e8O-pH-ICg">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -1597,10 +1616,10 @@ For more information please refer to the *Help Topics* page.</string>
</objects>
<point key="canvasLocation" x="-3633.3333333333335" y="-1212.2282608695652"/>
</scene>
<!--Onboarding Step 1a View Controller-->
<!--Privacy Policy View Controller-->
<scene sceneID="R6W-kj-4rq">
<objects>
<viewController storyboardIdentifier="onboardingStep1a" modalTransitionStyle="crossDissolve" modalPresentationStyle="fullScreen" useStoryboardIdentifierAsRestorationIdentifier="YES" id="JMg-fD-wpp" customClass="OnboardingStep1aViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<viewController storyboardIdentifier="onboardingStep1a" modalTransitionStyle="crossDissolve" modalPresentationStyle="fullScreen" useStoryboardIdentifierAsRestorationIdentifier="YES" id="JMg-fD-wpp" userLabel="Privacy Policy View Controller" customClass="PrivacyPolicyViewController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NCU-xg-1ta">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
@ -2261,9 +2280,9 @@ See the COVIDSafe *privacy policy* for further details about your rights about y
</scene>
</scenes>
<inferredMetricsTieBreakers>
<segue reference="bMl-IY-pjw"/>
<segue reference="4eL-1Z-MtW"/>
<segue reference="dge-8I-Ehz"/>
<segue reference="rHc-rZ-59x"/>
<segue reference="XiO-Zp-pOg"/>
<segue reference="Fee-nc-b99"/>
</inferredMetricsTieBreakers>
<resources>
<image name="AppPermissions1" width="311" height="188"/>

View file

@ -11,7 +11,7 @@
<!--Upload Data Navigation Controller-->
<scene sceneID="Qjz-CC-Kr9">
<objects>
<navigationController navigationBarHidden="YES" id="Nnq-F9-Zjn" customClass="UploadDataNavigationController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<navigationController storyboardIdentifier="uploadDataNavigationController" navigationBarHidden="YES" id="Nnq-F9-Zjn" customClass="UploadDataNavigationController" customModule="COVIDSafe" customModuleProvider="target" sceneMemberID="viewController">
<navigationBar key="navigationBar" hidden="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="qRv-Xf-UaQ">
<autoresizingMask key="autoresizingMask"/>
</navigationBar>

View file

@ -16,7 +16,10 @@ struct BluetraceConfig {
static let ProtocolVersion = 2
static let CentralScanInterval = 60.0 // in seconds
static let PayloadExpiry = 20.0 // in seconds
static let PeripheralPayloadExpiry = TimeInterval.minute * 5
static let PeripheralCleanInterval = 120.0 // in seconds
static let PeripheralPayloadSaveInterval = 30.0 // in seconds
static let CentralScanDuration = 10 // in seconds
static let DummyModel = ""

View file

@ -2,24 +2,58 @@ import UIKit
import CoreData
import CoreBluetooth
class BluetraceManager {
private var peripheralController: PeripheralController!
private var centralController: CentralController!
var queue: DispatchQueue!
var bluetoothDidUpdateStateCallback: ((CBManagerState) -> Void)?
class BluetraceManager: SensorDelegate {
private let logger = ConcreteSensorLogger(subsystem: "Herald", category: "BluetraceManager")
var sensorDidUpdateStateCallback: ((SensorState, SensorType?) -> Void)?
// Payload data supplier, sensor and contact log
var payloadDataSupplier: PayloadDataSupplier?
var sensor: Sensor?
static let shared = BluetraceManager()
private init() {
queue = DispatchQueue(label: "BluetraceManager")
peripheralController = PeripheralController(peripheralName: "TR", queue: queue)
centralController = CentralController(queue: queue)
centralController.centralDidUpdateStateCallback = centralDidUpdateStateCallback
var bleSensorState: SensorState = .unavailable
var awakeSensorState: SensorState = .unavailable
private var didRecordPayloadData: [String:Date] = [:]
private var didWriteLegacyPayloadData: [String:Date] = [:]
func turnOnBLE() {
payloadDataSupplier = EncounterMessageManager.shared
if sensor == nil {
sensor = SensorArray(payloadDataSupplier!)
sensor?.add(delegate: self)
}
sensor?.start()
}
func turnOn() {
peripheralController.turnOn()
centralController.turnOn()
func turnOnAllSensors() {
payloadDataSupplier = EncounterMessageManager.shared
if sensor == nil {
sensor = SensorArray(payloadDataSupplier!)
sensor?.add(delegate: self)
// we are going to turn on one at a time
let previousSensorDidUpdateStateCallback = sensorDidUpdateStateCallback
sensorDidUpdateStateCallback = { state, type in
if (state == .on ) {
self.sensorDidUpdateStateCallback = previousSensorDidUpdateStateCallback
self.turnOnLocationSensor()
}
}
}
sensor?.start()
}
func turnOnLocationSensor() {
guard let sensorArray = sensor as? SensorArray else {
return
}
DispatchQueue.main.async {
sensorArray.startAwakeSensor()
}
}
func isBluetoothAuthorized() -> Bool {
@ -31,26 +65,161 @@ class BluetraceManager {
}
func isBluetoothOn() -> Bool {
return centralController.getState() == .poweredOn
return bleSensorState == .on
}
func isLocationOnAuthorized() -> Bool {
return awakeSensorState == .on
}
func centralDidUpdateStateCallback(_ state: CBManagerState) {
bluetoothDidUpdateStateCallback?(state)
if state == .poweredOn {
sensorDidUpdateStateCallback?(.on, .BLE)
} else {
sensorDidUpdateStateCallback?(.off, .BLE)
}
}
func toggleAdvertisement(_ state: Bool) {
if state {
peripheralController.turnOn()
sensor?.start()
} else {
peripheralController.turnOff()
sensor?.stop()
}
}
func toggleScanning(_ state: Bool) {
if state {
centralController.turnOn()
sensor?.start()
} else {
centralController.turnOff()
sensor?.stop()
}
}
func cleanRecordsData( records: inout [String:Date], expiryInterval: TimeInterval) {
let removeDataBefore = Date() - expiryInterval
let recentDidWritePayloadData = records.filter({ $0.value >= removeDataBefore })
records = recentDidWritePayloadData
}
func saveEncounter(payload: PayloadData, proximity: Proximity, txPower: Int? ) throws {
let peripheralCharData = try JSONDecoder().decode(PeripheralCharacteristicsData.self, from: payload)
var encounterStruct = EncounterRecord(rssi: proximity.value, txPower: txPower == nil ? nil : Double(txPower!))
encounterStruct.msg = peripheralCharData.msg
encounterStruct.update(modelP: peripheralCharData.modelP)
encounterStruct.org = peripheralCharData.org
encounterStruct.v = peripheralCharData.v
encounterStruct.timestamp = Date()
// here the remote blob will be msg and modelp if v1, msg if v2
// local blob will be rssi, txpower, modelc
try encounterStruct.saveRemotePeripheralToCoreData()
}
func getIdentifier(forDevice: BLEDevice) -> String {
return forDevice.pseudoDeviceAddress != nil ? "\(forDevice.pseudoDeviceAddress!.address)" : forDevice.identifier
}
func shouldSaveEncounter(forDeviceIdentifier: String) -> Bool {
guard didRecordPayloadData[forDeviceIdentifier] == nil || abs(didRecordPayloadData[forDeviceIdentifier]!.timeIntervalSinceNow) > BluetraceConfig.PeripheralPayloadSaveInterval else {
return false
}
return true
}
// MARK:- SensorDelegate
func sensor(_ sensor: SensorType, didDetect: TargetIdentifier) {
logger.info(sensor.rawValue + ",didDetect=" + didDetect.description)
}
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier) {
logger.info(sensor.rawValue + ",didMeasure=" + didMeasure.description + ",fromTarget=" + fromTarget.description)
// Make a call to the messages API if needed.
// Dispatch in background queue
DispatchQueue.global(qos: .background).async {
MessageAPI.getMessagesIfNeeded() { (messageResponse, error) in
if let error = error {
DLog("Get messages error: \(error.localizedDescription)")
}
// We currently dont do anything with the response. Messages are delivered via APN
}
}
}
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier, withPayload: PayloadData, forDevice: BLEDevice) {
logger.info(sensor.rawValue + ",didMeasure=" + didMeasure.description + ",fromTarget=" + fromTarget.description + ",withPayload=" + withPayload.shortName)
let deviceIdentifier = getIdentifier(forDevice: forDevice)
guard shouldSaveEncounter(forDeviceIdentifier: deviceIdentifier) else {
logger.info("didMeasure, skipping save as time since last saved too recet fromTarget=" + fromTarget.description)
return
}
didRecordPayloadData[deviceIdentifier] = Date()
cleanRecordsData(records: &didRecordPayloadData, expiryInterval: BluetraceConfig.PeripheralPayloadExpiry)
do {
logger.debug("Saving on didMeasure fromTarget=" + fromTarget.description)
try saveEncounter(payload: withPayload, proximity: didMeasure, txPower: nil)
} catch {
logger.fault("ERROR "+sensor.rawValue + ",didMeasure=" + didMeasure.description + ",fromTarget=" + fromTarget.description + ",withPayload=" + withPayload.shortName )
}
}
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier) {
do {
let dataFromCentral = try JSONDecoder().decode(CentralWriteData.self, from: didRead)
logger.info(sensor.rawValue + ",didRead=" + dataFromCentral.msg + ",fromTarget=" + fromTarget.description)
} catch {
logger.fault(sensor.rawValue + ",didRead=" + didRead.shortName + ",fromTarget=" + fromTarget.description)
}
}
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier, atProximity: Proximity, withTxPower: Int?) {
logger.info(sensor.rawValue + ",didRead=\(didRead.shortName))" + ",fromTarget=" + fromTarget.description + ",atProximity=" + atProximity.description + ",withTxPower=\(String(describing: withTxPower))")
}
func sensor(_ sensor: SensorType, didShare: [PayloadData], fromTarget: TargetIdentifier, atProximity: Proximity) {
let payloads = didShare.map { $0.shortName }
logger.info(sensor.rawValue + ",didShare=" + payloads.description + ",fromTarget=" + fromTarget.description)
}
func sensor(_ sensor: SensorType, didUpdateState: SensorState) {
logger.info(sensor.rawValue + ",didUpdateState=" + didUpdateState.rawValue)
if sensor == .BLE {
bleSensorState = didUpdateState
sensorDidUpdateStateCallback?(didUpdateState, sensor)
}
if sensor == .AWAKE {
awakeSensorState = didUpdateState
sensorDidUpdateStateCallback?(didUpdateState, sensor)
}
}
func shouldWriteToLegacyDevice(_ device: BLEDevice) -> Bool {
guard device.legacyPayloadCharacteristic != nil &&
device.payloadCharacteristic == nil else {
return false
}
let cleanInterval = BluetraceConfig.PeripheralPayloadExpiry
let writeInterval = BluetraceConfig.PeripheralPayloadSaveInterval
let deviceIdentifier = getIdentifier(forDevice: device)
cleanRecordsData(records: &didWriteLegacyPayloadData, expiryInterval: cleanInterval)
guard didWriteLegacyPayloadData[deviceIdentifier] == nil || abs(didWriteLegacyPayloadData[deviceIdentifier]!.timeIntervalSinceNow) > writeInterval else {
return false
}
return true
}
func didWriteToLegacyDevice(_ device: BLEDevice) {
let deviceIdentifier = getIdentifier(forDevice: device)
didWriteLegacyPayloadData[deviceIdentifier] = Date()
}
}

View file

@ -1,45 +0,0 @@
//
// BluetraceUtils.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import Foundation
import CoreBluetooth
class BluetraceUtils {
static func centralStateToString(_ state: CBManagerState) -> String {
switch state {
case .poweredOff:
return "poweredOff"
case .poweredOn:
return "poweredOn"
case .resetting:
return "resetting"
case .unauthorized:
return "unauthorized"
case .unknown:
return "unknown"
case .unsupported:
return "unsupported"
default:
return "unknown"
}
}
static func peripheralStateToString(_ state: CBPeripheralState) -> String {
switch state {
case .disconnected:
return "disconnected"
case .connecting:
return "connecting"
case .connected:
return "connected"
case .disconnecting:
return "disconnecting"
default:
return "unknown"
}
}
}

View file

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina5_5" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="CSGenericContentViewController" customModule="COVIDSafe" customModuleProvider="target">
<connections>
<outlet property="actionButton" destination="pS6-bO-9yg" id="sYw-k5-R8U"/>
<outlet property="contentDescriptionText" destination="X8s-K8-VFq" id="YaF-PU-7SZ"/>
<outlet property="contentIllustration" destination="GdB-8O-YjS" id="V3F-Vb-uDL"/>
<outlet property="contentTitleLabel" destination="DwC-ld-pUl" id="OYv-w1-lOH"/>
<outlet property="stepCounterLabel" destination="yhY-Mm-uhw" id="epJ-LV-aL5"/>
<outlet property="view" destination="iN0-l3-epB" id="3J4-LH-feh"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="f5Y-Hn-xEY">
<rect key="frame" x="0.0" y="0.0" width="414" height="623"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="PA9-VS-Ata" userLabel="ContentView">
<rect key="frame" x="0.0" y="0.0" width="414" height="881"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="A0s-lP-iFl">
<rect key="frame" x="32" y="24" width="350" height="857"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Step x of x" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yhY-Mm-uhw">
<rect key="frame" x="0.0" y="0.0" width="350" height="19.333333333333332"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="AppPermissions1" translatesAutoresizingMaskIntoConstraints="NO" id="GdB-8O-YjS">
<rect key="frame" x="0.0" y="43.333333333333314" width="350" height="188"/>
<constraints>
<constraint firstAttribute="height" constant="188" id="cYY-sp-rPl"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Screen Title" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DwC-ld-pUl">
<rect key="frame" x="0.0" y="255.33333333333329" width="350" height="33.666666666666657"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" staticText="YES" header="YES"/>
</accessibility>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" bouncesZoom="NO" editable="NO" textAlignment="natural" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="X8s-K8-VFq">
<rect key="frame" x="0.0" y="313" width="350" height="544"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<color key="tintColor" red="0.0" green="0.40000000000000002" blue="0.1058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<string key="text">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin vel sapien a sapien dignissim tempus. Vestibulum ut ante ipsum. Vivamus lobortis hendrerit elit, non maximus nulla maximus in. Vivamus euismod elementum finibus. Nulla leo leo, gravida vel enim in, ultricies fringilla orci. Duis tincidunt vel tortor quis pellentesque. Fusce sit amet elit massa. Cras tellus tortor, convallis id vulputate sit amet, accumsan sit amet nisl.
Praesent viverra pretium lobortis. In laoreet at leo non viverra. Nullam sagittis nulla a lobortis auctor. Donec a pulvinar odio, eget eleifend velit. Maecenas vulputate eget libero at malesuada. Vestibulum orci nulla, consectetur ut lobortis a, tincidunt vel mauris. Integer et eros pharetra, porta sem et, rhoncus mauris. Duis porta semper malesuada.</string>
<color key="textColor" systemColor="labelColor"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="A0s-lP-iFl" firstAttribute="leading" secondItem="PA9-VS-Ata" secondAttribute="leading" constant="32" id="CD7-gV-If4"/>
<constraint firstItem="A0s-lP-iFl" firstAttribute="top" secondItem="PA9-VS-Ata" secondAttribute="top" constant="24" id="SOd-SJ-DLZ"/>
<constraint firstAttribute="bottom" secondItem="A0s-lP-iFl" secondAttribute="bottom" id="mxD-jf-tst"/>
<constraint firstAttribute="trailing" secondItem="A0s-lP-iFl" secondAttribute="trailing" constant="32" id="pbp-mf-DUM"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="PA9-VS-Ata" secondAttribute="trailing" id="UKv-58-Gea"/>
<constraint firstAttribute="bottom" secondItem="PA9-VS-Ata" secondAttribute="bottom" id="eq0-cS-sUs"/>
<constraint firstItem="PA9-VS-Ata" firstAttribute="leading" secondItem="f5Y-Hn-xEY" secondAttribute="leading" id="f7i-qf-xzj"/>
<constraint firstItem="PA9-VS-Ata" firstAttribute="top" secondItem="f5Y-Hn-xEY" secondAttribute="top" id="hBS-kh-b3c"/>
<constraint firstItem="PA9-VS-Ata" firstAttribute="width" secondItem="f5Y-Hn-xEY" secondAttribute="width" id="kOg-xd-wbK"/>
</constraints>
</scrollView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pS6-bO-9yg">
<rect key="frame" x="32" y="655" width="350" height="49"/>
<color key="backgroundColor" red="0.0" green="0.40000000000000002" blue="0.1058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="49" id="hSQ-bH-GZs"/>
</constraints>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<state key="normal" title="Button Label">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="pS6-bO-9yg" secondAttribute="bottom" constant="32" id="2o9-fJ-zq2"/>
<constraint firstItem="pS6-bO-9yg" firstAttribute="top" secondItem="f5Y-Hn-xEY" secondAttribute="bottom" constant="32" id="AWh-yg-NLr"/>
<constraint firstItem="f5Y-Hn-xEY" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="BNc-2U-Sh4"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="pS6-bO-9yg" secondAttribute="trailing" constant="32" id="J7k-aP-oh5"/>
<constraint firstItem="f5Y-Hn-xEY" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="dPk-ul-WHy"/>
<constraint firstItem="f5Y-Hn-xEY" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="ftn-U3-tkK"/>
<constraint firstItem="pS6-bO-9yg" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="32" id="gc9-xx-gfg"/>
</constraints>
<point key="canvasLocation" x="140.57971014492756" y="78.348214285714278"/>
</view>
</objects>
<resources>
<image name="AppPermissions1" width="311" height="188"/>
<systemColor name="labelColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View file

@ -0,0 +1,73 @@
//
// CSGenericContentViewController.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import UIKit
import SafariServices
class CSGenericContentViewController: UIViewController {
@IBOutlet weak var stepCounterLabel: UILabel!
@IBOutlet weak var contentIllustration: UIImageView!
@IBOutlet weak var contentTitleLabel: UILabel!
@IBOutlet weak var contentDescriptionText: UITextView!
@IBOutlet weak var actionButton: UIButton!
var contentViewModel: CSGenericContentViewModel?
override func viewDidLoad() {
super.viewDidLoad()
guard let viewModel = contentViewModel else {
return
}
// set the step counter
if let contentStep = viewModel.contentStepNumber, let contentTotal = viewModel.contentStepTotal {
stepCounterLabel.text = String.localizedStringWithFormat( "stepCounter".localizedString(),
contentStep,
contentTotal
)
} else {
stepCounterLabel.text = ""
stepCounterLabel.isHidden = true
}
// set the illustration
if let illustration = viewModel.contentIllustration {
contentIllustration.image = illustration
}
// set title and content
contentTitleLabel.text = viewModel.viewTitle
contentDescriptionText.attributedText = viewModel.viewContentDescription
contentDescriptionText.parseHTMLTags()
contentDescriptionText.addAllBold(enclosedIn: "#")
//set button label and action
actionButton.setTitle(viewModel.buttonLabel, for: .normal)
actionButton.addTarget(self, action: #selector(pressed), for: .touchUpInside)
}
@objc func pressed(sender: UIButton!) {
guard let viewModel = contentViewModel else {
return
}
viewModel.buttonCallback()
}
}
struct CSGenericContentViewModel {
var viewTitle: String
var viewContentDescription: NSAttributedString
var buttonLabel: String
var buttonCallback: () -> Void
var contentIllustration: UIImage?
var contentStepNumber: Int?
var contentStepTotal: Int?
}

View file

@ -1,496 +0,0 @@
//
// CentralController.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import Foundation
import CoreData
import CoreBluetooth
import UIKit
struct CentralWriteData: Codable {
var modelC: String // phone model of central
var rssi: Double
var txPower: Double?
var msg: String // tempID
var org: String
var v: Int
}
class CentralController: NSObject {
enum CentralError: Error {
case centralAlreadyOn
case centralAlreadyOff
}
var centralDidUpdateStateCallback: ((CBManagerState) -> Void)?
var characteristicDidReadValue: ((EncounterRecord) -> Void)?
private let restoreIdentifierKey = "com.joelkek.tracer.central"
private var central: CBCentralManager?
private var recoveredPeripherals: [CBPeripheral] = []
private var cleanupPeripherals: [CBPeripheral] = []
private var queue: DispatchQueue
// This dict is to keep track of discovered android devices, so that i do not connect to the same android device multiple times within the same BluetraceConfig.CentralScanInterval
private var discoveredAndroidPeriManufacturerToUUIDMap = [Data: UUID]()
// This dict has 2 purpose
// 1. To store all the EncounterRecord, because the RSSI and TxPower is gotten at the didDiscoverPeripheral delegate, but the characterstic value is gotten at didUpdateValueForCharacteristic delegate
// 2. Use to check for duplicated iphones peripheral being discovered, so that i dont connect to the same iphone again in the same scan window
private var scannedPeripherals = [UUID: (lastUpdated: Date, peripheral: CBPeripheral, encounter: EncounterRecord)]() // stores the peripherals encountered within one scan interval
var timerForScanning: Timer?
private var lastCleanedScannedPeripherals = Date()
public init(queue: DispatchQueue) {
self.queue = queue
super.init()
NotificationCenter.default.addObserver(
forName: UIApplication.didReceiveMemoryWarningNotification,
object: nil,
queue: .main) { [weak self] notification in
self?.queue.async {
self?.cleanupScannedPeripherals()
}
}
}
func turnOn() {
DLog("CC requested to be turnOn")
guard central == nil else {
return
}
let options: [String: Any] = [CBCentralManagerOptionRestoreIdentifierKey: restoreIdentifierKey,
CBCentralManagerOptionShowPowerAlertKey: NSNumber(true)]
central = CBCentralManager(delegate: self, queue: self.queue, options: options )
}
func turnOff() {
DLog("CC turnOff")
guard central != nil else {
return
}
central?.stopScan()
central = nil
}
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
}
public func getState() -> CBManagerState? {
return central?.state
}
public func logPeripheralsCount(description: String) {
#if DEBUG
guard let peripherals = central?.retrieveConnectedPeripherals(withServices: [BluetraceConfig.BluetoothServiceID]) else {
return
}
var connected = 0
var connecting = 0
var disconnected = 0
var disconnecting = 0
var unknown = 0
for peripheral in peripherals {
switch peripheral.state {
case .connecting:
connecting+=1
case .connected:
connected+=1
case .disconnected:
disconnected+=1
case .disconnecting:
disconnecting+=1
default:
unknown+=1
}
}
let bleLogStr = "CC \(description) Current peripherals \nconnected: \(connected), \nconnecting: \(connecting), \ndisconnected: \(disconnected), \ndisconnecting: \(disconnecting), \nunknown: \(unknown), \nscannedPeripherals: \(scannedPeripherals.count)"
let logRecord = BLELogRecord(message: bleLogStr)
logRecord.saveToCoreData()
#endif
}
}
extension CentralController: CBCentralManagerDelegate {
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
DLog("CC willRestoreState. Central state: \(BluetraceUtils.centralStateToString(central.state))")
recoveredPeripherals = []
if let peripheralsObject = dict[CBCentralManagerRestoredStatePeripheralsKey] {
let peripherals = peripheralsObject as! Array<CBPeripheral>
DLog("CC restoring \(peripherals.count) peripherals from system.")
logPeripheralsCount(description: "Restoring peripherals")
for peripheral in peripherals {
if peripheral.state == .connected {
// only recover connected peripherals, dispose/disconnect otherwise.
recoveredPeripherals.append(peripheral)
peripheral.delegate = self
} else {
cleanupPeripherals.append(peripheral)
}
}
logPeripheralsCount(description: "Done Restoring peripherals")
}
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
centralDidUpdateStateCallback?(central.state)
switch central.state {
case .poweredOn:
DLog("CC Starting a scan")
// for all peripherals that are not disconnected, disconnect them
self.scannedPeripherals.forEach { (scannedPeri) in
central.cancelPeripheralConnection(scannedPeri.value.peripheral)
}
// clear all peripherals, such that a new scan window can take place
self.scannedPeripherals = [UUID: (Date, CBPeripheral, EncounterRecord)]()
self.discoveredAndroidPeriManufacturerToUUIDMap = [Data: UUID]()
// handle a state restoration scenario
for recoveredPeripheral in recoveredPeripherals {
var restoredEncounter = EncounterRecord(rssi: 0, txPower: nil)
restoredEncounter.timestamp = nil
scannedPeripherals.updateValue((Date(), recoveredPeripheral, restoredEncounter),
forKey: recoveredPeripheral.identifier)
central.connect(recoveredPeripheral)
}
// can't cancel peripheral when BL OFF
for cleanupPeripheral in self.cleanupPeripherals {
central.cancelPeripheralConnection(cleanupPeripheral)
}
self.cleanupPeripherals = []
central.scanForPeripherals(withServices: [BluetraceConfig.BluetoothServiceID], options:nil)
logPeripheralsCount(description: "Update state powerOn")
default:
DLog("State chnged to \(central.state)")
}
}
func handlePeripheralOfUncertainStatus(_ peripheral: CBPeripheral) {
// If not connected to Peripheral, attempt connection and exit
if peripheral.state != .connected {
DLog("CC handlePeripheralOfUncertainStatus not connected")
central?.connect(peripheral)
return
}
// If don't know about Peripheral's services, discover services and exit
if peripheral.services == nil {
DLog("CC handlePeripheralOfUncertainStatus unknown services")
peripheral.discoverServices([BluetraceConfig.BluetoothServiceID])
return
}
// If Peripheral's services don't contain targetID, disconnect and remove, then exit.
// If it does contain targetID, discover characteristics for service
guard let service = peripheral.services?.first(where: { $0.uuid == BluetraceConfig.BluetoothServiceID }) else {
DLog("CC handlePeripheralOfUncertainStatus no matching Services")
central?.cancelPeripheralConnection(peripheral)
return
}
DLog("CC handlePeripheralOfUncertainStatus discoverCharacteristics")
peripheral.discoverCharacteristics([BluetraceConfig.BluetoothServiceID], for: service)
// If Peripheral's service's characteristics don't contain targetID, disconnect and remove, then exit.
// If it does contain targetID, read value for characteristic
guard let characteristic = service.characteristics?.first(where: { $0.uuid == BluetraceConfig.BluetoothServiceID}) else {
DLog("CC handlePeripheralOfUncertainStatus no matching Characteristics")
central?.cancelPeripheralConnection(peripheral)
return
}
DLog("CC handlePeripheralOfUncertainStatus readValue")
peripheral.readValue(for: characteristic)
return
}
fileprivate func cleanupScannedPeripherals() {
DLog("CC scannedPeripherals/pending connections cleanup \(scannedPeripherals.count)")
scannedPeripherals = scannedPeripherals.filter { scannedPeripheral in
// if has been sitting in scanned for over 2 mins, remove
if abs(scannedPeripheral.value.lastUpdated.timeIntervalSinceNow) > BluetraceConfig.PeripheralCleanInterval {
// expired timestamp, remove
cleanupPeripherals.append(scannedPeripheral.value.peripheral)
return false
} else {
// not yet expired timestamp, keep this scanned peripheral data
return true
}
}
// remove android manufacturer data where peripheral has been removed
discoveredAndroidPeriManufacturerToUUIDMap = discoveredAndroidPeriManufacturerToUUIDMap.filter(
{ andperi in
return !scannedPeripherals.keys.contains(andperi.value)}
)
guard let central = central else {
DLog("CC central is nil, unable to clean peripherals at the moment")
return
}
for cleanupPeripheral in cleanupPeripherals {
central.cancelPeripheralConnection(cleanupPeripheral)
}
cleanupPeripherals = []
lastCleanedScannedPeripherals = Date()
DLog("CC post scannedPeripherals/pending connections cleanup \(scannedPeripherals.count)")
return
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
let debugLogs = ["CentralState": BluetraceUtils.centralStateToString(central.state),
"peripheral": peripheral,
"advertisments": advertisementData as AnyObject] as AnyObject
// dispatch in bluetrace queue
DispatchQueue.global(qos: .background).async {
MessageAPI.getMessagesIfNeeded() { (messageResponse, error) in
if let error = error {
DLog("Get messages error: \(error.localizedDescription)")
}
// We currently dont do anything with the response. Messages are delivered via APN
}
}
DLog("\(debugLogs)")
// regularly cleanup and close pending connections
if abs(lastCleanedScannedPeripherals.timeIntervalSince(Date())) > BluetraceConfig.CentralScanInterval {
cleanupScannedPeripherals()
}
var initialEncounter = EncounterRecord(rssi: RSSI.doubleValue, txPower: advertisementData[CBAdvertisementDataTxPowerLevelKey] as? Double)
initialEncounter.timestamp = nil
// iphones will "mask" the peripheral's identifier for android devices, resulting in the same android device being discovered multiple times with different peripheral identifier. Hence Android is using use CBAdvertisementDataServiceDataKey data for identifying an android pheripheral
// Also, check that the length is greater than 2 to prevent crash. Otherwise ignore.
if let manuData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data, manuData.count > 2 {
let androidIdentifierData = manuData.subdata(in: 2..<manuData.count)
if discoveredAndroidPeriManufacturerToUUIDMap.keys.contains(androidIdentifierData) {
DLog("Android Peripheral \(peripheral) has been discovered already in this window, will not attempt to connect to it again")
return
} else {
peripheral.delegate = self
discoveredAndroidPeriManufacturerToUUIDMap.updateValue(peripheral.identifier, forKey: androidIdentifierData)
scannedPeripherals.updateValue((Date(), peripheral, initialEncounter), forKey: peripheral.identifier)
central.connect(peripheral)
}
} else {
// Means not android device, i will check if the peripheral.identifier exist in the scannedPeripherals
DLog("CBAdvertisementDataManufacturerDataKey Data not found. Peripheral is likely not android")
logPeripheralsCount(description: "begin didDiscover iOS device")
if let encounteredPeripheral = scannedPeripherals[peripheral.identifier] {
if shouldReconnectToPeripheral(peripheral: encounteredPeripheral.peripheral) {
peripheral.delegate = self
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 {
DLog("It was found \(scannedDate.timeIntervalSinceNow) seconds ago")
}
}
} else {
peripheral.delegate = self
scannedPeripherals.updateValue((Date(), peripheral, initialEncounter), forKey: peripheral.identifier)
central.connect(peripheral)
}
logPeripheralsCount(description: "finish didDiscover iOS device")
}
}
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 let seenPeripheral = scannedPeripherals[peripheral.identifier], shouldRecordEncounter(seenPeripheral.encounter) else {
central.cancelPeripheralConnection(peripheral)
return
}
peripheral.delegate = self
peripheral.readRSSI()
peripheral.discoverServices([BluetraceConfig.BluetoothServiceID])
logPeripheralsCount(description: "didConnect peripheral")
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
DLog("CC didDisconnectPeripheral \(peripheral) , \(error != nil ? "error: \(error.debugDescription)" : "" )")
// We check that the periferal has been scanned and that there is no error.
// No error indicates that cancelPeripheralConnection was called.
// An error may represent that the peripheral is out of range, BL is OFF etc. In that case we don't want to retry.
guard scannedPeripherals[peripheral.identifier] != nil && error == nil else {
// Remove from scanned peripherals as got diconnected due to error. Also look after memory.
scannedPeripherals.removeValue(forKey: peripheral.identifier)
central.cancelPeripheralConnection(peripheral)
return
}
// only attempt to reconnect if the peripheral is in the scanned dictionary and there was no error.
if #available(iOS 12, *) {
let options = [CBConnectPeripheralOptionStartDelayKey: NSNumber(15)]
central.connect(peripheral, options: options)
}
logPeripheralsCount(description: "didDisconnect peripheral")
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
DLog("CC didFailToConnect peripheral \(error != nil ? "error: \(error.debugDescription)" : "" )")
// Remove from scanned peripherals as connection failed. Also look after memory.
scannedPeripherals.removeValue(forKey: peripheral.identifier)
// by cancelling the connection we are being extra sure the peripheral is fully
// disconnected and not left in a pending state
central.cancelPeripheralConnection(peripheral)
logPeripheralsCount(description: "didFailToConnect peripheral")
}
}
extension CentralController: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
if let err = error {
DLog("error: \(err)")
}
if error == nil {
if let existingPeripheral = scannedPeripherals[peripheral.identifier] {
var scannedEncounter = existingPeripheral.encounter
scannedEncounter.rssi = RSSI.doubleValue
scannedPeripherals.updateValue((Date(), existingPeripheral.peripheral, scannedEncounter), forKey: peripheral.identifier)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
DLog("Peripheral: \(peripheral) didModifyServices: \(invalidatedServices)")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let err = error {
DLog("error: \(err)")
}
guard let service = peripheral.services?.first(where: { $0.uuid == BluetraceConfig.BluetoothServiceID }) else { return }
peripheral.discoverCharacteristics([BluetraceConfig.BluetoothServiceID], for: service)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let err = error {
DLog("error: \(err)")
}
guard let characteristic = service.characteristics?.first(where: { $0.uuid == BluetraceConfig.BluetoothServiceID}) else { return }
peripheral.readValue(for: characteristic)
// Do not need to wait for a successful read before writing, because no data from the read is needed in the write
if let currEncounter = scannedPeripherals[peripheral.identifier] {
EncounterMessageManager.shared.getTempId { (result) in
guard let tempId = result else {
DLog("broadcast msg not present")
return
}
guard let rssi = currEncounter.encounter.rssi else {
DLog("rssi should be present in \(currEncounter.encounter)")
return
}
let encounterToBroadcast = EncounterBlob(modelC: DeviceIdentifier.getModel(),
rssi: rssi,
txPower: currEncounter.encounter.txPower,
modelP: nil,
msg: tempId,
timestamp: Date().timeIntervalSince1970)
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 {
DLog("Error: \(error)")
}
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
let debugLogs = ["characteristic": characteristic as AnyObject,
"encounter": scannedPeripherals[peripheral.identifier] as AnyObject] as AnyObject
DLog("\(debugLogs)")
guard error == nil else {
DLog("Error: \(String(describing: error))")
return
}
if let scannedPeri = scannedPeripherals[peripheral.identifier],
let characteristicValue = characteristic.value,
shouldRecordEncounter(scannedPeri.encounter) {
do {
let peripheralCharData = try JSONDecoder().decode(PeripheralCharacteristicsData.self, from: characteristicValue)
var encounterStruct = scannedPeri.encounter
encounterStruct.msg = peripheralCharData.msg
encounterStruct.update(modelP: peripheralCharData.modelP)
encounterStruct.org = peripheralCharData.org
encounterStruct.v = peripheralCharData.v
encounterStruct.timestamp = Date()
scannedPeripherals.updateValue((Date(), scannedPeri.peripheral, encounterStruct), forKey: peripheral.identifier)
// 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 \(String(data: characteristicValue, encoding: .utf8) ?? "<nil>")")
}
} else {
DLog("Error: scannedPeripherals[peripheral.identifier] is \(String(describing: scannedPeripherals[peripheral.identifier])), characteristic.value is \(String(describing: characteristic.value))")
}
// regularly cleanup and close pending connections
if (abs(lastCleanedScannedPeripherals.timeIntervalSince(Date())) > BluetraceConfig.CentralScanInterval) {
cleanupScannedPeripherals()
}
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
DLog("didWriteValueFor to peripheral: \(peripheral), for characteristics: \(characteristic). \(error != nil ? "error: \(error.debugDescription)" : "" )")
central?.cancelPeripheralConnection(peripheral)
}
}

View file

@ -1,10 +1,34 @@
import Foundation
import CoreBluetooth
struct CentralWriteData: Codable {
var modelC: String // phone model of central
var rssi: Double
var txPower: Double?
var msg: String // tempID
var org: String
var v: Int
}
public struct PeripheralCharacteristicsData: Codable {
var modelP: String // phone model of peripheral
var msg: String // tempID
var org: String
var v: Int
}
class EncounterMessageManager {
let userDefaultsTempIdKey = "BROADCAST_MSG"
let userDefaultsAdvtKey = "ADVT_DATA"
let userDefaultsAdvtExpiryKey = "ADVT_EXPIRY"
struct CachedPayload {
var payload: Data,
expiry: TimeInterval
}
private var payloadLookaside = [UUID: CachedPayload]()
static let shared = EncounterMessageManager()
var tempId: String? {
@ -75,6 +99,92 @@ class EncounterMessageManager {
}
}
fileprivate func cleanUpExpiredCachedPayloads() {
for payloadKey in payloadLookaside.keys {
let currentTime = Date().timeIntervalSince1970
guard let payload = payloadLookaside[payloadKey], payload.expiry < currentTime else {
continue
}
// if payload exists and expiry time is less than current time, remove.
payloadLookaside.removeValue(forKey: payloadKey)
}
}
func getWritePayloadForCentral(device: BLEDevice, onComplete: @escaping (Data?) -> Void) {
guard let rssi = device.rssi else {
DLog("getWritePayloadForCentral failed, no rssi")
onComplete(nil)
return
}
guard device.legacyPayloadCharacteristic != nil else {
DLog("getWritePayloadForCentral failed, no legacyPayloadCharacteristic")
onComplete(nil)
return
}
getTempId { (result) in
guard let tempId = result else {
DLog("getWritePayloadForCentral failed, no tempid")
onComplete(nil)
return
}
var txPower: Double? = nil
if let bleTxPower = device.txPower {
txPower = Double(bleTxPower)
}
let encounterToBroadcast = EncounterBlob(modelC: DeviceIdentifier.getModel(),
rssi: Double(rssi),
txPower: txPower,
modelP: nil,
msg: tempId,
timestamp: Date().timeIntervalSince1970)
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)
onComplete(encodedData)
} catch {
DLog("Error: \(error)")
}
}
}
func getAdvertisementPayload(identifier: UUID, offset: Int, onComplete: @escaping (Data?) -> Void) {
cleanUpExpiredCachedPayloads()
guard offset > 0 else {
// new request coming in
getAdvertisementPayload{ (payloadToAdvertise) in
if let payload = payloadToAdvertise {
self.payloadLookaside[identifier] = CachedPayload(payload: payload, expiry: Date().timeIntervalSince1970 + BluetraceConfig.PayloadExpiry);
}
onComplete(payloadToAdvertise)
}
return
}
guard let cachedPayload = self.payloadLookaside[identifier] else {
// subsequent request but nothing cached
onComplete(nil)
return
}
onComplete(cachedPayload.payload)
}
func getLastKnownAdvertisementPayload(identifier: UUID) -> Data? {
guard let cachedPayload = self.payloadLookaside[identifier] else {
return nil
}
return cachedPayload.payload
}
// this will give herald the payload it's after
// gets the anon tempid for broadcasting
func getAdvertisementPayload(onComplete: @escaping (Data?) -> Void) {
// check expiry date of payload
@ -147,3 +257,36 @@ class EncounterMessageManager {
}
}
}
extension EncounterMessageManager: PayloadDataSupplier {
func payload(_ timestamp: PayloadTimestamp) -> PayloadData {
return advertisedPayload!
}
func payload(_ identifier: UUID, offset: Int, onComplete: @escaping (PayloadData?) -> Void) -> Void {
getAdvertisementPayload(identifier: identifier, offset: offset, onComplete: onComplete)
}
func payload(_ data: Data) -> [PayloadData] {
// We share only one payload at a time due to the length.
// No need to split payloads based on length or delimiter.
return [PayloadData(data)]
}
}
public extension PayloadData {
var shortName: String {
do {
let decodedPayload = try JSONDecoder().decode(PeripheralCharacteristicsData.self, from: self)
let message = decodedPayload.msg
return String(message.suffix(25))
} catch {
let startIndex = count >= 3 ? 3 : 0
let endIndex = count >= 3 ? count-3 : 0
return String(subdata(in: startIndex..<endIndex).base64EncodedString().prefix(6))
}
}
}

View file

@ -0,0 +1,337 @@
//
// BLEDatabase.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
import CoreBluetooth
/// Registry for collating fragments of information from asynchronous BLE operations.
protocol BLEDatabase {
/// Add delegate for handling database events
func add(delegate: BLEDatabaseDelegate)
/// Get or create device for collating information from asynchronous BLE operations.
func device(_ identifier: TargetIdentifier) -> BLEDevice
/// Get or create device for collating information from asynchronous BLE operations.
func device(_ peripheral: CBPeripheral, delegate: CBPeripheralDelegate) -> BLEDevice
/// Get or create device for collating information from asynchronous BLE operations.
func device(_ payload: PayloadData) -> BLEDevice
/// Get if a device exists
func hasDevice(_ payload: PayloadData) -> Bool
/// Get all devices
func devices() -> [BLEDevice]
/// Delete device from database
func delete(_ identifier: TargetIdentifier)
}
/// Delegate for receiving registry create/update/delete events
protocol BLEDatabaseDelegate {
func bleDatabase(didCreate device: BLEDevice)
func bleDatabase(didUpdate device: BLEDevice, attribute: BLEDeviceAttribute)
func bleDatabase(didDelete device: BLEDevice)
}
extension BLEDatabaseDelegate {
func bleDatabase(didCreate device: BLEDevice) {}
func bleDatabase(didUpdate device: BLEDevice, attribute: BLEDeviceAttribute) {}
func bleDatabase(didDelete device: BLEDevice) {}
}
class ConcreteBLEDatabase : NSObject, BLEDatabase, BLEDeviceDelegate {
private let logger = ConcreteSensorLogger(subsystem: "Sensor", category: "BLE.ConcreteBLEDatabase")
private var delegates: [BLEDatabaseDelegate] = []
private var database: [TargetIdentifier : BLEDevice] = [:]
private var queue = DispatchQueue(label: "Sensor.BLE.ConcreteBLEDatabase")
func add(delegate: BLEDatabaseDelegate) {
delegates.append(delegate)
}
func devices() -> [BLEDevice] {
return database.values.map { $0 }
}
func device(_ identifier: TargetIdentifier) -> BLEDevice {
if database[identifier] == nil {
let device = BLEDevice(identifier, delegate: self)
database[identifier] = device
queue.async {
self.logger.debug("create (device=\(identifier))")
self.delegates.forEach { $0.bleDatabase(didCreate: device) }
}
}
let device = database[identifier]!
return device
}
func device(_ peripheral: CBPeripheral, delegate: CBPeripheralDelegate) -> BLEDevice {
let identifier = TargetIdentifier(peripheral: peripheral)
if database[identifier] == nil {
let device = BLEDevice(identifier, delegate: self)
database[identifier] = device
queue.async {
self.logger.debug("create (device=\(identifier))")
self.delegates.forEach { $0.bleDatabase(didCreate: device) }
}
}
let device = database[identifier]!
if device.peripheral != peripheral {
device.peripheral = peripheral
peripheral.delegate = delegate
}
return device
}
func device(_ payload: PayloadData) -> BLEDevice {
if let device = database.values.filter({ $0.payloadData == payload }).first {
return device
}
// Create temporary UUID, the taskRemoveDuplicatePeripherals function
// will delete this when a direct connection to the peripheral has been
// established
let identifier = TargetIdentifier(UUID().uuidString)
let placeholder = device(identifier)
placeholder.payloadData = payload
return placeholder
}
func hasDevice(_ payload: PayloadData) -> Bool {
if database.values.filter({ $0.payloadData == payload }).first != nil {
return true
}
return false
}
func delete(_ identifier: TargetIdentifier) {
guard let device = database[identifier] else {
return
}
database[identifier] = nil
queue.async {
self.logger.debug("delete (device=\(identifier))")
self.delegates.forEach { $0.bleDatabase(didDelete: device) }
}
}
// MARK:- BLEDeviceDelegate
func device(_ device: BLEDevice, didUpdate attribute: BLEDeviceAttribute) {
queue.async {
self.logger.debug("update (device=\(device.identifier),attribute=\(attribute.rawValue))")
self.delegates.forEach { $0.bleDatabase(didUpdate: device, attribute: attribute) }
}
}
}
// MARK:- BLEDatabase data
public class BLEDevice : NSObject {
/// Device registratiion timestamp
let createdAt: Date
/// Last time anything changed, e.g. attribute update
var lastUpdatedAt: Date
/// Last time a wake up call was received from this device (iOS only)
var lastNotifiedAt: Date = Date.distantPast
/// Ephemeral device identifier, e.g. peripheral identifier UUID
let identifier: TargetIdentifier
/// Pseudo device address for tracking devices that change device identifier constantly like the Samsung A10, A20 and Note 8
var pseudoDeviceAddress: BLEPseudoDeviceAddress? {
didSet {
lastUpdatedAt = Date()
}}
/// Delegate for listening to attribute updates events.
let delegate: BLEDeviceDelegate
/// CoreBluetooth peripheral object for interacting with this device.
var peripheral: CBPeripheral? {
didSet {
lastUpdatedAt = Date()
delegate.device(self, didUpdate: .peripheral)
}}
/// Service characteristic for signalling between BLE devices, e.g. to keep awake
var signalCharacteristic: CBCharacteristic? {
didSet {
lastUpdatedAt = Date()
delegate.device(self, didUpdate: .signalCharacteristic)
}}
/// Service characteristic for reading payload data
var payloadCharacteristic: CBCharacteristic? {
didSet {
lastUpdatedAt = Date()
delegate.device(self, didUpdate: .payloadCharacteristic)
}}
var legacyPayloadCharacteristic: CBCharacteristic? {
didSet {
lastUpdatedAt = Date()
delegate.device(self, didUpdate: .payloadCharacteristic)
}}
/// Device operating system, this is necessary for selecting different interaction procedures for each platform.
var operatingSystem: BLEDeviceOperatingSystem = .unknown {
didSet {
lastUpdatedAt = Date()
delegate.device(self, didUpdate: .operatingSystem)
}}
/// Device is receive only, this is necessary for filtering payload sharing data
var receiveOnly: Bool = false {
didSet {
lastUpdatedAt = Date()
}}
/// Payload data acquired from the device via payloadCharacteristic read
var payloadData: PayloadData? {
didSet {
payloadDataLastUpdatedAt = Date()
lastUpdatedAt = payloadDataLastUpdatedAt
delegate.device(self, didUpdate: .payloadData)
}}
/// Payload data last update timestamp, this is used to determine what needs to be shared with peers.
var payloadDataLastUpdatedAt: Date = Date.distantPast
/// Payload data already shared with this peer
var payloadSharingData: [PayloadData] = []
/// Most recent RSSI measurement taken by readRSSI or didDiscover.
var rssi: BLE_RSSI? {
didSet {
lastUpdatedAt = Date()
rssiLastUpdatedAt = lastUpdatedAt
delegate.device(self, didUpdate: .rssi)
}}
/// RSSI last update timestamp, this is used to track last advertised at without relying on didDiscover
var rssiLastUpdatedAt: Date = Date.distantPast
/// Transmit power data where available (only provided by Android devices)
var txPower: BLE_TxPower? {
didSet {
lastUpdatedAt = Date()
delegate.device(self, didUpdate: .txPower)
}}
/// Track discovered at timestamp, used by taskConnect to prioritise connection when device runs out of concurrent connection capacity
var lastDiscoveredAt: Date = Date.distantPast
/// Track connect request at timestamp, used by taskConnect to prioritise connection when device runs out of concurrent connection capacity
var lastConnectRequestedAt: Date = Date.distantPast
/// Track connected at timestamp, used by taskConnect to prioritise connection when device runs out of concurrent connection capacity
var lastConnectedAt: Date? {
didSet {
// Reset lastDisconnectedAt
lastDisconnectedAt = nil
// Reset lastConnectionInitiationAttempt
lastConnectionInitiationAttempt = nil
}}
/// Track read payload request at timestamp, used by readPayload to de-duplicate requests from asynchronous calls
var lastReadPayloadRequestedAt: Date = Date.distantPast
/// Track Herald initiated connection attempts - workaround for iOS peripheral caching incorrect state bug
var lastConnectionInitiationAttempt: Date?
/// Track disconnected at timestamp, used by taskConnect to prioritise connection when device runs out of concurrent connection capacity
var lastDisconnectedAt: Date? {
didSet {
// Reset lastConnectionInitiationAttempt
lastConnectionInitiationAttempt = nil
}
}
/// Last advert timestamp, inferred from payloadDataLastUpdatedAt, payloadSharingDataLastUpdatedAt, rssiLastUpdatedAt
var lastAdvertAt: Date { get {
max(createdAt, lastDiscoveredAt, payloadDataLastUpdatedAt, rssiLastUpdatedAt)
}}
/// Time interval since last payload data update, this is used to identify devices that require a payload update.
var timeIntervalSinceLastPayloadDataUpdate: TimeInterval { get {
Date().timeIntervalSince(payloadDataLastUpdatedAt)
}}
/// Time interval since created at timestamp
var timeIntervalSinceCreated: TimeInterval { get {
Date().timeIntervalSince(createdAt)
}}
/// Time interval since last attribute value update, this is used to identify devices that may have expired and should be removed from the database.
var timeIntervalSinceLastUpdate: TimeInterval { get {
Date().timeIntervalSince(lastUpdatedAt)
}}
/// Time interval since last advert detected, this is used to detect concurrent connection quota and prioritise disconnections
var timeIntervalSinceLastAdvert: TimeInterval { get {
Date().timeIntervalSince(lastAdvertAt)
}}
/// Time interval between last connection request, this is used to priortise disconnections
var timeIntervalSinceLastConnectRequestedAt: TimeInterval { get {
Date().timeIntervalSince(lastConnectRequestedAt)
}}
/// Time interval between last connected at and last advert, this is used to estimate last period of continuous tracking, to priortise disconnections
var timeIntervalSinceLastDisconnectedAt: TimeInterval { get {
guard let lastDisconnectedAt = lastDisconnectedAt else {
return Date().timeIntervalSince(createdAt)
}
return Date().timeIntervalSince(lastDisconnectedAt)
}}
/// Time interval between last connected at and last advert, this is used to estimate last period of continuous tracking, to priortise disconnections
var timeIntervalBetweenLastConnectedAndLastAdvert: TimeInterval { get {
guard let lastConnectedAt = lastConnectedAt, lastAdvertAt > lastConnectedAt else {
return TimeInterval(0)
}
return lastAdvertAt.timeIntervalSince(lastConnectedAt)
}}
public override var description: String { get {
return "BLEDevice[id=\(identifier),os=\(operatingSystem.rawValue),payload=\(payloadData?.shortName ?? "nil"),address=\(pseudoDeviceAddress?.data.base64EncodedString() ?? "nil")]"
}}
init(_ identifier: TargetIdentifier, delegate: BLEDeviceDelegate) {
self.createdAt = Date()
self.identifier = identifier
self.delegate = delegate
lastUpdatedAt = createdAt
}
}
protocol BLEDeviceDelegate {
func device(_ device: BLEDevice, didUpdate attribute: BLEDeviceAttribute)
}
enum BLEDeviceAttribute : String {
case peripheral, signalCharacteristic, payloadCharacteristic, payloadSharingCharacteristic, operatingSystem, payloadData, rssi, txPower
}
enum BLEDeviceOperatingSystem : String {
case android, ios, restored, unknown, shared
}
/// RSSI in dBm.
typealias BLE_RSSI = Int
typealias BLE_TxPower = Int
class BLEPseudoDeviceAddress {
let address: Int
let data: Data
var description: String { get {
return "BLEPseudoDeviceAddress(address=\(address),data=\(data.base64EncodedString()))"
}}
init?(fromAdvertisementData: [String: Any]) {
guard let manufacturerData = fromAdvertisementData["kCBAdvDataManufacturerData"] as? Data else {
return nil
}
guard let manufacturerId = manufacturerData.uint16(0), manufacturerId == BLESensorConfiguration.manufacturerIdForSensor else {
return nil
}
guard manufacturerData.count == 8 else {
return nil
}
data = Data(manufacturerData.subdata(in: 2..<8))
var longValueData = Data(repeating: 0, count: 2)
longValueData.append(data)
guard let longValue = longValueData.int64(0) else {
return nil
}
address = Int(longValue)
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,213 @@
//
// BLESensor.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
import CoreBluetooth
protocol BLESensor : Sensor {
}
/// Defines BLE sensor configuration data, e.g. service and characteristic UUIDs
struct BLESensorConfiguration {
#if DEBUG
static let logLevel: SensorLoggerLevel = .debug;
#else
static let logLevel: SensorLoggerLevel = .fault;
#endif
/**
Service UUID for beacon service. This is a fixed UUID to enable iOS devices to find each other even
in background mode. Android devices will need to find Apple devices first using the manufacturer code
then discover services to identify actual beacons.
*/
static let serviceUUID = BluetraceConfig.BluetoothServiceID
///Signaling characteristic for controlling connection between peripheral and central, e.g. keep each other from suspend state
///- Characteristic UUID is randomly generated V4 UUIDs that has been tested for uniqueness by conducting web searches to ensure it returns no results.
public static var androidSignalCharacteristicUUID = CBUUID(string: "f617b813-092e-437a-8324-e09a80821a11")
///Signaling characteristic for controlling connection between peripheral and central, e.g. keep each other from suspend state
///- Characteristic UUID is randomly generated V4 UUIDs that has been tested for uniqueness by conducting web searches to ensure it returns no results.
public static var iosSignalCharacteristicUUID = CBUUID(string: "0eb0d5f2-eae4-4a9a-8af3-a4adb02d4363")
///Primary payload characteristic (read) for distributing payload data from peripheral to central, e.g. identity data
///- Characteristic UUID is randomly generated V4 UUIDs that has been tested for uniqueness by conducting web searches to ensure it returns no results.
public static var payloadCharacteristicUUID = CBUUID(string: "3e98c0f8-8f05-4829-a121-43e38f8933e7")
static let legacyCovidsafePayloadCharacteristicUUID = BluetraceConfig.BluetoothServiceID
/// Time delay between notifications for subscribers.
static let notificationDelay = DispatchTimeInterval.seconds(8)
/// Time delay between advert restart
static let advertRestartTimeInterval = TimeInterval.hour
/// Herald internal connection expiry timeout
static let connectionAttemptTimeout = TimeInterval(12)
/// Expiry time for shared payloads, to ensure only recently seen payloads are shared
/// Must be > payloadSharingTimeInterval to share pending payloads
static let payloadSharingExpiryTimeInterval = TimeInterval.minute * 5
/// Maximum number of concurrent BLE connections
static let concurrentConnectionQuota = 12
/// Manufacturer data is being used on Android to store pseudo device address
static let manufacturerIdForSensor = UInt16(65530);
/// Advert refresh time interval on Android devices
static let androidAdvertRefreshTimeInterval = TimeInterval.minute * 15;
// Filter duplicate payload data and suppress sensor(didRead:fromTarget) delegate calls
/// - Set to .never to disable this feature
/// - Set time interval N to filter duplicate payload data seen in last N seconds
/// - Example : 60 means filter duplicates in last minute
/// - Filters all occurrences of payload data from all targets
public static var filterDuplicatePayloadData = TimeInterval(30 * 60)
/// Signal characteristic action code for write payload, expect 1 byte action code followed by 2 byte little-endian Int16 integer value for payload data length, then payload data
static let signalCharacteristicActionWritePayload = UInt8(1)
/// Signal characteristic action code for write RSSI, expect 1 byte action code followed by 4 byte little-endian Int32 integer value for RSSI value
static let signalCharacteristicActionWriteRSSI = UInt8(2)
/// Signal characteristic action code for write payload, expect 1 byte action code followed by 2 byte little-endian Int16 integer value for payload sharing data length, then payload sharing data
static let signalCharacteristicActionWritePayloadSharing = UInt8(3)
/// Are Location Permissions enabled in the app, and thus awake on screen on enabled
public static var awakeOnLocationEnabled: Bool = true
}
/**
BLE sensor based on CoreBluetooth
Requires : Signing & Capabilities : BackgroundModes : Uses Bluetooth LE accessories = YES
Requires : Signing & Capabilities : BackgroundModes : Acts as a Bluetooth LE accessory = YES
Requires : Info.plist : Privacy - Bluetooth Always Usage Description
Requires : Info.plist : Privacy - Bluetooth Peripheral Usage Description
*/
class ConcreteBLESensor : NSObject, BLESensor, BLEDatabaseDelegate {
private let logger = ConcreteSensorLogger(subsystem: "Sensor", category: "BLE.ConcreteBLESensor")
private let sensorQueue = DispatchQueue(label: "Sensor.BLE.ConcreteBLESensor.SensorQueue")
private let delegateQueue = DispatchQueue(label: "Sensor.BLE.ConcreteBLESensor.DelegateQueue")
private var delegates: [SensorDelegate] = []
private let database: BLEDatabase
private let transmitter: BLETransmitter
private let receiver: BLEReceiver
// Record payload data to enable de-duplication
private var didReadPayloadData: [PayloadData:Date] = [:]
init(_ payloadDataSupplier: PayloadDataSupplier) {
database = ConcreteBLEDatabase()
transmitter = ConcreteBLETransmitter(queue: sensorQueue, delegateQueue: delegateQueue, database: database, payloadDataSupplier: payloadDataSupplier)
receiver = ConcreteBLEReceiver(queue: sensorQueue,delegateQueue: delegateQueue, database: database, payloadDataSupplier: payloadDataSupplier)
super.init()
database.add(delegate: self)
}
func start() {
logger.debug("start")
var permissionRequested = false
if #available(iOS 13.1, *) {
permissionRequested = (CBManager.authorization != .notDetermined)
} else {
permissionRequested = CBPeripheralManager.authorizationStatus() != .notDetermined
}
if let receiver = receiver as? ConcreteBLEReceiver, !permissionRequested {
// BLE receivers start on powerOn event, on status change the transmitter will be started.
// This is to request permissions and turn on dialogs sequentially when registering
receiver.addConnectionDelegate(delegate: self)
}
receiver.start()
// if permissions have been requested start transmitter immediately
if permissionRequested {
transmitter.start()
}
}
func stop() {
logger.debug("stop")
transmitter.stop()
receiver.stop()
// BLE transmitter and receivers stops on powerOff event
}
func add(delegate: SensorDelegate) {
delegates.append(delegate)
transmitter.add(delegate: delegate)
receiver.add(delegate: delegate)
}
// MARK:- BLEDatabaseDelegate
func bleDatabase(didCreate device: BLEDevice) {
logger.debug("didDetect (device=\(device.identifier),payloadData=\(device.payloadData?.shortName ?? "nil"))")
delegateQueue.async {
self.delegates.forEach { $0.sensor(.BLE, didDetect: device.identifier) }
}
}
func bleDatabase(didUpdate device: BLEDevice, attribute: BLEDeviceAttribute) {
switch attribute {
case .rssi:
guard let rssi = device.rssi else {
return
}
let proximity = Proximity(unit: .RSSI, value: Double(rssi))
logger.debug("didMeasure (device=\(device.identifier),payloadData=\(device.payloadData?.shortName ?? "nil"),proximity=\(proximity.description))")
delegateQueue.async {
self.delegates.forEach { $0.sensor(.BLE, didMeasure: proximity, fromTarget: device.identifier) }
}
guard let payloadData = device.payloadData else {
return
}
delegateQueue.async {
self.delegates.forEach { $0.sensor(.BLE, didMeasure: proximity, fromTarget: device.identifier, withPayload: payloadData, forDevice: device) }
}
case .payloadData:
guard let payloadData = device.payloadData else {
return
}
guard device.lastReadPayloadRequestedAt != Date.distantPast else {
logger.debug("didRead payload. lastReadPayloadRequestedAt is not set and payload has been updated. This is an android data share/copy and is ignored.")
return
}
logger.debug("didRead (device=\(device.identifier),payloadData=\(payloadData.shortName))")
guard let rssi = device.rssi else {
logger.debug("didRead rssi is nil, not proceeding")
return
}
// De-duplicate payload in recent time
if BLESensorConfiguration.filterDuplicatePayloadData != .never {
let removePayloadDataBefore = Date() - BLESensorConfiguration.filterDuplicatePayloadData
let recentDidReadPayloadData = didReadPayloadData.filter({ $0.value >= removePayloadDataBefore })
didReadPayloadData = recentDidReadPayloadData
if let lastReportedAt = didReadPayloadData[payloadData] {
logger.debug("didRead, filtered duplicate (device=\(device.identifier),payloadData=\(payloadData.shortName),lastReportedAt=\(lastReportedAt.description))")
return
}
didReadPayloadData[payloadData] = Date()
}
let proximity = Proximity(unit: .RSSI, value: Double(rssi))
delegateQueue.async {
self.delegates.forEach { $0.sensor(.BLE, didRead: payloadData, fromTarget: device.identifier, atProximity: proximity, withTxPower: device.txPower) }
}
default:
return
}
}
}
extension ConcreteBLESensor: SensorDelegate {
func sensor(_ sensor: SensorType, didUpdateState: SensorState) {
guard let receiver = receiver as? ConcreteBLEReceiver else {
return
}
receiver.removeConnectionDelegate()
transmitter.start()
}
}
extension TargetIdentifier {
init(peripheral: CBPeripheral) {
self.init(peripheral.identifier.uuidString)
}
init(central: CBCentral) {
self.init(central.identifier.uuidString)
}
}

View file

@ -0,0 +1,553 @@
//
// BLETransmitter.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
import CoreBluetooth
/**
Beacon transmitter broadcasts a fixed service UUID to enable background scan by iOS. When iOS
enters background mode, the UUID will disappear from the broadcast, so Android devices need to
search for Apple devices and then connect and discover services to read the UUID.
*/
protocol BLETransmitter : Sensor {
}
/**
Transmitter offers two services:
1. Signal characteristic for maintaining connection between iOS devices and also enable non-transmitting Android devices (receive only,
like the Samsung J6) to make their presence known by writing their beacon code and RSSI as data to this characteristic.
2. Payload characteristic for publishing beacon identity data.
Keeping the transmitter and receiver working in iOS background mode is a major challenge, in particular when both
iOS devices are in background mode. The transmitter on iOS offers a notifying beacon characteristic that is triggered
by writing anything to the characteristic. On characteristic write, the transmitter will call updateValue after 8 seconds
to notify the receivers, to wake up the receivers with a didUpdateValueFor call. The process can repeat as a loop
between the transmitter and receiver to keep both devices awake. This is unnecessary for Android-Android and also
Android-iOS and iOS-Android detection, which can rely solely on scanForPeripherals for detection.
The notification based wake up method relies on an open connection which seems to be fine for iOS but may cause
problems for Android. Experiments have found that Android devices cannot accept new connections (without explicit
disconnect) indefinitely and the bluetooth stack ceases to function after around 500 open connections. The device
will need to be rebooted to recover. However, if each connection is disconnected, the bluetooth stack can work
indefinitely, but frequent connect and disconnect can still cause the same problem. The recommendation is to
(1) always disconnect from Android as soon as the work is complete, (2) minimise the number of connections to
an Android device, and (3) maximise time interval between connections. With all these in mind, the transmitter
on Android does not support notify and also a connect is only performed on first contact to get the bacon code.
*/
class ConcreteBLETransmitter : NSObject, BLETransmitter, CBPeripheralManagerDelegate {
private let logger = ConcreteSensorLogger(subsystem: "Sensor", category: "BLE.ConcreteBLETransmitter")
private var delegates: [SensorDelegate] = []
/// Dedicated sequential queue for all beacon transmitter and receiver tasks.
private let queue: DispatchQueue
private let delegateQueue: DispatchQueue
private let database: BLEDatabase
/// Beacon code generator for creating cryptographically secure public codes that can be later used for on-device matching.
private let payloadDataSupplier: PayloadDataSupplier
/// Peripheral manager for managing all connections, using a single manager for simplicity.
private var peripheral: CBPeripheralManager!
/// Beacon service and characteristics being broadcasted by the transmitter.
private var signalCharacteristic: CBMutableCharacteristic?
private var payloadCharacteristic: CBMutableCharacteristic?
private var legacyCovidPayloadCharacteristic: CBMutableCharacteristic?
private var advertisingStartedAt: Date = Date.distantPast
/// Dummy data for writing to the receivers to trigger state restoration or resume from suspend state to background state.
private let emptyData = Data(repeating: 0, count: 0)
/**
Shifting timer for triggering notify for subscribers several seconds after resume from suspend state to background state,
but before re-entering suspend state. The time limit is under 10 seconds as desribed in Apple documentation.
*/
private var notifyTimer: DispatchSourceTimer?
/// Dedicated sequential queue for the shifting timer.
private let notifyTimerQueue = DispatchQueue(label: "Sensor.BLE.ConcreteBLETransmitter.Timer")
/**
Create a transmitter that uses the same sequential dispatch queue as the receiver.
Transmitter starts automatically when Bluetooth is enabled.
*/
init(queue: DispatchQueue, delegateQueue: DispatchQueue, database: BLEDatabase, payloadDataSupplier: PayloadDataSupplier) {
self.queue = queue
self.delegateQueue = delegateQueue
self.database = database
self.payloadDataSupplier = payloadDataSupplier
super.init()
}
func add(delegate: SensorDelegate) {
delegates.append(delegate)
}
func start() {
logger.debug("start")
// Create a peripheral that supports state restoration
if peripheral == nil {
self.peripheral = CBPeripheralManager(delegate: self, queue: queue, options: [
CBPeripheralManagerOptionRestoreIdentifierKey : "Sensor.BLE.ConcreteBLETransmitter",
CBPeripheralManagerOptionShowPowerAlertKey : true
])
}
guard peripheral.state == .poweredOn else {
logger.fault("start denied, not powered on")
return
}
if signalCharacteristic != nil, payloadCharacteristic != nil, legacyCovidPayloadCharacteristic != nil {
logger.debug("starting advert with existing characteristics")
if !peripheral.isAdvertising {
startAdvertising(withNewCharacteristics: false)
} else {
queue.async {
self.peripheral.stopAdvertising()
self.peripheral.startAdvertising([CBAdvertisementDataServiceUUIDsKey : [BLESensorConfiguration.serviceUUID]])
}
}
logger.debug("start successful, for existing characteristics")
} else {
startAdvertising(withNewCharacteristics: true)
logger.debug("start successful, for new characteristics")
}
signalCharacteristic?.subscribedCentrals?.forEach() { central in
// FEATURE : Symmetric connection on subscribe
_ = database.device(central.identifier.uuidString)
}
notifySubscribers("start")
}
func stop() {
logger.debug("stop")
guard peripheral != nil else {
return
}
guard peripheral.isAdvertising else {
logger.fault("stop denied, already stopped (source=%s)")
self.peripheral = nil
return
}
stopAdvertising()
}
private func startAdvertising(withNewCharacteristics: Bool) {
logger.debug("startAdvertising (withNewCharacteristics=\(withNewCharacteristics))")
if withNewCharacteristics || signalCharacteristic == nil || payloadCharacteristic == nil || legacyCovidPayloadCharacteristic == nil {
signalCharacteristic = CBMutableCharacteristic(type: BLESensorConfiguration.iosSignalCharacteristicUUID, properties: [.write, .notify], value: nil, permissions: [.writeable])
payloadCharacteristic = CBMutableCharacteristic(type: BLESensorConfiguration.payloadCharacteristicUUID, properties: [.read], value: nil, permissions: [.readable])
legacyCovidPayloadCharacteristic = CBMutableCharacteristic(type: BluetraceConfig.BluetoothServiceID, properties: [.read, .write, .writeWithoutResponse], value: nil, permissions: [.readable, .writeable])
}
let service = CBMutableService(type: BLESensorConfiguration.serviceUUID, primary: true)
signalCharacteristic?.value = nil
payloadCharacteristic?.value = nil
legacyCovidPayloadCharacteristic?.value = nil
service.characteristics = [signalCharacteristic!, payloadCharacteristic!, legacyCovidPayloadCharacteristic!]
queue.async {
self.peripheral.stopAdvertising()
self.peripheral.removeAllServices()
self.peripheral.add(service)
self.peripheral.startAdvertising([CBAdvertisementDataServiceUUIDsKey : [BLESensorConfiguration.serviceUUID]])
}
}
private func stopAdvertising() {
logger.debug("stopAdvertising()")
queue.async {
self.peripheral.stopAdvertising()
self.peripheral = nil
}
notifyTimer?.cancel()
notifyTimer = nil
}
/// All work starts from notify subscribers loop.
/// Generate updateValue notification after 8 seconds to notify all subscribers and keep the iOS receivers awake.
private func notifySubscribers(_ source: String) {
notifyTimer?.cancel()
notifyTimer = DispatchSource.makeTimerSource(queue: notifyTimerQueue)
notifyTimer?.schedule(deadline: DispatchTime.now() + BLESensorConfiguration.notificationDelay)
notifyTimer?.setEventHandler { [weak self] in
guard let s = self, let logger = self?.logger, let signalCharacteristic = self?.signalCharacteristic else {
return
}
// Notify subscribers to keep them awake
s.queue.async {
logger.debug("notifySubscribers (source=\(source))")
s.peripheral.updateValue(s.emptyData, for: signalCharacteristic, onSubscribedCentrals: nil)
}
// Restart advert if required
let advertUpTime = Date().timeIntervalSince(s.advertisingStartedAt)
if s.peripheral.isAdvertising, advertUpTime > BLESensorConfiguration.advertRestartTimeInterval {
logger.debug("advertRestart (upTime=\(advertUpTime))")
s.startAdvertising(withNewCharacteristics: true)
}
}
notifyTimer?.resume()
}
// MARK:- CBPeripheralManagerDelegate
/// Restore advert and reinstate advertised characteristics.
func peripheralManager(_ peripheral: CBPeripheralManager, willRestoreState dict: [String : Any]) {
logger.debug("willRestoreState")
self.peripheral = peripheral
peripheral.delegate = self
if let services = dict[CBPeripheralManagerRestoredStateServicesKey] as? [CBMutableService] {
for service in services {
logger.debug("willRestoreState (service=\(service.uuid.uuidString))")
if let characteristics = service.characteristics {
for characteristic in characteristics {
logger.debug("willRestoreState (characteristic=\(characteristic.uuid.uuidString))")
switch characteristic.uuid {
case BLESensorConfiguration.androidSignalCharacteristicUUID:
if let mutableCharacteristic = characteristic as? CBMutableCharacteristic {
signalCharacteristic = mutableCharacteristic
logger.debug("willRestoreState (androidSignalCharacteristic=\(characteristic.uuid.uuidString))")
} else {
logger.fault("willRestoreState characteristic not mutable (androidSignalCharacteristic=\(characteristic.uuid.uuidString))")
}
case BLESensorConfiguration.iosSignalCharacteristicUUID:
if let mutableCharacteristic = characteristic as? CBMutableCharacteristic {
signalCharacteristic = mutableCharacteristic
logger.debug("willRestoreState (iosSignalCharacteristic=\(characteristic.uuid.uuidString))")
} else {
logger.fault("willRestoreState characteristic not mutable (iosSignalCharacteristic=\(characteristic.uuid.uuidString))")
}
case BLESensorConfiguration.payloadCharacteristicUUID:
if let mutableCharacteristic = characteristic as? CBMutableCharacteristic {
payloadCharacteristic = mutableCharacteristic
logger.debug("willRestoreState (payloadCharacteristic=\(characteristic.uuid.uuidString))")
} else {
logger.fault("willRestoreState characteristic not mutable (payloadCharacteristic=\(characteristic.uuid.uuidString))")
}
default:
logger.debug("willRestoreState (unknownCharacteristic=\(characteristic.uuid.uuidString))")
}
}
}
}
}
}
/// Start advertising on bluetooth power on.
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
// Bluetooth on -> Advertise
if (peripheral.state == .poweredOn) {
logger.debug("Update state (state=poweredOn)")
start()
} else {
if #available(iOS 10.0, *) {
logger.debug("Update state (state=\(peripheral.state.description))")
} else {
// Required to support iOS 9.3
switch peripheral.state {
case .poweredOff:
logger.debug("Update state (state=poweredOff)")
case .poweredOn:
logger.debug("Update state (state=poweredOn)")
case .resetting:
logger.debug("Update state (state=resetting)")
case .unauthorized:
logger.debug("Update state (state=unauthorized)")
case .unknown:
logger.debug("Update state (state=unknown)")
case .unsupported:
logger.debug("Update state (state=unsupported)")
default:
logger.debug("Update state (state=undefined)")
}
}
}
}
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
logger.debug("peripheralManagerDidStartAdvertising (error=\(String(describing: error)))")
if error == nil {
advertisingStartedAt = Date()
}
}
/**
Write request offers a mechanism for non-transmitting BLE devices (e.g. Samsung J6 can only receive) to make
its presence known by submitting its beacon code and RSSI as data. This also offers a mechanism for iOS to
write blank data to transmitter to keep bringing it back from suspended state to background state which increases
its chance of background scanning over a long period without being killed off. Payload sharing is also based on
write characteristic to enable Android peers to act as a bridge for sharing iOS device payloads, thus enabling
iOS - iOS background detection without location permission or screen on, as background detection and tracking method.
*/
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
// Write -> Notify delegates -> Write response -> Notify subscribers
for request in requests {
let targetIdentifier = TargetIdentifier(central: request.central)
// FEATURE : Symmetric connection on write
let targetDevice = database.device(targetIdentifier)
logger.debug("didReceiveWrite (central=\(targetIdentifier))")
if let data = request.value {
guard request.characteristic.uuid != legacyCovidPayloadCharacteristic?.uuid else {
logger.debug("didReceiveWrite (central=\(targetIdentifier),action=writeLegacyCovidPayload)")
// we don't do anything with the payload.
// Herald relies only on reads. Therefore when legacy writes we ignore.
// However, to maintain legacy data as expected, payload is still written after read.
// See BLEReceiver writeLegacyPayload
queue.async { peripheral.respond(to: request, withResult: .success) }
continue
}
if data.count == 0 {
// Receiver writes blank data on detection of transmitter to bring iOS transmitter back from suspended state
logger.debug("didReceiveWrite (central=\(targetIdentifier),action=wakeTransmitter)")
queue.async { peripheral.respond(to: request, withResult: .success) }
} else if let actionCode = data.uint8(0) {
switch actionCode {
case BLESensorConfiguration.signalCharacteristicActionWritePayload:
// Receive-only Android device writing its payload to make its presence known
logger.debug("didReceiveWrite (central=\(targetIdentifier),action=writePayload)")
// writePayload data format
// 0-0 : actionCode
// 1-2 : payload data count in bytes (Int16)
// 3.. : payload data
if let payloadDataCount = data.int16(1) {
logger.debug("didReceiveWrite -> didDetect=\(targetIdentifier)")
delegateQueue.async {
self.delegates.forEach { $0.sensor(.BLE, didDetect: targetIdentifier) }
}
if data.count == (3 + payloadDataCount) {
let payloadData = PayloadData(data.subdata(in: 3..<data.count))
logger.debug("didReceiveWrite -> didRead=\(payloadData.shortName),fromTarget=\(targetIdentifier)")
targetDevice.operatingSystem = .android
targetDevice.receiveOnly = true
targetDevice.payloadData = payloadData
queue.async { peripheral.respond(to: request, withResult: .success) }
} else {
logger.fault("didReceiveWrite, invalid payload (central=\(targetIdentifier),action=writePayload)")
queue.async { peripheral.respond(to: request, withResult: .invalidAttributeValueLength) }
}
} else {
logger.fault("didReceiveWrite, invalid request (central=\(targetIdentifier),action=writePayload)")
queue.async { peripheral.respond(to: request, withResult: .invalidAttributeValueLength) }
}
case BLESensorConfiguration.signalCharacteristicActionWriteRSSI:
// Receive-only Android device writing its RSSI to make its proximity known
logger.debug("didReceiveWrite (central=\(targetIdentifier),action=writeRSSI)")
// writeRSSI data format
// 0-0 : actionCode
// 1-2 : rssi value (Int16)
if let rssi = data.int16(1) {
let proximity = Proximity(unit: .RSSI, value: Double(rssi))
logger.debug("didReceiveWrite -> didMeasure=\(proximity.description),fromTarget=\(targetIdentifier)")
targetDevice.operatingSystem = .android
targetDevice.receiveOnly = true
targetDevice.rssi = BLE_RSSI(rssi)
queue.async { peripheral.respond(to: request, withResult: .success) }
} else {
logger.fault("didReceiveWrite, invalid request (central=\(targetIdentifier),action=writeRSSI)")
queue.async { peripheral.respond(to: request, withResult: .invalidAttributeValueLength) }
}
case BLESensorConfiguration.signalCharacteristicActionWritePayloadSharing:
// Android device sharing detected iOS devices with this iOS device to enable background detection
logger.debug("didReceiveWrite (central=\(targetIdentifier),action=writePayloadSharing)")
// writePayloadSharing data format
// 0-0 : actionCode
// 1-2 : rssi value (Int16)
// 3-4 : payload sharing data count in bytes (Int16)
// 5.. : payload sharing data (to be parsed by payload data supplier)
if let rssi = data.int16(1), let payloadDataCount = data.int16(3) {
// skip if a payload with length 0 is sent
if data.count == (5 + payloadDataCount) && payloadDataCount > 0 {
let payloadSharingData = payloadDataSupplier.payload(data.subdata(in: 5..<data.count))
logger.debug("didReceiveWrite -> didShare=\(payloadSharingData.description),fromTarget=\(targetIdentifier)")
let proximity = Proximity(unit: .RSSI, value: Double(rssi))
var filteredPayloadSharingData: [PayloadData]
if let cachedPayload = EncounterMessageManager.shared.getLastKnownAdvertisementPayload(identifier: request.central.identifier) {
// check that the shared data is not the data sent to devices we are receiving from and it does not exist already
filteredPayloadSharingData = payloadSharingData.filter({ (dataToCheck) -> Bool in
return dataToCheck != cachedPayload && !self.database.hasDevice(dataToCheck)
})
} else {
// check that it does not exist already
filteredPayloadSharingData = payloadSharingData.filter({ (dataToCheck) -> Bool in
return !self.database.hasDevice(dataToCheck)
})
}
self.logger.debug("didReceiveWrite -> filtered didShare=\(filteredPayloadSharingData.description),fromTarget=\(targetIdentifier)")
queue.async { peripheral.respond(to: request, withResult: .success) }
targetDevice.operatingSystem = .android
targetDevice.rssi = BLE_RSSI(rssi)
filteredPayloadSharingData.forEach() { payloadData in
logger.debug("didReceiveWrite, storing device with shared payload=\(payloadData.shortName)")
let sharedDevice = self.database.device(payloadData)
if sharedDevice.operatingSystem == .unknown {
sharedDevice.operatingSystem = .shared
}
sharedDevice.rssi = BLE_RSSI(rssi)
self.delegateQueue.async {
self.delegates.forEach {
$0.sensor(.BLE, didShare: filteredPayloadSharingData, fromTarget: sharedDevice.identifier, atProximity: proximity)
}
}
}
} else {
logger.fault("didReceiveWrite, invalid payload (central=\(targetIdentifier),action=writePayloadSharing)")
queue.async { peripheral.respond(to: request, withResult: .invalidAttributeValueLength) }
}
} else {
logger.fault("didReceiveWrite, invalid request (central=\(targetIdentifier),action=writePayloadSharing)")
queue.async { peripheral.respond(to: request, withResult: .invalidAttributeValueLength) }
}
default:
logger.fault("didReceiveWrite (central=\(targetIdentifier),action=unknown,actionCode=\(actionCode))")
queue.async { peripheral.respond(to: request, withResult: .invalidAttributeValueLength) }
}
}
} else {
queue.async { peripheral.respond(to: request, withResult: .invalidAttributeValueLength) }
}
}
notifySubscribers("didReceiveWrite")
}
/// Read request from central for obtaining payload data from this peripheral.
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
// Read -> Notify subscribers
let central = database.device(TargetIdentifier(request.central.identifier.uuidString))
switch request.characteristic.uuid {
case BLESensorConfiguration.payloadCharacteristicUUID, BLESensorConfiguration.legacyCovidsafePayloadCharacteristicUUID:
logger.debug("Read received (central=\(central.description),characteristic=payload,offset=\(request.offset))")
payloadDataSupplier.payload(request.central.identifier,
offset: request.offset) { [self] (payloadData) in
queue.async {
guard let data = payloadData else {
peripheral.respond(to: request, withResult: .unlikelyError)
return
}
logger.debug("Read received (central=\(central.description),characteristic=\(request.characteristic.uuid),payload=\(data.shortName))")
guard request.offset < data.count else {
logger.fault("Read, invalid offset (central=\(central.description),characteristic=payload,offset=\(request.offset),data=\(data.count))")
peripheral.respond(to: request, withResult: .invalidOffset)
return
}
guard request.offset != data.count else {
// the receiver already read all the data in its last read request
peripheral.respond(to: request, withResult: .success)
return
}
request.value = (request.offset == 0 ? data : data.subdata(in: request.offset..<data.count))
peripheral.respond(to: request, withResult: .success)
}
}
default:
logger.fault("Read (central=\(central.description),characteristic=unknown)")
queue.async { peripheral.respond(to: request, withResult: .requestNotSupported) }
}
notifySubscribers("didReceiveRead")
}
/// Another iOS central has subscribed to this iOS peripheral, implying the central is also a peripheral for this device to connect to.
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
// Subscribe -> Notify subscribers
// iOS receiver subscribes to the signal characteristic on first contact. This ensures the first call keeps
// the transmitter and receiver awake. Future loops will rely on didReceiveWrite as the trigger.
logger.debug("Subscribe (central=\(central.identifier.uuidString))")
// FEATURE : Symmetric connection on subscribe
_ = database.device(central.identifier.uuidString)
notifySubscribers("didSubscribeTo")
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
// Unsubscribe -> Notify subscribers
logger.debug("Unsubscribe (central=\(central.identifier.uuidString))")
// FEATURE : Symmetric connection on unsubscribe
_ = database.device(central.identifier.uuidString)
notifySubscribers("didUnsubscribeFrom")
}
}
extension Data {
/// Get Int8 from byte array (little-endian).
func int8(_ index: Int) -> Int8? {
guard let value = uint8(index) else {
return nil
}
return Int8(bitPattern: value)
}
/// Get UInt8 from byte array (little-endian).
func uint8(_ index: Int) -> UInt8? {
let bytes = [UInt8](self)
guard index < bytes.count else {
return nil
}
return bytes[index]
}
/// Get Int16 from byte array (little-endian).
func int16(_ index: Int) -> Int16? {
guard let value = uint16(index) else {
return nil
}
return Int16(bitPattern: value)
}
/// Get UInt16 from byte array (little-endian).
func uint16(_ index: Int) -> UInt16? {
let bytes = [UInt8](self)
guard index < (bytes.count - 1) else {
return nil
}
return UInt16(bytes[index]) |
UInt16(bytes[index + 1]) << 8
}
/// Get Int32 from byte array (little-endian).
func int32(_ index: Int) -> Int32? {
guard let value = uint32(index) else {
return nil
}
return Int32(bitPattern: value)
}
/// Get UInt32 from byte array (little-endian).
func uint32(_ index: Int) -> UInt32? {
let bytes = [UInt8](self)
guard index < (bytes.count - 3) else {
return nil
}
return UInt32(bytes[index]) |
UInt32(bytes[index + 1]) << 8 |
UInt32(bytes[index + 2]) << 16 |
UInt32(bytes[index + 3]) << 24
}
/// Get Int64 from byte array (little-endian).
func int64(_ index: Int) -> Int64? {
guard let value = uint64(index) else {
return nil
}
return Int64(bitPattern: value)
}
/// Get UInt64 from byte array (little-endian).
func uint64(_ index: Int) -> UInt64? {
let bytes = [UInt8](self)
guard index < (bytes.count - 7) else {
return nil
}
return UInt64(bytes[index]) |
UInt64(bytes[index + 1]) << 8 |
UInt64(bytes[index + 2]) << 16 |
UInt64(bytes[index + 3]) << 24 |
UInt64(bytes[index + 4]) << 32 |
UInt64(bytes[index + 5]) << 40 |
UInt64(bytes[index + 6]) << 48 |
UInt64(bytes[index + 7]) << 56
}
}

View file

@ -0,0 +1,195 @@
//
// BLEUtilities.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
import CoreBluetooth
/**
Extension to make the state human readable in logs.
*/
@available(iOS 10.0, *)
extension CBManagerState: CustomStringConvertible {
/**
Get plain text description of state.
*/
public var description: String {
switch self {
case .poweredOff: return ".poweredOff"
case .poweredOn: return ".poweredOn"
case .resetting: return ".resetting"
case .unauthorized: return ".unauthorized"
case .unknown: return ".unknown"
case .unsupported: return ".unsupported"
@unknown default: return "undefined"
}
}
}
extension CBPeripheralManagerState : CustomStringConvertible {
/**
Get plain text description of state.
*/
public var description: String {
switch self {
case .poweredOff: return ".poweredOff"
case .poweredOn: return ".poweredOn"
case .resetting: return ".resetting"
case .unauthorized: return ".unauthorized"
case .unknown: return ".unknown"
case .unsupported: return ".unsupported"
@unknown default: return "undefined"
}
}
}
extension CBCentralManagerState : CustomStringConvertible {
/**
Get plain text description of state.
*/
public var description: String {
switch self {
case .poweredOff: return ".poweredOff"
case .poweredOn: return ".poweredOn"
case .resetting: return ".resetting"
case .unauthorized: return ".unauthorized"
case .unknown: return ".unknown"
case .unsupported: return ".unsupported"
@unknown default: return "undefined"
}
}
}
/**
Extension to make the state human readable in logs.
*/
extension CBPeripheralState: CustomStringConvertible {
/**
Get plain text description fo state.
*/
public var description: String {
switch self {
case .connected: return ".connected"
case .connecting: return ".connecting"
case .disconnected: return ".disconnected"
case .disconnecting: return ".disconnecting"
@unknown default: return "undefined"
}
}
}
/**
Extension to make the time intervals more human readable in code.
*/
extension TimeInterval {
static var day: TimeInterval { get { TimeInterval(86400) } }
static var hour: TimeInterval { get { TimeInterval(3600) } }
static var minute: TimeInterval { get { TimeInterval(60) } }
static var never: TimeInterval { get { TimeInterval(Int.max) } }
}
/**
Sample statistics.
*/
class Sample {
private var n:Int64 = 0
private var m1:Double = 0.0
private var m2:Double = 0.0
private var m3:Double = 0.0
private var m4:Double = 0.0
/**
Minimum sample value.
*/
var min:Double? = nil
/**
Maximum sample value.
*/
var max:Double? = nil
/**
Sample size.
*/
var count:Int64 { get { n } }
/**
Mean sample value.
*/
var mean:Double? { get { n > 0 ? m1 : nil } }
/**
Sample variance.
*/
var variance:Double? { get { n > 1 ? m2 / Double(n - 1) : nil } }
/**
Sample standard deviation.
*/
var standardDeviation:Double? { get { n > 1 ? sqrt(m2 / Double(n - 1)) : nil } }
/**
String representation of mean, standard deviation, min and max
*/
var description: String { get {
let sCount = n.description
let sMean = (mean == nil ? "-" : mean!.description)
let sStandardDeviation = (standardDeviation == nil ? "-" : standardDeviation!.description)
let sMin = (min == nil ? "-" : min!.description)
let sMax = (max == nil ? "-" : max!.description)
return "count=" + sCount + ",mean=" + sMean + ",sd=" + sStandardDeviation + ",min=" + sMin + ",max=" + sMax
} }
/**
Add sample value.
*/
func add(_ x:Double) {
// Sample value accumulation algorithm avoids reiterating sample to compute variance.
let n1 = n
n += 1
let d = x - m1
let d_n = d / Double(n)
let d_n2 = d_n * d_n;
let t = d * d_n * Double(n1);
m1 += d_n;
m4 += t * d_n2 * Double(n * n - 3 * n + 3) + 6 * d_n2 * m2 - 4 * d_n * m3;
m3 += t * d_n * Double(n - 2) - 3 * d_n * m2;
m2 += t;
if min == nil || x < min! {
min = x;
}
if max == nil || x > max! {
max = x;
}
}
}
/**
Time interval samples for collecting elapsed time statistics.
*/
class TimeIntervalSample : Sample {
private var startTime: Date?
private var timestamp: Date?
var period: TimeInterval? { get {
(startTime == nil ? nil : timestamp?.timeIntervalSince(startTime!))
}}
override var description: String { get {
let sPeriod = (period == nil ? "-" : period!.description)
return super.description + ",period=" + sPeriod
}}
/**
Add elapsed time since last call to add() as sample.
*/
func add() {
guard timestamp != nil else {
timestamp = Date()
startTime = timestamp
return
}
let now = Date()
if let timestamp = timestamp {
add(now.timeIntervalSince(timestamp))
}
timestamp = now
}
}

View file

@ -0,0 +1,50 @@
//
// BatteryLog.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import UIKit
import NotificationCenter
import os
/// Battery log for monitoring battery level over time
class BatteryLog {
private let logger = ConcreteSensorLogger(subsystem: "Sensor", category: "BatteryLog")
private let textFile: TextFile
private let dateFormatter = DateFormatter()
private let updateInterval = TimeInterval(30)
init(filename: String) {
textFile = TextFile(filename: filename)
if textFile.empty() {
textFile.write("time,source,level")
}
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
UIDevice.current.isBatteryMonitoringEnabled = true
NotificationCenter.default.addObserver(self, selector: #selector(batteryLevelDidChange), name: UIDevice.batteryLevelDidChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(batteryStateDidChange), name: UIDevice.batteryStateDidChangeNotification, object: nil)
let _ = Timer.scheduledTimer(timeInterval: updateInterval, target: self, selector: #selector(update), userInfo: nil, repeats: true)
}
private func timestamp() -> String {
let timestamp = dateFormatter.string(from: Date())
return timestamp
}
@objc func update() {
let powerSource = (UIDevice.current.batteryState == .unplugged ? "battery" : "external")
let batteryLevel = Float(UIDevice.current.batteryLevel * 100).description
textFile.write(timestamp() + "," + powerSource + "," + batteryLevel)
logger.debug("update (powerSource=\(powerSource),batteryLevel=\(batteryLevel))");
}
@objc func batteryLevelDidChange(_ sender: NotificationCenter) {
update()
}
@objc func batteryStateDidChange(_ sender: NotificationCenter) {
update()
}
}

View file

@ -0,0 +1,57 @@
//
// ContactLog.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
/// CSV contact log for post event analysis and visualisation
class ContactLog: NSObject, SensorDelegate {
private let textFile: TextFile
private let dateFormatter = DateFormatter()
init(filename: String) {
textFile = TextFile(filename: filename)
if textFile.empty() {
textFile.write("time,sensor,id,detect,read,measure,share,visit,data")
}
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
}
private func timestamp() -> String {
let timestamp = dateFormatter.string(from: Date())
return timestamp
}
private func csv(_ value: String) -> String {
return TextFile.csv(value)
}
// MARK:- SensorDelegate
func sensor(_ sensor: SensorType, didDetect: TargetIdentifier) {
textFile.write(timestamp() + "," + sensor.rawValue + "," + csv(didDetect) + ",1,,,,,")
}
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier) {
textFile.write(timestamp() + "," + sensor.rawValue + "," + csv(fromTarget) + ",,2,,,," + csv(didRead.shortName))
}
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier, atProximity: Proximity, withTxPower: Int?) {
textFile.write(timestamp() + "," + sensor.rawValue + "," + csv(fromTarget) + ",,2,,,," + csv(didRead.shortName))
}
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier) {
textFile.write(timestamp() + "," + sensor.rawValue + "," + csv(fromTarget) + ",,,3,,," + csv(didMeasure.description))
}
func sensor(_ sensor: SensorType, didShare: [PayloadData], fromTarget: TargetIdentifier, atProximity: Proximity) {
let prefix = timestamp() + "," + sensor.rawValue + "," + csv(fromTarget)
didShare.forEach() { payloadData in
textFile.write(prefix + ",,,,4,," + csv(payloadData.shortName))
}
}
}

View file

@ -0,0 +1,88 @@
//
// DetectionLog.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
import UIKit
/// CSV contact log for post event analysis and visualisation
class DetectionLog: NSObject, SensorDelegate {
private let logger = ConcreteSensorLogger(subsystem: "Sensor", category: "Data.DetectionLog")
private let textFile: TextFile
private let payloadData: PayloadData
private let deviceName = UIDevice.current.name
private let deviceOS = UIDevice.current.systemVersion
private var payloads: Set<String> = []
private let queue = DispatchQueue(label: "Sensor.Data.DetectionLog.Queue")
init(filename: String, payloadData: PayloadData) {
textFile = TextFile(filename: filename)
self.payloadData = payloadData
super.init()
write()
}
private func csv(_ value: String) -> String {
return TextFile.csv(value)
}
private func write() {
var content = "\(csv(deviceName)),iOS,\(csv(deviceOS)),\(csv(payloadData.shortName))"
var payloadList: [String] = []
payloads.forEach() { payload in
guard payload != payloadData.shortName else {
return
}
payloadList.append(payload)
}
payloadList.sort()
payloadList.forEach() { payload in
content.append(",")
content.append(csv(payload))
}
logger.debug("write (content=\(content))")
content.append("\n")
textFile.overwrite(content)
}
// MARK:- SensorDelegate
func sensor(_ sensor: SensorType, didDetect: TargetIdentifier) {
}
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier) {
queue.async {
if self.payloads.insert(didRead.shortName).inserted {
self.logger.debug("didRead (payload=\(didRead.shortName))")
self.write()
}
}
}
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier, atProximity: Proximity, withTxPower: Int?) {
queue.async {
if self.payloads.insert(didRead.shortName).inserted {
self.logger.debug("didRead (payload=\(didRead.shortName))")
self.write()
}
}
}
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier) {
}
func sensor(_ sensor: SensorType, didShare: [PayloadData], fromTarget: TargetIdentifier, atProximity: Proximity) {
didShare.forEach() { payloadData in
queue.async {
if self.payloads.insert(payloadData.shortName).inserted {
self.logger.debug("didShare (payload=\(payloadData.shortName))")
self.write()
}
}
}
}
}

View file

@ -0,0 +1,96 @@
//
// Logger.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
import UIKit
import os
protocol SensorLogger {
init(subsystem: String, category: String)
func log(_ level: SensorLoggerLevel, _ message: String)
func debug(_ message: String)
func info(_ message: String)
func fault(_ message: String)
}
enum SensorLoggerLevel: String {
case debug, info, fault
}
class ConcreteSensorLogger: NSObject, SensorLogger {
private let subsystem: String
private let category: String
private let dateFormatter = DateFormatter()
private let log: OSLog?
private static let logFile = TextFile(filename: "log.txt")
required init(subsystem: String, category: String) {
self.subsystem = subsystem
self.category = category
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
if #available(iOS 10.0, *) {
log = OSLog(subsystem: subsystem, category: category)
} else {
log = nil
}
}
private func suppress(_ level: SensorLoggerLevel) -> Bool {
switch level {
case .debug:
return (BLESensorConfiguration.logLevel == .info || BLESensorConfiguration.logLevel == .fault);
case .info:
return (BLESensorConfiguration.logLevel == .fault);
default:
return false;
}
}
func log(_ level: SensorLoggerLevel, _ message: String) {
guard !suppress(level) else {
return
}
// Write to unified os log if available, else print to console
let timestamp = dateFormatter.string(from: Date())
let csvMessage = message.replacingOccurrences(of: "\"", with: "'")
let quotedMessage = (message.contains(",") ? "\"" + csvMessage + "\"" : csvMessage)
let entry = timestamp + "," + level.rawValue + "," + subsystem + "," + category + "," + quotedMessage
ConcreteSensorLogger.logFile.write(entry)
guard let log = log else {
print(entry)
return
}
if #available(iOS 10.0, *) {
switch (level) {
case .debug:
os_log("%s", log: log, type: .debug, message)
case .info:
os_log("%s", log: log, type: .info, message)
case .fault:
os_log("%s", log: log, type: .fault, message)
}
return
}
}
func debug(_ message: String) {
log(.debug, message)
}
func info(_ message: String) {
log(.debug, message)
}
func fault(_ message: String) {
log(.debug, message)
}
}

View file

@ -0,0 +1,95 @@
//
// StatisticsLog.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
/// CSV contact log for post event analysis and visualisation
class StatisticsLog: NSObject, SensorDelegate {
private let textFile: TextFile
private let payloadData: PayloadData
private var identifierToPayload: [TargetIdentifier:String] = [:]
private var payloadToTime: [String:Date] = [:]
private var payloadToSample: [String:Sample] = [:]
init(filename: String, payloadData: PayloadData) {
textFile = TextFile(filename: filename)
self.payloadData = payloadData
}
private func csv(_ value: String) -> String {
return TextFile.csv(value)
}
private func add(identifier: TargetIdentifier) {
guard let payload = identifierToPayload[identifier] else {
return
}
add(payload: payload)
}
private func add(payload: String) {
guard let time = payloadToTime[payload], let sample = payloadToSample[payload] else {
payloadToTime[payload] = Date()
payloadToSample[payload] = Sample()
return
}
let now = Date()
payloadToTime[payload] = now
sample.add(Double(now.timeIntervalSince(time)))
write()
}
private func write() {
var content = "payload,count,mean,sd,min,max\n"
var payloadList: [String] = []
payloadToSample.keys.forEach() { payload in
guard payload != payloadData.shortName else {
return
}
payloadList.append(payload)
}
payloadList.sort()
payloadList.forEach() { payload in
guard let sample = payloadToSample[payload] else {
return
}
guard let mean = sample.mean, let sd = sample.standardDeviation, let min = sample.min, let max = sample.max else {
return
}
content.append("\(csv(payload)),\(sample.count),\(mean),\(sd),\(min),\(max)\n")
}
textFile.overwrite(content)
}
// MARK:- SensorDelegate
func sensor(_ sensor: SensorType, didDetect: TargetIdentifier) {
}
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier) {
identifierToPayload[fromTarget] = didRead.shortName
add(identifier: fromTarget)
}
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier, atProximity: Proximity, withTxPower: Int?) {
identifierToPayload[fromTarget] = didRead.shortName
add(identifier: fromTarget)
}
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier) {
add(identifier: fromTarget)
}
func sensor(_ sensor: SensorType, didShare: [PayloadData], fromTarget: TargetIdentifier, atProximity: Proximity) {
didShare.forEach() { payloadData in
add(payload: payloadData.shortName)
}
}
}

View file

@ -0,0 +1,71 @@
//
// TextFile.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
class TextFile {
private let logger = ConcreteSensorLogger(subsystem: "Sensor", category: "Data.TextFile")
private var file: URL?
private let queue: DispatchQueue
init(filename: String) {
file = try? FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(filename)
queue = DispatchQueue(label: "Sensor.Data.TextFile(\(filename))")
}
func empty() -> Bool {
guard let file = file else {
return true
}
return !FileManager.default.fileExists(atPath: file.path)
}
/// Append line to new or existing file
func write(_ line: String) {
queue.sync {
guard let file = file else {
return
}
guard let data = (line+"\n").data(using: .utf8) else {
return
}
if FileManager.default.fileExists(atPath: file.path) {
if let fileHandle = try? FileHandle(forWritingTo: file) {
fileHandle.seekToEndOfFile()
fileHandle.write(data)
fileHandle.closeFile()
}
} else {
try? data.write(to: file, options: .atomicWrite)
}
}
}
/// Overwrite file content
func overwrite(_ content: String) {
queue.sync {
guard let file = file else {
return
}
guard let data = content.data(using: .utf8) else {
return
}
try? data.write(to: file, options: .atomicWrite)
}
}
/// Quote value for CSV output if required.
static func csv(_ value: String) -> String {
guard value.contains(",") || value.contains("\"") || value.contains("'") || value.contains("") else {
return value
}
return "\"" + value + "\""
}
}

View file

@ -0,0 +1,122 @@
//
// AwakeSensor.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
import CoreLocation
protocol AwakeSensor : Sensor {
}
/**
Screen awake sensor based on CoreLocation. Does NOT make use of the GPS position
Requires : Signing & Capabilities : BackgroundModes : LocationUpdates = YES
Requires : Info.plist : Privacy - Location When In Use Usage Description
Requires : Info.plist : Privacy - Location Always and When In Use Usage Description
*/
class ConcreteAwakeSensor : NSObject, AwakeSensor, CLLocationManagerDelegate {
private let logger = ConcreteSensorLogger(subsystem: "Sensor", category: "ConcreteAwakeSensor")
private var delegates: [SensorDelegate] = []
private let locationManager = CLLocationManager()
private let rangeForBeacon: UUID?
init(desiredAccuracy: CLLocationAccuracy = kCLLocationAccuracyThreeKilometers, distanceFilter: CLLocationDistance = CLLocationDistanceMax, rangeForBeacon: UUID? = nil) {
logger.debug("init(desiredAccuracy=\(desiredAccuracy == kCLLocationAccuracyThreeKilometers ? "3km" : desiredAccuracy.description),distanceFilter=\(distanceFilter == CLLocationDistanceMax ? "max" : distanceFilter.description),rangeForBeacon=\(rangeForBeacon == nil ? "disabled" : rangeForBeacon!.description))")
self.rangeForBeacon = rangeForBeacon
super.init()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.desiredAccuracy = desiredAccuracy
locationManager.distanceFilter = distanceFilter
locationManager.allowsBackgroundLocationUpdates = true
if #available(iOS 11.0, *) {
logger.debug("init(ios>=11.0)")
locationManager.showsBackgroundLocationIndicator = false
} else {
logger.debug("init(ios<11.0)")
}
}
func add(delegate: SensorDelegate) {
delegates.append(delegate)
}
func start() {
logger.debug("start")
locationManager.startUpdatingLocation()
logger.debug("startUpdatingLocation")
// Start beacon ranging
guard let beaconUUID = rangeForBeacon else {
return
}
if #available(iOS 13.0, *) {
locationManager.startRangingBeacons(satisfying: CLBeaconIdentityConstraint(uuid: beaconUUID))
logger.debug("startRangingBeacons(ios>=13.0,beaconUUID=\(beaconUUID.description))")
} else {
let beaconRegion = CLBeaconRegion(proximityUUID: beaconUUID, identifier: beaconUUID.uuidString)
locationManager.startRangingBeacons(in: beaconRegion)
logger.debug("startRangingBeacons(ios<13.0,beaconUUID=\(beaconUUID.uuidString)))")
}
}
func stop() {
logger.debug("stop")
locationManager.stopUpdatingLocation()
logger.debug("stopUpdatingLocation")
// Start beacon ranging
guard let beaconUUID = rangeForBeacon else {
return
}
if #available(iOS 13.0, *) {
locationManager.stopRangingBeacons(satisfying: CLBeaconIdentityConstraint(uuid: beaconUUID))
logger.debug("stopRangingBeacons(ios>=13.0,beaconUUID=\(beaconUUID.description))")
} else {
let beaconRegion = CLBeaconRegion(proximityUUID: beaconUUID, identifier: beaconUUID.uuidString)
locationManager.stopRangingBeacons(in: beaconRegion)
logger.debug("stopRangingBeacons(ios<13.0,beaconUUID=\(beaconUUID.description))")
}
}
// MARK:- CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
var state = SensorState.off
if status == CLAuthorizationStatus.authorizedWhenInUse ||
status == CLAuthorizationStatus.authorizedAlways {
state = .on
}
if status == CLAuthorizationStatus.notDetermined {
locationManager.requestAlwaysAuthorization()
locationManager.stopUpdatingLocation()
locationManager.startUpdatingLocation()
}
if status != CLAuthorizationStatus.notDetermined {
delegates.forEach({ $0.sensor(.AWAKE, didUpdateState: state) })
}
}
@available(iOS 14.0, *)
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
var state = SensorState.off
if manager.authorizationStatus == CLAuthorizationStatus.authorizedWhenInUse ||
manager.authorizationStatus == CLAuthorizationStatus.authorizedAlways {
state = .on
}
if manager.authorizationStatus == CLAuthorizationStatus.notDetermined {
locationManager.requestAlwaysAuthorization()
locationManager.stopUpdatingLocation()
locationManager.startUpdatingLocation()
}
if manager.authorizationStatus != CLAuthorizationStatus.notDetermined {
delegates.forEach({ $0.sensor(.AWAKE, didUpdateState: state) })
}
}
}

View file

@ -0,0 +1,53 @@
//
// PayloadDataSupplier.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
/// Payload data supplier for generating payload data that is shared with other devices to provide device identity information while maintaining privacy and security.
/// Implement this to integration your solution with this transport.
public protocol PayloadDataSupplier {
/// Get payload for given timestamp. Use this for integration with any payload generator.
func payload(_ timestamp: PayloadTimestamp) -> PayloadData
/// Get payload for given identifier. Use this for integration with any payload generator.
func payload(_ identifier: UUID, offset: Int, onComplete: @escaping (PayloadData?) -> Void) -> Void
/// Parse raw data into payloads. This is used to split concatenated payloads that are transmitted via share payload. The default implementation assumes payload data is fixed length.
func payload(_ data: Data) -> [PayloadData]
}
/// Implements payload splitting function, assuming fixed length payloads.
public extension PayloadDataSupplier {
/// Default implementation assumes fixed length payload data.
func payload(_ data: Data) -> [PayloadData] {
// Get example payload to determine length
let fixedLengthPayload = payload(PayloadTimestamp())
let payloadLength = fixedLengthPayload.count
// Split data into payloads based on fixed length
var payloads: [PayloadData] = []
var indexStart = 0, indexEnd = payloadLength
while indexEnd <= data.count {
let payload = PayloadData(data.subdata(in: indexStart..<indexEnd))
payloads.append(payload)
indexStart += payloadLength
indexEnd += payloadLength
}
return payloads
}
/// Default Implementation returns payload(timestamp:)
func payload(_ identifier: UUID, offset: Int, onComplete: @escaping (PayloadData?) -> Void) -> Void {
onComplete(payload(PayloadTimestamp()))
}
}
/// Payload timestamp, should normally be Date, but it may change to UInt64 in the future to use server synchronised relative timestamp.
public typealias PayloadTimestamp = Date
/// Encrypted payload data received from target. This is likely to be an encrypted datagram of the target's actual permanent identifier.
public typealias PayloadData = Data

View file

@ -0,0 +1,21 @@
//
// Sensor.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
/// Sensor for detecting and tracking various kinds of disease transmission vectors, e.g. contact with people, time at location.
public protocol Sensor {
/// Add delegate for responding to sensor events.
func add(delegate: SensorDelegate)
/// Start sensing.
func start()
/// Stop sensing.
func stop()
}

View file

@ -0,0 +1,59 @@
//
// SensorArray.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
import UIKit
/// Sensor array for combining multiple detection and tracking methods.
public class SensorArray : NSObject, Sensor {
private let logger = ConcreteSensorLogger(subsystem: "Sensor", category: "SensorArray")
private var sensorArray: [Sensor] = []
private var sensorDelegates: [SensorDelegate] = []
public let payloadData: PayloadData
public static let deviceDescription = "\(UIDevice.current.name) (iOS \(UIDevice.current.systemVersion))"
public init(_ payloadDataSupplier: PayloadDataSupplier) {
logger.debug("init")
// BLE sensor for detecting and tracking proximity
sensorArray.append(ConcreteBLESensor(payloadDataSupplier))
// Payload data at initiation time for identifying this device in the logs
payloadData = payloadDataSupplier.payload(PayloadTimestamp())
super.init()
// Loggers
#if DEBUG
add(delegate: ContactLog(filename: "contacts.csv"))
add(delegate: StatisticsLog(filename: "statistics.csv", payloadData: payloadData))
add(delegate: DetectionLog(filename: "detection.csv", payloadData: payloadData))
_ = BatteryLog(filename: "battery.csv")
#endif
logger.info("DEVICE (payloadPrefix=\(payloadData.shortName),description=\(SensorArray.deviceDescription))")
}
public func add(delegate: SensorDelegate) {
sensorDelegates.append(delegate)
sensorArray.forEach { $0.add(delegate: delegate) }
}
public func start() {
logger.debug("start")
sensorArray.forEach { $0.start() }
}
public func stop() {
logger.debug("stop")
sensorArray.forEach { $0.stop() }
}
public func startAwakeSensor() {
// Location sensor is necessary for enabling background BLE advert detection
let awakeSensor = ConcreteAwakeSensor(rangeForBeacon: UUID(uuidString: BLESensorConfiguration.serviceUUID.uuidString))
sensorDelegates.forEach { awakeSensor.add(delegate: $0) }
sensorArray.append(awakeSensor)
awakeSensor.start()
}
}

View file

@ -0,0 +1,109 @@
//
// SensorDelegate.swift
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
import Foundation
/// Sensor delegate for receiving sensor events.
public protocol SensorDelegate {
/// Detection of a target with an ephemeral identifier, e.g. BLE central detecting a BLE peripheral.
func sensor(_ sensor: SensorType, didDetect: TargetIdentifier)
/// Read payload data from target, e.g. encrypted device identifier from BLE peripheral after successful connection.
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier)
/// Read payload data of other targets recently acquired by a target, e.g. Android peripheral sharing payload data acquired from nearby iOS peripherals.
func sensor(_ sensor: SensorType, didShare: [PayloadData], fromTarget: TargetIdentifier, atProximity: Proximity)
/// Measure proximity to target, e.g. a sample of RSSI values from BLE peripheral.
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier)
/// Measure proximity to target with payload data. Combines didMeasure and didRead into a single convenient delegate method
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier, withPayload: PayloadData)
/// Measure proximity to target with payload data. Combines didMeasure and didRead into a single convenient delegate method
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier, withPayload: PayloadData, forDevice: BLEDevice)
/// Measure proximity to target with payload data. Combines didMeasure and didRead into a single convenient delegate method
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier, atProximity: Proximity, withTxPower: Int?)
/// Sensor state update
func sensor(_ sensor: SensorType, didUpdateState: SensorState)
/// Check if backwards compatibility legacy payload should be written to given device
func shouldWriteToLegacyDevice(_ device: BLEDevice) -> Bool
/// Did write backwards compatibility legacy payload to given device
func didWriteToLegacyDevice(_ device: BLEDevice)
}
/// Sensor delegate functions are all optional.
public extension SensorDelegate {
func sensor(_ sensor: SensorType, didDetect: TargetIdentifier) {}
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier) {}
func sensor(_ sensor: SensorType, didShare: [PayloadData], fromTarget: TargetIdentifier, atProximity: Proximity) {}
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier) {}
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier, withPayload: PayloadData) {}
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier, withPayload: PayloadData, forDevice: BLEDevice) {}
func sensor(_ sensor: SensorType, didUpdateState: SensorState) {}
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier, atProximity: Proximity, withTxPower: Int?) {}
func shouldWriteToLegacyDevice(_ device: BLEDevice) -> Bool { return false }
func didWriteToLegacyDevice(_ device: BLEDevice) {}
}
// MARK:- SensorDelegate data
/// Sensor type as qualifier for target identifier.
public enum SensorType : String {
/// Bluetooth Low Energy (BLE)
case BLE
/// Awake location sensor - uses Location API to be alerted to screen on events
case AWAKE
/// GPS location sensor - not used by default in Herald
case GPS
/// Physical beacon, e.g. iBeacon
case BEACON
/// Ultrasound audio beacon.
case ULTRASOUND
}
/// Sensor state
public enum SensorState : String {
/// Sensor is powered on, active and operational
case on
/// Sensor is powered off, inactive and not operational
case off
/// Sensor is not available
case unavailable
}
/// Ephemeral identifier for detected target (e.g. smartphone, beacon, place). This is likely to be an UUID but using String for variable identifier length.
public typealias TargetIdentifier = String
// MARK:- Proximity data
/// Raw data for estimating proximity between sensor and target, e.g. RSSI for BLE.
public struct Proximity {
/// Unit of measurement, e.g. RSSI
let unit: ProximityMeasurementUnit
/// Measured value, e.g. raw RSSI value.
let value: Double
/// Get plain text description of proximity data
public var description: String { get {
unit.rawValue + ":" + value.description
}}
}
/// Measurement unit for interpreting the proximity data values.
public enum ProximityMeasurementUnit : String {
/// Received signal strength indicator, e.g. BLE signal strength as proximity estimator.
case RSSI
/// Roundtrip time, e.g. Audio signal echo time duration as proximity estimator.
case RTT
}

18
CovidSafe/Herald/herald.h Normal file
View file

@ -0,0 +1,18 @@
//
// Herald.h
//
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: MIT
//
#import <Foundation/Foundation.h>
//! Project version number for Herald.
FOUNDATION_EXPORT double HeraldVersionNumber;
//! Project version string for Herald.
FOUNDATION_EXPORT const unsigned char HeraldVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Herald/PublicHeader.h>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -23,6 +23,7 @@
<outlet property="inactiveAppSectionView" destination="784-Jf-kOX" id="J3m-Pu-697"/>
<outlet property="inactiveSettingsContent" destination="AUW-C2-ven" id="NNP-o9-zkK"/>
<outlet property="inactiveTokenExpiredView" destination="nxM-ji-ttb" id="vVC-KW-yek"/>
<outlet property="locationPermissionsView" destination="u4f-uR-ri3" id="70q-Jr-1cc"/>
<outlet property="pairingRequestsLabel" destination="EF2-ER-Q6h" id="Wx5-bg-Rp3"/>
<outlet property="scrollView" destination="Lws-gT-pWp" id="pTG-Kh-B9T"/>
<outlet property="shareView" destination="vs9-rS-UOM" id="eoj-sn-TkG"/>
@ -42,6 +43,11 @@
<action selector="onAppSettingsTapped:" destination="-1" id="Tiq-zP-NGf"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="yhN-Oh-SVH" userLabel="LocationAppSettingsTapped">
<connections>
<action selector="onAppSettingsTapped:" destination="-1" id="guC-1O-5d5"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="eUy-DU-cZn" userLabel="BluetoothPairingLabelTapped">
<connections>
<action selector="bluetoothPairingTapped:" destination="-1" id="byI-uk-ZfC"/>
@ -165,19 +171,19 @@
</constraints>
</view>
<stackView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="h36-8c-K2n">
<rect key="frame" x="0.0" y="120" width="414" height="1566.5"/>
<rect key="frame" x="0.0" y="120" width="414" height="1711"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bRs-XW-qzv" userLabel="StatusView">
<rect key="frame" x="0.0" y="0.0" width="414" height="764"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="908.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="rcS-nL-IAO">
<rect key="frame" x="0.0" y="0.0" width="414" height="752"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="896.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="784-Jf-kOX" userLabel="InactiveView">
<rect key="frame" x="0.0" y="0.0" width="414" height="552"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="696.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="1" translatesAutoresizingMaskIntoConstraints="NO" id="xti-6W-zko" userLabel="Inactive Stack View">
<rect key="frame" x="0.0" y="0.0" width="414" height="552"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="696.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4fe-SU-8Q6" userLabel="InActiveHeader">
<rect key="frame" x="0.0" y="0.0" width="414" height="81"/>
@ -367,7 +373,7 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ers-f9-BFH" userLabel="Bluetooth Status Off Section">
<rect key="frame" x="0.0" y="450.5" width="414" height="101.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="p5d-dk-foR" userLabel="Bluetooth Status Bar Off">
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="p5d-dk-foR" userLabel="Bluetooth Status Bar Off">
<rect key="frame" x="0.0" y="0.0" width="414" height="101.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Bluetooth®: Off" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="g0P-dF-xcR">
@ -431,6 +437,74 @@
<outletCollection property="gestureRecognizers" destination="tGp-em-x7B" appends="YES" id="mo7-ga-Hnw"/>
</connections>
</view>
<view contentMode="scaleToFill" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="u4f-uR-ri3" userLabel="Location Settings">
<rect key="frame" x="0.0" y="553" width="414" height="143.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aGh-Fm-LNa" userLabel="Location Off bar">
<rect key="frame" x="0.0" y="0.0" width="414" height="142.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Location settings" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4a2-uh-JwT">
<rect key="frame" x="16" y="16" width="150" height="24"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle3"/>
<color key="textColor" red="0.63921568630000003" green="0.098039215690000001" blue="0.098039215690000001" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizationKey" value="location_off"/>
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" userInteractionEnabled="NO" contentMode="center" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="GjW-bP-Gyh">
<rect key="frame" x="364" y="6" width="44" height="44"/>
<accessibility key="accessibilityConfiguration">
<bool key="isElement" value="NO"/>
</accessibility>
<constraints>
<constraint firstAttribute="height" constant="44" id="TWO-o0-rOr"/>
<constraint firstAttribute="width" constant="44" id="tPO-9M-U8r"/>
</constraints>
<state key="normal" image="chevron-right-red"/>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6HO-2o-TTh">
<rect key="frame" x="16" y="44" width="340" height="82.5"/>
<accessibility key="accessibilityConfiguration">
<bool key="isElement" value="NO"/>
</accessibility>
<string key="text">Enabling Location Services improves Bluetooth performance when identifying close contacts. COVIDSafe does NOT store or use location data.</string>
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
<color key="textColor" red="0.63921568630000003" green="0.098039215690000001" blue="0.098039215690000001" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizationKey" value="location_off_description"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="6HO-2o-TTh" secondAttribute="bottom" constant="16" id="2bJ-8i-mJK"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="4a2-uh-JwT" secondAttribute="trailing" constant="20" symbolic="YES" id="ABg-mm-FYN"/>
<constraint firstItem="4a2-uh-JwT" firstAttribute="top" secondItem="aGh-Fm-LNa" secondAttribute="top" constant="16" id="Cil-S8-xIK"/>
<constraint firstAttribute="trailing" secondItem="GjW-bP-Gyh" secondAttribute="trailing" constant="6" id="IuY-4n-l2D"/>
<constraint firstItem="6HO-2o-TTh" firstAttribute="leading" secondItem="aGh-Fm-LNa" secondAttribute="leading" constant="16" id="Jv8-9f-6YY"/>
<constraint firstItem="4a2-uh-JwT" firstAttribute="leading" secondItem="aGh-Fm-LNa" secondAttribute="leading" constant="16" id="LdD-zr-19B"/>
<constraint firstItem="GjW-bP-Gyh" firstAttribute="leading" secondItem="6HO-2o-TTh" secondAttribute="trailing" constant="8" id="MTX-4f-BX4"/>
<constraint firstItem="GjW-bP-Gyh" firstAttribute="centerY" secondItem="4a2-uh-JwT" secondAttribute="centerY" id="g01-mZ-oty"/>
<constraint firstItem="6HO-2o-TTh" firstAttribute="top" secondItem="4a2-uh-JwT" secondAttribute="bottom" constant="4" id="wR8-8y-108"/>
</constraints>
</view>
</subviews>
<accessibility key="accessibilityConfiguration" label="Bluetooth permissions off">
<accessibilityTraits key="traits" button="YES"/>
<bool key="isElement" value="YES"/>
</accessibility>
<constraints>
<constraint firstItem="aGh-Fm-LNa" firstAttribute="top" secondItem="u4f-uR-ri3" secondAttribute="top" id="6KK-e5-iql"/>
<constraint firstAttribute="bottom" secondItem="aGh-Fm-LNa" secondAttribute="bottom" constant="1" id="Qne-bx-T9S"/>
<constraint firstItem="aGh-Fm-LNa" firstAttribute="leading" secondItem="u4f-uR-ri3" secondAttribute="leading" id="ZbU-z8-9iT"/>
<constraint firstAttribute="trailing" secondItem="aGh-Fm-LNa" secondAttribute="trailing" id="bgp-cU-6fm"/>
</constraints>
<connections>
<outletCollection property="gestureRecognizers" destination="yhN-Oh-SVH" appends="YES" id="uuY-YZ-hRu"/>
</connections>
</view>
</subviews>
</stackView>
</subviews>
@ -443,7 +517,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="m1D-93-7sF" userLabel="ActiveView">
<rect key="frame" x="0.0" y="552" width="414" height="200"/>
<rect key="frame" x="0.0" y="696.5" width="414" height="200"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="1" translatesAutoresizingMaskIntoConstraints="NO" id="981-Cd-Xm3" userLabel="Active Stack View">
<rect key="frame" x="0.0" y="0.0" width="414" height="200"/>
@ -562,7 +636,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Aop-Ae-hRv" userLabel="StatisticsViewSection">
<rect key="frame" x="0.0" y="764" width="414" height="124"/>
<rect key="frame" x="0.0" y="908.5" width="414" height="124"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleToFill" verticalHuggingPriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="eZl-C5-gSv" userLabel="StatisticsView">
<rect key="frame" x="0.0" y="12" width="414" height="100"/>
@ -580,7 +654,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9cE-NC-A20" userLabel="Help">
<rect key="frame" x="0.0" y="888" width="414" height="153"/>
<rect key="frame" x="0.0" y="1032.5" width="414" height="153"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="co1-dK-1WU">
<rect key="frame" x="0.0" y="12" width="414" height="129"/>
@ -713,7 +787,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7cN-DY-lc3" userLabel="Change language">
<rect key="frame" x="0.0" y="1041" width="414" height="153"/>
<rect key="frame" x="0.0" y="1185.5" width="414" height="153"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bE9-gT-Hba">
<rect key="frame" x="0.0" y="12" width="414" height="129"/>
@ -846,7 +920,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vs9-rS-UOM" userLabel="Share CovidSafe">
<rect key="frame" x="0.0" y="1194" width="414" height="151"/>
<rect key="frame" x="0.0" y="1338.5" width="414" height="151"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RCa-zU-3Vo">
<rect key="frame" x="0.0" y="12" width="414" height="127"/>
@ -957,7 +1031,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JSe-D6-hyV" userLabel="Upload Data">
<rect key="frame" x="0.0" y="1345" width="414" height="165.5"/>
<rect key="frame" x="0.0" y="1489.5" width="414" height="165.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8pS-Df-p0U">
<rect key="frame" x="0.0" y="12" width="414" height="141.5"/>
@ -1077,7 +1151,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eZ2-CQ-dtQ" userLabel="Version View">
<rect key="frame" x="0.0" y="1510.5" width="414" height="56"/>
<rect key="frame" x="0.0" y="1655" width="414" height="56"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Version number:" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="O1w-Sl-OIH" userLabel="Version">
<rect key="frame" x="16" y="12" width="382" height="20"/>

View file

@ -10,6 +10,7 @@ class HomeViewController: UIViewController {
@IBOutlet weak var bluetoothStatusOffView: UIView!
@IBOutlet weak var bluetoothPermissionOffView: UIView!
@IBOutlet weak var locationPermissionsView: UIView!
@IBOutlet weak var inactiveSettingsContent: UIView!
@IBOutlet weak var inactiveTokenExpiredView: UIView!
@IBOutlet weak var shareView: UIView!
@ -48,6 +49,7 @@ class HomeViewController: UIViewController {
var bluetoothStatusOn = true
var bluetoothPermissionOn = true
var pushNotificationOn = true
var locationPermissionOn = true
var shouldShowUpdateApp = false
var didUploadData: Bool {
@ -100,15 +102,18 @@ class HomeViewController: UIViewController {
scrollView.refreshControl = UIRefreshControl()
scrollView.refreshControl?.addTarget(self, action: #selector(refreshControlEvent), for: .valueChanged)
// this is to show the settings prompt initially if bluetooth is off
if !BluetraceManager.shared.isBluetoothOn() {
BluetraceManager.shared.turnOn()
}
observer = NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main) { [unowned self] notification in
self.refreshView()
}
BluetraceManager.shared.sensorDidUpdateStateCallback = { state, sensorType in
if let sensor = sensorType, sensor == .AWAKE {
self.locationPermissionOn = state == .on
self.toggleViews()
}
}
if !shouldShowPolicyUpdateMessage() {
startAllSensors()
}
updateAnimationViewWithAnimationName(name: "Spinner_home")
@ -253,6 +258,7 @@ class HomeViewController: UIViewController {
self?.toggleBluetoothStatusView()
self?.toggleBluetoothPermissionStatusView()
self?.toggleLocationPermissionStatusView()
self?.toggleHeaderView()
self?.toggleUploadView()
self?.toggleUploadDateView()
@ -306,8 +312,9 @@ class HomeViewController: UIViewController {
fileprivate func readPermissions(notificationSettings: UNNotificationSettings) {
self.bluetoothStatusOn = BluetraceManager.shared.isBluetoothOn()
self.bluetoothPermissionOn = BluetraceManager.shared.isBluetoothAuthorized()
self.locationPermissionOn = BluetraceManager.shared.isLocationOnAuthorized()
self.pushNotificationOn = notificationSettings.authorizationStatus == .authorized
let newAllPermissionsOn = self.bluetoothStatusOn && self.bluetoothPermissionOn
let newAllPermissionsOn = self.bluetoothStatusOn && self.bluetoothPermissionOn && self.locationPermissionOn
if newAllPermissionsOn != self.allPermissionOn {
self.allPermissionOn = newAllPermissionsOn
@ -360,13 +367,17 @@ class HomeViewController: UIViewController {
toggleViewVisibility(view: bluetoothPermissionOffView, isVisible: !self.allPermissionOn && !self.bluetoothPermissionOn && !registrationNeeded)
}
fileprivate func toggleLocationPermissionStatusView() {
toggleViewVisibility(view: locationPermissionsView, isVisible: !allPermissionOn && !locationPermissionOn && (bluetoothPermissionOn && bluetoothStatusOn) && !registrationNeeded)
}
fileprivate func toggleRegistrationNeededView() {
toggleViewVisibility(view: inactiveTokenExpiredView, isVisible: registrationNeeded)
}
func attemptTurnOnBluetooth() {
BluetraceManager.shared.toggleScanning(false)
BluetraceManager.shared.turnOn()
BluetraceManager.shared.turnOnBLE()
}
fileprivate func updateAppActiveSubtitle() {
@ -386,14 +397,13 @@ class HomeViewController: UIViewController {
func shouldShowPolicyUpdateMessage() -> Bool {
// this is the min version that the disclamer should be diplayed on.
let minVersionShowPolicyUpdate = 77
let minVersionShowPolicyUpdate = 89
let latestVersionShown = UserDefaults.standard.integer(forKey: "latestPolicyUpdateVersionShown")
guard let currentVersion = (Bundle.main.version as NSString?)?.integerValue else {
return false
}
if currentVersion >= minVersionShowPolicyUpdate && currentVersion > latestVersionShown {
UserDefaults.standard.set(currentVersion, forKey: "latestPolicyUpdateVersionShown")
return true
}
return false
@ -401,14 +411,24 @@ class HomeViewController: UIViewController {
func showPolicyUpdateMessage() {
let privacyPolicyUrl = URLHelper.getPrivacyPolicyURL()
let disclaimerAlert = CSAlertViewController(nibName: "CSAlertView", bundle: nil)
let disclaimerMsg = NSMutableAttributedString(string: "collection_message".localizedString(), attributes: [.font : UIFont.preferredFont(forTextStyle: .body)])
disclaimerMsg.addLink(enclosedIn: "*", urlString: privacyPolicyUrl)
disclaimerAlert.set(message: disclaimerMsg, buttonLabel: "dismiss".localizedString())
guard let currentVersion = (Bundle.main.version as NSString?)?.integerValue else {
return
}
UserDefaults.standard.set(currentVersion, forKey: "latestPolicyUpdateVersionShown")
let disclaimerAlert = CSGenericContentViewController(nibName: "CSGenericContentView", bundle: nil)
let contentAttributedString = NSMutableAttributedString(string: "update_description".localizedString(), attributes: [
.font: UIFont.preferredFont(forTextStyle: .body)
])
disclaimerAlert.contentViewModel = CSGenericContentViewModel(viewTitle: "update_heading".localizedString(),
viewContentDescription: contentAttributedString,
buttonLabel: "update_modal_button".localizedString(),
buttonCallback: {
self.startAllSensors()
disclaimerAlert.dismiss(animated: true)
})
disclaimerAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
disclaimerAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
disclaimerAlert.modalTransitionStyle = UIModalTransitionStyle.coverVertical
present(disclaimerAlert, animated: true, completion: nil)
}
@ -417,6 +437,12 @@ class HomeViewController: UIViewController {
toggleViews()
}
// MARK: Sensors
func startAllSensors() {
BluetraceManager.shared.turnOnAllSensors()
}
// MARK: API calls
func getMessagesFromServer(force: Bool = false, completion: @escaping () -> Void = {}) {
let onMessagesDone: (MessageResponse?, CovidSafeAPIError?) -> Void = { (messageResponse, error) in

View file

@ -1,5 +1,5 @@
//
// OnboardingStep1bViewController.swift
// HowItWorksViewController.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
@ -7,7 +7,7 @@
import UIKit
class OnboardingStep1bViewController: UIViewController {
class HowItWorksViewController: UIViewController {
@IBOutlet weak var textView: UITextView!

View file

@ -30,12 +30,19 @@
<string>COVIDSafe exchanges Bluetooth® signals with nearby phones running the same app. These signals contain an anonymised ID, which is encrypted and changes continually to ensure your privacy.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>COVIDSafe exchanges Bluetooth® signals with nearby phones running the same app. These signals contain an anonymised ID, which is encrypted and changes continually to ensure your privacy.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We request location permissions to improve the performance of the system and how we provide information about COVID-19. Location permissions will only be used for this purpose. We will not collect or store your location information.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>We request location permissions to improve the performance of the system and how we provide information about COVID-19. Location permissions will only be used for this purpose. We will not collect or store your location information.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We request location permissions to improve the performance of the system and how we provide information about COVID-19. Location permissions will only be used for this purpose. We will not collect or store your location information.</string>
<key>TRACER_SVC_ID</key>
<string>${SERVICE_UUID}</string>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>bluetooth-peripheral</string>
<string>location</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>

View file

@ -4,57 +4,76 @@ import UIKit
public extension NSMutableAttributedString {
enum ElementType {
case Link,
Bold
}
func parseHTMLLinks() {
while canParseHtmlOccurenceLink() {
parseHtmlOccurenceLink()
let regexLinkStartElementString = #"\<a(.*?)\>"#
let regexLinkEndElementTextString = #"\<\/a\>"#
while canParseOccurence(elementStartRegex: regexLinkStartElementString, elementEndRegex: regexLinkEndElementTextString) {
parseHtmlOccurence(elementStartRegex: regexLinkStartElementString, elementEndRegex: regexLinkEndElementTextString, elementType: .Link)
}
}
fileprivate func canParseHtmlOccurenceLink() -> Bool {
let regexLinkStartElementString = #"\<a(.*?)\>"#
let regexLinkEndElementTextString = #"\<\/a\>"#
guard string.range(of: regexLinkStartElementString, options: .regularExpression) != nil else {
func parseBoldTags() {
let regexBoldStartElementString = #"\<b(.*?)\>"#
let regexBoldEndElementTextString = #"\<\/b\>"#
while canParseOccurence(elementStartRegex: regexBoldStartElementString, elementEndRegex: regexBoldEndElementTextString) {
parseHtmlOccurence(elementStartRegex: regexBoldStartElementString, elementEndRegex: regexBoldEndElementTextString, elementType: .Bold)
}
}
func canParseOccurence(elementStartRegex: String, elementEndRegex: String) -> Bool {
guard string.range(of: elementStartRegex, options: .regularExpression) != nil else {
return false
}
guard string.range(of: regexLinkEndElementTextString, options: .regularExpression) != nil else {
guard string.range(of: elementEndRegex, options: .regularExpression) != nil else {
return false
}
return true
}
fileprivate func parseHtmlOccurenceLink() {
let regexLinkStartElementString = #"\<a(.*?)\>"#
let regexLinkEndElementTextString = #"\<\/a\>"#
guard let strStartElementRange = string.range(of: regexLinkStartElementString, options: .regularExpression) else {
fileprivate func parseHtmlOccurence(elementStartRegex: String, elementEndRegex: String, elementType: ElementType) {
guard let strStartElementRange = string.range(of: elementStartRegex, options: .regularExpression) else {
return
}
guard let strEndElementRange = string.range(of: regexLinkEndElementTextString, options: .regularExpression) else {
guard let strEndElementRange = string.range(of: elementEndRegex, options: .regularExpression) else {
return
}
var urlString = ""
let startElementStr = String(string[strStartElementRange])
if let urlRange = startElementStr.range(of: #"\"(.*?)\""#, options: .regularExpression) {
let urlMatch = startElementStr[urlRange]
urlString = String(urlMatch)
let start = urlString.index(after: urlString.startIndex)
//ofset by 2 to as the quotes are escaped with \
let end = urlString.index(urlString.endIndex, offsetBy: -2)
urlString = String(urlString[start...end])
}
let convertedStartRange = NSRange(strStartElementRange, in: string)
let convertedEndRange = NSRange(strEndElementRange, in: string)
let nsStartElementRange = NSRange(location: convertedStartRange.location, length: convertedStartRange.upperBound - convertedStartRange.lowerBound)
let nsEndElementRange = NSRange(location: convertedEndRange.location, length: convertedEndRange.upperBound - convertedEndRange.lowerBound)
//remove html marking from text
replaceCharacters(in: nsEndElementRange, with: "*")
replaceCharacters(in: nsStartElementRange, with: "*")
addLink(enclosedIn: "*", urlString: urlString)
switch elementType {
case .Link:
//get the url string
var urlString = ""
let startElementStr = String(string[strStartElementRange])
if let urlRange = startElementStr.range(of: #"\"(.*?)\""#, options: .regularExpression) {
let urlMatch = startElementStr[urlRange]
urlString = String(urlMatch)
let start = urlString.index(after: urlString.startIndex)
//ofset by 2 to as the quotes are escaped with \
let end = urlString.index(urlString.endIndex, offsetBy: -2)
urlString = String(urlString[start...end])
}
//remove html marking from text
replaceCharacters(in: nsEndElementRange, with: "*")
replaceCharacters(in: nsStartElementRange, with: "*")
addLink(enclosedIn: "*", urlString: urlString)
case .Bold:
//remove bold marking from text
replaceCharacters(in: nsEndElementRange, with: "#")
replaceCharacters(in: nsStartElementRange, with: "#")
addBold(enclosedIn: "#")
}
}
@discardableResult
@ -87,4 +106,34 @@ public extension NSMutableAttributedString {
return true
}
@discardableResult
func addBold(enclosedIn marker: String) -> Bool {
guard !marker.isEmpty else { return false }
let regexString = "\(marker)(.*?)\(marker)"
guard let strRange = string.range(of: regexString, options: .regularExpression) else {
return false
}
let convertedRange = NSRange(strRange, in: string)
let matchingString = string[strRange]
let enclosedString = matchingString.replacingOccurrences(of: marker, with: "")
let nsBeginRange = NSRange(location: convertedRange.location, length: marker.count)
let nsEndRange = NSRange(location: convertedRange.upperBound - marker.count, length: marker.count)
// first replace end, otherwise the range will change
replaceCharacters(in: nsEndRange, with: "")
replaceCharacters(in: nsBeginRange, with: "")
let linkRange = NSRange(location: convertedRange.location, length: enclosedString.count)
// for now only supporting body. Need to get the UIFont from the current string.
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont.preferredFont(for: .body, weight: .semibold)
]
addAttributes(attributes, range: linkRange)
return true
}
}

View file

@ -286,7 +286,7 @@ class OTPViewController: UIViewController, RegistrationHandler {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let successVC = segue.destination as? OnboardingStep2bViewController {
if let successVC = segue.destination as? RegistrationSuccessViewController {
successVC.reauthenticating = true
}
}

View file

@ -1,52 +0,0 @@
//
// OnboardingStep2ViewController.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import UIKit
import CoreBluetooth
import UserNotifications
class OnboardingStep2ViewController: UIViewController {
private var bluetoothDidUpdateStateCallback: ((CBManagerState) -> Void)?
@IBOutlet weak var stepCounterLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
stepCounterLabel.text = String.localizedStringWithFormat( "stepCounter".localizedString(),
4,
4
)
}
@IBAction func proceedTapped(_ sender: UIButton) {
self.bluetoothDidUpdateStateCallback = BluetraceManager.shared.bluetoothDidUpdateStateCallback
BluetraceManager.shared.bluetoothDidUpdateStateCallback = centralDidUpdateStateCallback
BluetraceManager.shared.turnOn()
UserDefaults.standard.set(true, forKey: "turnedOnBluetooth")
}
func centralDidUpdateStateCallback(_ state: CBManagerState) {
DLog("state changed in permission request to \(BluetraceUtils.centralStateToString(state))")
requestPushPermissions()
}
func requestPushPermissions() {
BluetraceManager.shared.bluetoothDidUpdateStateCallback = self.bluetoothDidUpdateStateCallback
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) {
granted, error in
UserDefaults.standard.set(true, forKey: "allowedPermissions")
print("Permissions granted: \(granted)")
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
self.performSegue(withIdentifier: "showSuccessSegue", sender: self)
}
}
}
}

View file

@ -1,194 +0,0 @@
import CoreBluetooth
public struct PeripheralCharacteristicsData: Codable {
var modelP: String // phone model of peripheral
var msg: String // tempID
var org: String
var v: Int
}
let TRACER_SVC_ID: CBUUID = CBUUID(string: "\(PlistHelper.getvalueFromInfoPlist(withKey: "TRACER_SVC_ID") ?? "B82AB3FC-1595-4F6A-80F0-FE094CC218F9")")
let TRACER_SVC_CHARACTERISTIC_ID = CBUUID(string: "\(PlistHelper.getvalueFromInfoPlist(withKey: "TRACER_SVC_ID") ?? "B82AB3FC-1595-4F6A-80F0-FE094CC218F9")")
let ORG_ID = "<your org id here>"
let PROTOCOL_VERSION = 1
public class PeripheralController: NSObject {
enum PeripheralError: Error {
case peripheralAlreadyOn
case peripheralAlreadyOff
}
struct CachedPayload {
var payload: Data,
expiry: TimeInterval
}
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 payloadLookaside = [UUID: CachedPayload]()
private let FREQUENCY_OF_CONNECTION_IN_S = 20.0
private var characteristicData: PeripheralCharacteristicsData
private var peripheral: CBPeripheralManager!
private var queue: DispatchQueue
private lazy var readableCharacteristic = CBMutableCharacteristic(type: BluetraceConfig.BluetoothServiceID, properties: [.read, .write, .writeWithoutResponse], value: nil, permissions: [.readable, .writeable])
public init(peripheralName: String, queue: DispatchQueue) {
DLog("PC init")
self.queue = queue
self.peripheralName = peripheralName
self.characteristicData = PeripheralCharacteristicsData(modelP: DeviceIdentifier.getModel(), msg: "<unknown>", org: BluetraceConfig.OrgID, v: BluetraceConfig.ProtocolVersion)
super.init()
}
public func turnOn() {
guard peripheral == nil else {
return
}
peripheral = CBPeripheralManager(delegate: self, queue: self.queue, options: [CBPeripheralManagerOptionRestoreIdentifierKey: restoreIdentifierKey, CBPeripheralManagerOptionShowPowerAlertKey: 1])
}
public func turnOff() {
guard peripheral != nil else {
return
}
peripheral.stopAdvertising()
peripheral = nil
}
public func getState() -> String {
return BluetraceUtils.centralStateToString(peripheral.state)
}
}
extension PeripheralController: CBPeripheralManagerDelegate {
public func peripheralManager(_ peripheral: CBPeripheralManager,
willRestoreState dict: [String : Any]) {
DLog("PC willRestoreState")
}
public func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
DLog("PC peripheralManagerDidUpdateState. Current state: \(BluetraceUtils.centralStateToString(peripheral.state))")
didUpdateState?(BluetraceUtils.centralStateToString(peripheral.state))
guard peripheral.state == .poweredOn else { return }
let advertisementData: [String: Any] = [CBAdvertisementDataLocalNameKey: peripheralName,
CBAdvertisementDataServiceUUIDsKey: [BluetraceConfig.BluetoothServiceID]]
let tracerService = CBMutableService(type: BluetraceConfig.BluetoothServiceID, primary: true)
tracerService.characteristics = [readableCharacteristic]
peripheral.removeAllServices()
peripheral.add(tracerService)
peripheral.startAdvertising(advertisementData)
}
public func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
DLog("\(["request": request] as AnyObject)")
let remoteCentral = request.central.identifier;
// clean up expired payloads
cleanUpExpiredCachedPayloads()
if request.offset == 0 {
// new request, create new payload
EncounterMessageManager.shared.getAdvertisementPayload { (payloadToAdvertise) in
self.queue.async {
if let payload = payloadToAdvertise {
// cache payload for remote
self.payloadLookaside[remoteCentral] = CachedPayload(payload: payload, expiry: Date().timeIntervalSince1970 + self.FREQUENCY_OF_CONNECTION_IN_S);
request.value = payload.advanced(by: request.offset)
peripheral.respond(to: request, withResult: .success)
} else {
DLog("Error getting payload to advertise")
peripheral.respond(to: request, withResult: .unlikelyError)
}
}
}
} else {
// get cached payload, check offset valid
guard let cachedPayload = self.payloadLookaside[remoteCentral] else {
peripheral.respond(to: request, withResult: .unlikelyError)
return
}
if request.offset > cachedPayload.payload.count {
peripheral.respond(to: request, withResult: .invalidOffset)
return
}
if request.offset == cachedPayload.payload.count {
// the central already read all the data in its last read request
peripheral.respond(to: request, withResult: .success)
return
}
// return payload as normal
request.value = cachedPayload.payload.advanced(by: request.offset)
peripheral.respond(to: request, withResult: .success)
}
}
fileprivate func cleanUpExpiredCachedPayloads() {
for payloadKey in payloadLookaside.keys {
let currentTime = Date().timeIntervalSince1970
guard let payload = payloadLookaside[payloadKey], payload.expiry < currentTime else {
continue
}
// if payload exists and expiry time is less than current time, remove.
payloadLookaside.removeValue(forKey: payloadKey)
}
}
public func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
let debugLogs = ["requests": requests as AnyObject,
"reqValue": String(data: requests[0].value!, encoding: .utf8) ?? "<nil>"] as AnyObject
DLog("\(debugLogs)")
do {
for request in requests {
if let characteristicValue = request.value {
let dataFromCentral = try JSONDecoder().decode(CentralWriteData.self, from: characteristicValue)
let encounter = EncounterRecord(from: dataFromCentral)
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)
} catch {
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
}
}

View file

@ -1,5 +1,5 @@
//
// OnboardingStep1aViewController.swift
// PrivacyPolicyViewController.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
@ -9,7 +9,7 @@ import UIKit
import KeychainSwift
import SafariServices
class OnboardingStep1aViewController: UIViewController, UITextViewDelegate {
class PrivacyPolicyViewController: UIViewController, UITextViewDelegate {
@IBOutlet weak var openLink: UILabel!

View file

@ -1,5 +1,5 @@
//
// OnboardingStep1ViewController.swift
// RegistrationIntroViewController.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
@ -9,7 +9,7 @@ import UIKit
import SafariServices
import KeychainSwift
class OnboardingStep1ViewController: UIViewController {
class RegistrationIntroViewController: UIViewController {
let keychain = KeychainSwift()

View file

@ -1,5 +1,5 @@
//
// OnboardingStep2bViewController.swift
// RegistrationSuccessViewController.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
@ -8,7 +8,7 @@
import UIKit
import SafariServices
class OnboardingStep2bViewController: UIViewController {
class RegistrationSuccessViewController: UIViewController {
@IBOutlet weak var pointOneLabel: UILabel!
@IBOutlet weak var pointTwoLabel: UILabel!
@IBOutlet weak var pointThreeLabel: UILabel!

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -206,9 +206,6 @@
<constraint firstAttribute="bottom" secondItem="nm4-W8-yfz" secondAttribute="bottom" constant="1" id="Rog-yq-MUA"/>
<constraint firstItem="nm4-W8-yfz" firstAttribute="top" secondItem="1F3-ji-p1Z" secondAttribute="top" id="SY0-Kp-41h"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localVOLabelKey" value="AllowBluetoothOFF_VOLabel"/>
</userDefinedRuntimeAttributes>
<connections>
<outletCollection property="gestureRecognizers" destination="9uF-DB-1IF" appends="YES" id="MIg-P4-8ip"/>
</connections>
@ -266,9 +263,6 @@
<constraint firstAttribute="trailing" secondItem="2Uq-lN-t0Y" secondAttribute="trailing" id="dJP-zw-VDV"/>
<constraint firstItem="2Uq-lN-t0Y" firstAttribute="leading" secondItem="syj-9c-EKO" secondAttribute="leading" id="ipO-6X-Lp6"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localVOLabelKey" value="BluetoothOFF_VOLabel"/>
</userDefinedRuntimeAttributes>
<connections>
<outletCollection property="gestureRecognizers" destination="YgE-FC-hoe" appends="YES" id="ODK-ba-1bP"/>
</connections>

View file

@ -12,11 +12,22 @@ extension UITextView {
tintColor = UIColor.covidSafeColor
}
func parseHTMLLinks() {
func addAllBold(enclosedIn marker: String) {
guard let attributedText = attributedText else { return }
let mutableString = NSMutableAttributedString(attributedString: attributedText)
while mutableString.canParseOccurence(elementStartRegex: marker, elementEndRegex: marker) {
mutableString.addBold(enclosedIn: marker)
}
self.attributedText = mutableString
}
func parseHTMLTags() {
guard let attributedText = attributedText else { return }
let mutableString = NSMutableAttributedString(attributedString: attributedText)
mutableString.parseHTMLLinks()
mutableString.parseBoldTags()
self.attributedText = mutableString
tintColor = UIColor.covidSafeColor
}

View file

@ -1,8 +1,11 @@
"28_days" = "";
"7_days" = "";
"action_continue" = "تابع";
"action_report_an_issue" = "بلِّغ عن مشكلة";
"action_upload_done" = "تمّ";
"action_verify_invalid_pin" = "رقم التعريف الشخصي غير صالح، يرجى الطلب من موظف دائرة الصحة أن يرسل لك رقم تعريف شخصي آخر.";
"action_verify_upload_pin" = "تحميل معلوماتي";
"active_cases" = "";
/* Figma page iOS Screens (05/06) */
"allow_bluetooth_call" = "السماح لتطبيق COVIDSafe بالوصول إلى Bluetooth®";
/* Figma page iOS Screens (05/06) */
@ -16,6 +19,8 @@
"BluetoothOFF_content" = "شغِّل Bluetooth® على هاتفك، اختر \"السماح باتصالات جديدة\" في حال ظهورها.";
/* Figma page iOS Screens (05/06) */
"BluetoothON" = "Bluetooth®: مشغّل";
"cases_28_days" = "";
"cases_7_days" = "";
"change_language" = "تغيير اللغة";
"change_language_content" = "اقرأ الدليل حول استخدام تطبيق COVIDSafe بلغة مختلفة.";
"collection_message" = "يقوم تطبيق COVIDSafe الآن بجمع معلومات تشخيصية لمساعدتك على استكشاف المشكلات في تطبيقك وإصلاحها. *أعرف المزيد*";
@ -239,7 +244,7 @@
/* OnBoarding Data Privacy */
"data_privacy_headline" = "التسجيل والخصوصية";
"deaths" = "الوفيات";
"dialog_error_uploading_message" = "حدث خطأ أثناء تحميل معلوماتك، يرجى المحاولة مرة أخرى.";
"dialog_error_uploading_message" = "حاول تحميل بياناتك مرة أخرى. \n\n ارجع إلى رمز الخطأ هذا إذا طلب موظف دائرة الصحة في الولاية أو الإقليم ذلك: %@\n";
"dialog_error_uploading_negative" = "إلغاء";
"dialog_error_uploading_positive" = "حاول مرة أخرى";
"dialog_uploading_message" = "يتم الآن تحميل معلومات COVIDSafe الخاصة بك. \n\n الرجاء عدم إغلاق التطبيق.";
@ -293,6 +298,7 @@
"home_set_complete_external_link_share_title" = "شارك تطبيق COVIDSafe";
"home_setup_help" = "مساعدة";
"home_version_number_ios" = "الإصدار %@، الرقم %@";
"hotspots_state_territory" = "";
"how_it_works_button" = "التالي";
"how_it_works_content" = "تُستخدم إشارات Bluetooth® لتحديد متى تكون بالقرب من مستخدم آخر لتطبيق COVIDSafe . \n\n سيتم تدوين ملاحظة في كل مرة يحصل فيها اختلاط عن قرب بينك وبين مستخدمي تطبيق COVIDSafe الآخرين لجمع معلومات عن اختلاط الأشخاص. يتم تشفير المعلومات وتخزينها فقط في هاتفك. \n\n إذا جاءت نتيجة فحصك ﻟ COVID-19 إيجابية كمستخدم لتطبيق COVIDSafe، سوف يتصل بك أحد موظفي دائرة الصحة في الولاية أو الإقليم ويساعدك في القيام بتحميل اختياري لمعلومات الاتصال بك إلى نظام تخزين معلومات آمن للغاية \nيمكن أيضًا لموظفي دائرة الصحة في الولاية أو الإقليم الاتصال بك إذا حصل اتصال وثيق بينك وبين مستخدم آخر لتطبيق COVIDSafe أظهرت نتائج الفحص أنه مصاب بالفيروس.\n\n لمزيد من المعلومات، يرجى الرجوع إلى صفحة *مواضيع المساعدة*.";
/* OnBoarding How it works */
@ -315,12 +321,16 @@
"invalid_norfolk_island_phone_number_error_prompt" = "تتألف أرقام الهواتف المحمولة في جزيرة نورفولك من 5 إلى 6 أرقام.";
"jwt_description" = "هناك مشكلة في تفاصيل التسجيل الخاصة بك.";
"jwt_heading" = "يرجى التسجيل مرة أخرى";
"jwt_success" = "";
"jwt_success" = "تم تجديد التسجيل بنجاح";
"loading_numbers" = "تحميل أحدث الأرقام";
"locally_acquired" = "";
"location_off" = "الموقع الجغرافي: معطّل";
"location_off_description" = "يتطلب جهاز iphone الخاص بك تشغيل الموقع الجغرافي ليتمكن تطبيق COVIDSafe من العمل. لا يقوم تطبيق COVIDSafe بتتبّع بيانات الموقع الجغرافي أو تخزينها.";
/* Splash Screen */
"migration_in_progress" = "تحديث تطبيق COVIDSafe قيد الإنجاز. \n\nيُرجى التأكد من عدم إغلاق هاتفك إلى أن يتم اكتمال التحديث.";
"national_numbers" = "الأرقام على الصعيد الوطني";
"new_cases" = "الحالات الجديدة في ال 24 ساعة الماضية";
"new_cases_total" = "";
"new_south_wales" = "نيو ساوث ويلز";
/* Figma page Android Screens (05/06) */
"norfolk_hint" = "على سبيل المثال: 51234";
@ -341,11 +351,11 @@
"numbers_no_internet" = "اتصل بالإنترنت لكي تحصل على آخر الأرقام.";
"numbers_refresh" = "قم بالتحديث الآن";
"options_for_australia" = "خيارات لأستراليا";
"overseas_acquired" = "";
"permission_button" = "تابع";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS" = "يحتاج تطبيق COVIDSafe إلى تمكين Bluetooth® لكي يعمل. يسمح تمكين الإشعارات بتلقي تحديثات لتذكيرك عندما يكون تطبيق COVIDSafe متوقف عن العمل. \n\n اختر \"تابع\" للتمكين:";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS_2" = "1. Bluetooth® \n 2. الإشعارات \n\n لا يرسل تطبيق COVIDSafe طلبات الاقتران.";
"permission_content_iOS" = "يحتاج تطبيق COVIDSafe لأن يكون #Bluetooth# مشغلًا لكي يعمل. لا يرسل تطبيق COVIDSafe طلبات الاقتران.\n\nيسمح تمكين #الإشعارات# بتلقي تحديثات لتذكيرك عندما يكون تطبيق COVIDSafe متوقفًا عن العمل. \n\nيتطلب جهاز iphone الخاص بك تشغيل #الموقع الجغرافي# ليتمكن تطبيق COVIDSafe من العمل.\n*لا يقوم تطبيق COVIDSafe بتتبّع بيانات الموقع الجغرافي أو تخزينها*\n\n اختر \"تابع\" لتمكين:";
"permission_content_iOS_2" = "1. Bluetooth \n2. الإشعارات\n3. خدمات الموقع الجغرافي";
/* OnBoarding Permission */
"permission_headline" = "إعدادات التطبيق";
"permission_success_button" = "تمّ";
@ -391,12 +401,15 @@
"share_this_app_content" = "إنضم لي لنعمل على وقف انتشار COVID-19! قم بتنزيل COVIDSafe، وهو تطبيق من الحكومة الأسترالية. # COVID19 #coronavirusaustralia #stayhomesavelives https://covidsafe.gov.au";
"show" = "اظهار";
"south_australia" = "ولاية جنوب أستراليا";
"state_number_heading" = "";
"stepCounter" = "الخطوة %d من %d";
"support" = "الدعم";
"support_content" = "للحصول على مساعدة بشأن المشكلات التي لا تغطيها مواضيع المساعدة";
"Support_VOLabel" = "الدعم. للحصول على مساعدة بشأن المشكلات التي لا تغطيها مواضيع المساعدة";
"tasmania" = "ولاية تسمانيا";
"total" = "";
"total_confirmed_cases" = "مجموع الحالات المؤكدة";
"total_deaths" = "";
"total_per_state" = "مجموع الحالات المؤكدة في كل ولاية وإقليم";
"UILaunchStoryboardName" = "LaunchScreen_ar";
"under_sixteen_content" = "أؤكد موافقة أحد والدي أو الوصي علي على أن تعمل وكالة التحول الرقمي كمدير لتخزين البيانات، بموجب قرار قانوني من قبل أمين وزارة الصحة في الحكومة الأسترالية لجمع:";
@ -412,10 +425,15 @@
"update_available_dismiss_btn" = "ذكّرني لاحقًا";
"update_available_message_ios" = "لقد أجرينا تحسينات على تطبيق COVIDSafe. قم بتحديث التطبيق عبر متجر التطبيقات App Store.";
"update_available_title" = "التحديث متوفر!";
"update_description" = "";
"update_description_VO" = "";
"update_heading" = "تمكين خدمات الموقع الجغرافي";
"update_modal_button" = "تابع";
"upload_answer_no" = "لا";
/* Upload flow */
"upload_answer_yes" = "نعم";
"upload_consent_button" = "أوافق";
"upload_fail_heading" = "لم ينجح التحميل";
"upload_failed" = "فشل التحميل";
"upload_finished_header" = "شكراً لك على المساعدة في وقف انتشار COVID-19!";
"upload_finished_sub_header" = "لقد قمت بتحميل معلوماتك بنجاح إلى نظام تخزين COVIDSafe العالي الأمان. \n\n سيقوم موظفو دائرة الصحة في الولاية أو الإقليم بإخطار مستخدمي تطبيق COVIDSafe الآخرين الذين سجلوا حالات اختلاط عن قرب معك. ستظل هويتك مجهولة للمستخدمين الآخرين.";
@ -501,7 +519,7 @@
"NotificationsDisabled_VOLabel" = "الإشعارات معطّلة. لن تتلقى إشعارًا إذا كان تطبيق COVIDSafe لا يعمل.\nقم بتغيير إعدادات الإشعارات";
"NotificationsEnabled_VOLabel" = "يُسمح بالإشعارات. سوف تتلقى إشعارًا إذا كان تطبيق COVIDSafe لا يعمل.\nقم بتغيير إعدادات الإشعارات";
"OS1b_TopParagraph_VOLabel" = "تُستخدم إشارات Bluetooth لتحديد وجودك بالقرب من مستخدم آخر لتطبيق COVIDSafe. \n سيتم تدوين ملاحظة في كل مرة يحصل فيها اختلاط عن قرب بينك وبين مستخدمي تطبيق COVIDSafe الآخرين لجمع معلومات عن اختلاط الأشخاص. يتم تشفير هذه المعلومات وتخزينها فقط في هاتفك. \n إذا جاءت نتيجة فحصك ﻟ COVID-19 إيجابية كمستخدم لتطبيق COVIDSafe، سوف يتصل بك أحد موظفي دائرة الصحة في الولاية أو الإقليم ويساعدك في القيام بتحميل اختياري لمعلومات الاتصال بك إلى نظام تخزين معلومات آمن للغاية \nيمكن أيضًا لموظفي دائرة الصحة في الولاية أو الإقليم الاتصال بك إذا حصل اتصال وثيق بينك وبين مستخدم آخر لتطبيق COVIDSafe أظهرت نتائج الفحص أنه مصاب بالفيروس. \n لمزيد من المعلومات، يرجى الرجوع إلى صفحة مواضيع المساعدة.";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth \n 2. الإشعارات \n\n لا يرسل تطبيق COVIDSafe طلبات الاقتران.";
"permission_content_iOS_VOLabel" = "يحتاج تطبيق COVIDSafe إلى تمكين Bluetooth لكي يعمل. يسمح تمكين الإشعارات بتلقي تحديثات لتذكيرك عندما يكون تطبيق COVIDSafe متوقف عن العمل. \n\n اختر \"تابع\" للتمكين:";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth \n2. الإشعارات\n3. خدمات الموقع الجغرافي";
"permission_content_iOS_VOLabel" = "يحتاج تطبيق COVIDSafe لأن يكون Bluetooth مشغلًا لكي يعمل. لا يرسل تطبيق COVIDSafe طلبات الاقتران.\n\nيسمح تمكين الإشعارات بتلقي تحديثات لتذكيرك عندما يكون تطبيق COVIDSafe متوقفًا عن العمل. \n\nيتطلب جهاز iphone الخاص بك تشغيل الموقع الجغرافي ليتمكن تطبيق COVIDSafe من العمل.\n*لا يقوم تطبيق COVIDSafe* بتتبّع بيانات الموقع الجغرافي أو تخزينها*\n\n اختر \"تابع\" لتمكين:";
"ShareCovidSafe_VOLabel" = "شارك COVIDSafe. قم بدعوة الآخرين للمساعدة. معاً، نحن أقوى.";
"UploadData_VOLabel" = "هل اتصل بك أحد موظفي دائرة الصحة؟ يمكنك تحميل معلوماتك فقط إذا كانت نتيجة فحصك إيجابية.";

View file

@ -1,8 +1,11 @@
"28_days" = "";
"7_days" = "";
"action_continue" = "Συνεχίστε";
"action_report_an_issue" = "Αναφέρετε το πρόβλημα";
"action_upload_done" = "Συνεχίστε";
"action_verify_invalid_pin" = "Μη έγκυρο PIN, ζητήστε από τον υπεύθυνο υγείας να σας στείλει καινούριο PIN.";
"action_verify_upload_pin" = "Ανεβάστε τα στοιχεία μου";
"active_cases" = "";
/* Figma page iOS Screens (05/06) */
"allow_bluetooth_call" = "Επιτρέψτε την COVIDSafe να έχει πρόσβαση στο Bluetooth®";
/* Figma page iOS Screens (05/06) */
@ -16,6 +19,8 @@
"BluetoothOFF_content" = "Ενεργοποιήστε το Bluetooth® του τηλεφώνου σας. Επιλέξτε «να επιτρέπονται Νέες Συνδέσεις» εάν εμφανιστεί.";
/* Figma page iOS Screens (05/06) */
"BluetoothON" = "Bluetooth®: ΕΝΕΡΓΟ";
"cases_28_days" = "";
"cases_7_days" = "";
"change_language" = "Αλλάξτε γλώσσα";
"change_language_content" = "Διαβάστε τον οδηγό για τη χρήση του COVIDSafe σε διαφορετική γλώσσα.";
"collection_message" = "Η COVIDSafe συλλέγει τώρα διαγνωστικές πληροφορίες για να σας βοηθήσει να αντιμετωπίσετε προβλήματα με την εφαρμογή σας. *Μάθετε περισσότερα*";
@ -239,7 +244,7 @@
/* OnBoarding Data Privacy */
"data_privacy_headline" = "Εγγραφή και προστασία του απορρήτου";
"deaths" = "Θάνατοι";
"dialog_error_uploading_message" = "Παρουσιάστηκε πρόβλημα κατά την ανάρτηση των στοιχείων σας. Δοκιμάστε ξανά.";
"dialog_error_uploading_message" = "Προσπαθήστε να ανεβάσετε ξανά τα δεδομένα σας. \n\n Ανατρέξτε σε αυτόν τον κωδικό σφάλματος εάν σας το ζητήσει υπάλληλος υγείας της Πολιτείας ή της Επικρατείας σας: \n \n%@";
"dialog_error_uploading_negative" = "Ακύρωση";
"dialog_error_uploading_positive" = "Προσπαθήστε ξανά";
"dialog_uploading_message" = "Τα στοιχεία σας αναρτίζονται στην COVIDSafe αυτή τη στιγμή. \n\nΜην κλείσετε την εφαρμογή.";
@ -293,6 +298,7 @@
"home_set_complete_external_link_share_title" = "Μοιραστείτε την COVIDSafe";
"home_setup_help" = "Βοήθεια";
"home_version_number_ios" = "Έκδοση %@, Κατασκευή %@";
"hotspots_state_territory" = "";
"how_it_works_button" = "Επόμενο";
"how_it_works_content" = "Τα σήματα Bluetooth® χρησιμοποιούνται για να προσδιορίσουν πότε βρίσκεστε κοντά σε άλλο χρήστη της COVIDSafe.\n\nΗ κάθε στενή επαφή σας με άλλους χρήστες της COVIDSafe σημειώνεται για να δημιουργεί στενά στοιχεία επικοινωνίας. Οι πληροφορίες είναι κρυπτογραφημένες και αποθηκεύονται μόνο στο τηλέφωνό σας.\n\nΕάν διαγνωστείτε θετικά στην COVID-19 ως χρήστης της COVIDSafe, υπεύθυνος υγείας της Πολιτείας ή της Επικράτειάς σας θα επικοινωνήσει μαζί σας. Θα βοηθήσει με την εθελοντική ανάρτηση των προσωπικών στοιχείων επικοινωνίας σας σε ένα εξαιρετικά ασφαλές σύστημα αποθήκευσης πληροφοριών. \n\nΟι υπεύθυνοι υγείας της Πολιτείας ή της Επικράτειάς σας μπορούν επίσης να επικοινωνήσουν μαζί σας εάν ήρθατε σε στενή επαφή με άλλο χρήστη της COVIDSafe που επίσης διαγνώσθηκε θετικά.\n\nΓια περισσότερες πληροφορίες, πηγαίνετε στη σελίδα *Θέματα Βοήθειας*.";
/* OnBoarding How it works */
@ -315,12 +321,16 @@
"invalid_norfolk_island_phone_number_error_prompt" = "Οι αριθμοί κινητής τηλεφωνίας στο νησί Νόρφολκ περιέχουν 5 έως 6 ψηφία.";
"jwt_description" = "Υπάρχει ένα πρόβλημα με τα στοιχεία εγγραφής σας.";
"jwt_heading" = "Παρακαλούμε εγγραφείτε ξανά";
"jwt_success" = "";
"jwt_success" = "Η εγγραφή ανανεώθηκε με επιτυχία";
"loading_numbers" = "Φόρτωση τελευταίων αριθμών";
"locally_acquired" = "";
"location_off" = "Τοποθεσία: ΑΠΕΝΕΡΓΟΠΟΙΗΜΕΝΗ";
"location_off_description" = "Το iPhone σας απαιτεί άδεια τοποθεσίας για να λειτουργήσει την COVIDSafe. Η COVIDSafe ΔΕΝ παρακολουθεί ούτε αποθηκεύει τα δεδομένα τοποθεσίας σας.";
/* Splash Screen */
"migration_in_progress" = "Η ενημέρωση της COVIDSafe συνεχίζεται. \n\nΒεβαιωθείτε ότι το τηλέφωνό σας δεν είναι απενεργοποιημένο έως ότου ολοκληρωθεί η ενημέρωση.";
"national_numbers" = "Στατιστικές για όλη την Αυστραλία";
"new_cases" = "Νέα κρούσματα κατά τις τελευταίες 24 ώρες";
"new_cases_total" = "";
"new_south_wales" = "Νέα Νότια Ουαλία";
/* Figma page Android Screens (05/06) */
"norfolk_hint" = "Για παράδειγμα: 51234";
@ -341,11 +351,11 @@
"numbers_no_internet" = "Συνδεθείτε στο Διαδίκτυο για να βεβαιωθείτε ότι λαμβάνετε ενημερωμένους αριθμούς.";
"numbers_refresh" = "Ανανεώστε τώρα";
"options_for_australia" = "Επιλογές για την Αυστραλία";
"overseas_acquired" = "";
"permission_button" = "Προχωρήστε";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS" = "Για να λειτουργήσει η COVIDSafe θα πρέπει το Bluetooth® να είναι ενεργοποιημένο. Όταν ενεργοποιήσετε τις Ειδοποιήσεις, θα λάβετε τις ενημερώσεις που υπενθυμίζουν πότε η COVIDSafe δεν είναι ενεργή. \n\n Επιλέξτε «Προχωρήστε» για ενεργοποίηση:";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS_2" = "1. Bluetooth® \n2. Ειδοποιήσεις \n\n Η COVIDSafe δεν αποστέλλει αιτήματα σύζευξης.";
"permission_content_iOS" = "Η COVIDSafe χρειάζεται # Bluetooth # για να λειτουργεί. Η COVIDSafe δεν αποστέλλει αιτήματα σύζευξης. \n\nΕνεργοποιώντας τις # Ειδοποιήσεις #, λαμβάνετε ενημερώσεις για να σας υπενθυμίσουν όταν η COVIDSafe δεν είναι ενεργή. \n\nΤο iPhone σας απαιτεί # άδεια τοποθεσίας # για να λειτουργεί η COVIDSafe. * Η COVIDSafe ΔΕΝ παρακολουθεί ούτε αποθηκεύει τα δεδομένα τοποθεσίας σας * \n\nΕπιλέξτε «Συνέχεια» για να ενεργοποιήσετε:";
"permission_content_iOS_2" = "1. Bluetooth\n 2. Ειδοποιήσεις\n 3. Υπηρεσίες Τοποθεσίας";
/* OnBoarding Permission */
"permission_headline" = "Ρυθμίσεις εφαρμογής";
"permission_success_button" = "Ολοκληρώθηκε";
@ -391,12 +401,15 @@
"share_this_app_content" = "Ελάτε μαζί μου να σταματήσουμε την εξάπλωση του COVID-19! Κατεβάστε την COVIDSafe, μια εφαρμογή της Αυστραλιανής Κυβέρνησης. # COVID19 #coronavirusaustralia #stayhomesavelives https://covidsafe.gov.au";
"show" = "Εμφάνιση";
"south_australia" = "Νότια Αυστραλία";
"state_number_heading" = "";
"stepCounter" = "Βήμα %d του %d";
"support" = "Υποστήριξη";
"support_content" = "Για βοήθεια σε θέματα που δεν καλύπτονται από θέματα Βοήθειας";
"Support_VOLabel" = "Υποστήριξη. Για βοήθεια σε θέματα που δεν καλύπτονται από θέματα Βοήθειας";
"tasmania" = "Τασμανία";
"total" = "";
"total_confirmed_cases" = "Σύνολο επιβεβαιωμένων κρουσμάτων";
"total_deaths" = "";
"total_per_state" = "Σύνολο επιβεβαιωμένων κρουσμάτων ανά πολιτεία και επικράτεια";
"UILaunchStoryboardName" = "LaunchScreen_el_GR";
"under_sixteen_content" = "Επιβεβαιώνω ότι ο γονέας ή ο κηδεμόνας μου συναινεί στον Οργανισμό Ψηφιακού Μετασχηματισμού ως διαχειριστή του χώρου αποθήκευσης δεδομένων, σύμφωνα με νόμιμο καθορισμό του Γενικού Γραμματέα του Υπουργείου Υγείας της Αυστραλιανής Κυβέρνησης που συλλέγει:";
@ -412,10 +425,15 @@
"update_available_dismiss_btn" = "Θύμισέ μου αργότερα";
"update_available_message_ios" = "Πραγματοποιήσαμε βελτιώσεις στην COVIDSafe. Ενημερώστε την εφαρμογή μέσῳ του App Store.";
"update_available_title" = "Η ενημερωμένη έκδοση είναι διαθέσιμη!";
"update_description" = "";
"update_description_VO" = "";
"update_heading" = "Ενεργοποιήστε τις Υπηρεσίες Τοποθεσίας";
"update_modal_button" = "Προχωρήστε";
"upload_answer_no" = "Όχι";
/* Upload flow */
"upload_answer_yes" = "Ναι";
"upload_consent_button" = "Συμφωνώ";
"upload_fail_heading" = "Η ανάρτησή σας απέτυχε";
"upload_failed" = "Η ανάρτηση απέτυχε";
"upload_finished_header" = "Σας ευχαριστούμε που βοηθήσατε να σταματήσει η εξάπλωση του COVID-19!";
"upload_finished_sub_header" = "Έχετε ανεβάσει με επιτυχία τις πληροφορίες σας στο σύστημα αποθήκευσης υψηλής ασφάλειας COVIDSafe.\n\nΟι υγειονομικοί υπάλληλοι της Πολιτείας ή της Επικράτειάς σας θα ενημερώσουν άλλους χρήστες της COVIDSafe που έχουν καταγράψει στενή επαφή μαζί σας. Η ταυτότητά σας θα παραμείνει ανώνυμη σε άλλους χρήστες.";
@ -501,7 +519,7 @@
"NotificationsDisabled_VOLabel" = "Οι ειδοποιήσεις είναι απενεργοποιημένες. Δε θα λάβετε ειδοποίηση εάν η COVIDSafe δεν είναι ενεργή. \nΑλλάξετε τις ρυθμίσεις ειδοποίησης";
"NotificationsEnabled_VOLabel" = "Οι ειδοποιήσεις είναι ενεργοποιημένες. Θα λάβετε ειδοποίηση εάν η COVIDSafe δεν είναι ενεργή. \nΑλλαγή ρυθμίσεων ειδοποίησης";
"OS1b_TopParagraph_VOLabel" = "Τα σήματα Bluetooth χρησιμοποιούνται για να προσδιορίσουν πότε βρίσκεστε κοντά σε άλλο χρήστη της COVIDSafe. \nΚάθε στενή επαφή σας με άλλους χρήστες της COVIDSafe σημειώνεται για να δημιουργεί στενά στοιχεία επικοινωνίας. Οι πληροφορίες είναι κρυπτογραφημένες και αποθηκεύονται μόνο στο τηλέφωνό σας. \nΕάν έχετε διαγνωστεί θετικά στον COVID-19 κατόπιν χρήσης της COVIDSafe, ένας υπεύθυνος υγείας της Πολιτείας ή της Επικράτειάς σας θα επικοινωνήσει μαζί σας. Θα βοηθήσει με την εθελοντική ανάρτηση των προσωπικών στοιχείων επικοινωνίας σας σε ένα εξαιρετικά ασφαλές σύστημα αποθήκευσης πληροφοριών. \nΟι υπεύθυνοι υγείας της Πολιτείας ή της Επικράτειάς σας μπορούν επίσης να επικοινωνήσουν μαζί σας εάν ήρθατε σε στενή επαφή με άλλο χρήστη της COVIDSafe που επίσης διαγνώστηκε θετικά. \nΓια περισσότερες πληροφορίες, πηγαίνετε στη σελίδα Θέματα Βοήθειας.";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth \n2. Ειδοποιήσεις \n\n Η COVIDSafe δεν αποστέλλει αιτήματα σύζευξης.";
"permission_content_iOS_VOLabel" = "Η COVIDSafe για να λειτουργήσει θα πρέπει το Bluetooth να είναι ενεργοποιημένο. Όταν ενεργοποιήσετε τις Ειδοποιήσεις, θα λάβετε τις ενημερώσεις που υπενθυμίζουν πότε η COVIDSafe δεν είναι ενεργή. \n\n Επιλέξτε «Συνέχεια» για ενεργοποίηση:";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth\n 2. Ειδοποιήσεις\n 3. Υπηρεσίες Τοποθεσίας";
"permission_content_iOS_VOLabel" = "Η COVIDSafe χρειάζεται Bluetooth για να λειτουργεί. Η COVIDSafe δεν αποστέλλει αιτήματα σύζευξης. \n\n Ενεργοποιώντας τις Ειδοποιήσεις, λαμβάνετε ενημερώσεις που σας υπενθυμίζουν πότε η COVIDSafe δεν είναι ενεργή. \n\nΤο iPhone σας απαιτεί άδεια τοποθεσίας για να λειτουργεί το COVIDSafe. * Η COVIDSafe ΔΕΝ παρακολουθεί ούτε αποθηκεύει τα δεδομένα τοποθεσίας σας * \n\n Επιλέξτε \"Συνέχεια\" για να ενεργοποιήσετε:";
"ShareCovidSafe_VOLabel" = "Μοιραστείτε την COVIDSafe. Καλέστε και άλλους να βοηθήσουν. Μαζί, είμαστε ισχυρότεροι.";
"UploadData_VOLabel" = "Έχει επικοινωνήσει μαζί σας υπάλληλος υγείας; Μπορείτε να αναρτήσετε τις πληροφορίες σας μόνο αν έχετε διαγνωστεί θετικά.";

View file

@ -5,4 +5,7 @@
"BluetoothUsageDesc" = "COVIDSafe exchanges Bluetooth® signals with nearby phones running the same app. These signals contain an anonymised ID, which is encrypted and changes continually to ensure your privacy.";
"NSBluetoothAlwaysUsageDescription" = "COVIDSafe exchanges Bluetooth® signals with nearby phones running the same app. These signals contain an anonymised ID, which is encrypted and changes continually to ensure your privacy.";
"NSBluetoothPeripheralUsageDescription" = "COVIDSafe exchanges Bluetooth® signals with nearby phones running the same app. These signals contain an anonymised ID, which is encrypted and changes continually to ensure your privacy.";
"NSLocationAlwaysAndWhenInUseUsageDescription" = "Select Always Allow.\n\nYour location is used to send you relevant COVID-19 alerts and to optimise COVIDSafe performance.\nThese alerts will contain important information and updates for your state or territory.";
"NSLocationAlwaysUsageDescription" = "Select Always Allow.\n\nYour location is used to send you relevant COVID-19 alerts and to optimise COVIDSafe performance.\nThese alerts will contain important information and updates for your state or territory.";
"NSLocationWhenInUseUsageDescription" = "Select While Using App.\n\nYour location is used to send you relevant COVID-19 alerts and to optimise COVIDSafe performance.\nThese alerts will contain important information and updates for your state or territory.";
"UILaunchStoryboardName" = "LaunchScreen_en";

View file

@ -1,8 +1,11 @@
"28_days" = "28 days";
"7_days" = "7 days";
"action_continue" = "Continue";
"action_report_an_issue" = "Report an issue";
"action_upload_done" = "Done";
"action_verify_invalid_pin" = "Invalid PIN, please ask the health official to send you another PIN.";
"action_verify_upload_pin" = "Upload my information";
"active_cases" = "Active cases (estimated)";
/* Figma page iOS Screens (05/06) */
"allow_bluetooth_call" = "Allow COVIDSafe to access Bluetooth®";
/* Figma page iOS Screens (05/06) */
@ -16,6 +19,8 @@
"BluetoothOFF_content" = "Turn on your phones Bluetooth®. Select Allow New Connections if displayed.";
/* Figma page iOS Screens (05/06) */
"BluetoothON" = "Bluetooth®: ON";
"cases_28_days" = "New cases in the last 28 days";
"cases_7_days" = "New cases in the last 7 days";
"change_language" = "Change language";
"change_language_content" = "Read the guide on using COVIDSafe in a different language.";
"collection_message" = "COVIDSafe now collects diagnostic information to help you troubleshoot issues with your App. *Learn more*";
@ -239,7 +244,7 @@
/* OnBoarding Data Privacy */
"data_privacy_headline" = "Registration and privacy";
"deaths" = "Deaths";
"dialog_error_uploading_message" = "An error occurred while uploading your information, please try again.";
"dialog_error_uploading_message" = "Try uploading your data again.\n\nRefer to this error code if a state or territory health official asks for it: \n%@";
"dialog_error_uploading_negative" = "Cancel";
"dialog_error_uploading_positive" = "Try again";
"dialog_uploading_message" = "Your COVIDSafe information is currently being uploaded.\n\nPlease do not close the app.";
@ -293,6 +298,7 @@
"home_set_complete_external_link_share_title" = "Share COVIDSafe";
"home_setup_help" = "Help";
"home_version_number_ios" = "Version %@, Build %@";
"hotspots_state_territory" = "Hotspots in %@";
"how_it_works_button" = "Next";
"how_it_works_content" = "Bluetooth® signals are used to determine when you're near another COVIDSafe user.\n\nEvery instance of close contact between you and other COVIDSafe users is noted to create contact data. This information is encrypted and only stored in your phone.\n\nIf you test positive to COVID-19 as a COVIDSafe user, a state or territory health official will contact you. They will assist with voluntary upload of your contact data to a highly secure information storage system.\n\nState or territory health officials can also contact you if you came in close contact with another COVIDSafe user who tested positive.\n\nFor more information please refer to the *Help Topics* page.";
/* OnBoarding How it works */
@ -317,10 +323,14 @@
"jwt_heading" = "Please register again";
"jwt_success" = "Registration successfully renewed";
"loading_numbers" = "Loading latest numbers";
"locally_acquired" = "%@ locally acquired";
"location_off" = "Location: OFF";
"location_off_description" = "Your iPhone requires Location permission for COVIDSafe to work. COVIDSafe does NOT track or store your location data.";
/* Splash Screen */
"migration_in_progress" = " COVIDSafe update in progress. \n\n Please make sure you phone is not switched off until the update is complete.";
"national_numbers" = "National numbers";
"new_cases" = "New cases in the last 24 hours";
"new_cases_total" = "New cases since 1 Feb 2020";
"new_south_wales" = "New South Wales";
/* Figma page Android Screens (05/06) */
"norfolk_hint" = "For example: 51234";
@ -341,11 +351,11 @@
"numbers_no_internet" = "Connect to the internet to ensure you get updated numbers.";
"numbers_refresh" = "Refresh now";
"options_for_australia" = "Options for Australia";
"overseas_acquired" = "%@ acquired overseas";
"permission_button" = "Proceed";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS" = "COVIDSafe needs Bluetooth® enabled to work. By enabling Notifications, you get updates to remind you when COVIDSafe is not active. \n\nSelect Proceed' to enable:";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS_2" = "1. Bluetooth®\n2. Notifications\n\nCOVIDSafe does not send pairing requests.";
"permission_content_iOS" = "COVIDSafe needs #Bluetooth# enabled to work. COVIDSafe does not send pairing requests.\n\nBy enabling #Notifications#, you get updates to remind you when COVIDSafe is not active. \n\nYour iPhone requires #Location permission# for COVIDSafe to work. *COVIDSafe does NOT track or store your location data*\n\nSelect Proceed' to enable:";
"permission_content_iOS_2" = "1. Bluetooth\n2. Notifications\n3. Location Services";
/* OnBoarding Permission */
"permission_headline" = "App settings";
"permission_success_button" = "Done";
@ -391,12 +401,15 @@
"share_this_app_content" = "Join me in stopping the spread of COVID-19! Download COVIDSafe, an app from the Australian Government. #COVID19 #coronavirusaustralia #stayhomesavelives https://covidsafe.gov.au";
"show" = "Show";
"south_australia" = "South Australia";
"state_number_heading" = "%@ numbers";
"stepCounter" = "Step %d of %d";
"support" = "Support";
"support_content" = "For assistance on issues not covered by Help topics";
"Support_VOLabel" = "Support. For assistance on issues not covered by Help topics";
"tasmania" = "Tasmania";
"total" = "Total";
"total_confirmed_cases" = "Total confirmed cases";
"total_deaths" = "%@ total deaths";
"total_per_state" = "Total confirmed cases by state and territory";
"UILaunchStoryboardName" = "LaunchScreen_en";
"under_sixteen_content" = "I confirm my parent or guardian consents to the Digital Transformation Agency as data store administrator, under legal determination made by the Secretary of the Australian Government Department of Health collecting:";
@ -412,10 +425,15 @@
"update_available_dismiss_btn" = "Remind me later";
"update_available_message_ios" = "Weve been making improvements to COVIDSafe. Update through the App Store";
"update_available_title" = "Update available!";
"update_description" = "This update improves COVIDSafes Bluetooth performance to better capture your close contacts.\n\nThis update requests access to your #Location Services# for COVIDSafe to work. \n\nYour location is used to send you relevant COVID-19 alerts and to optimise COVIDSafe performance.\nThese alerts will contain important information and updates for your state or territory.\n\n<a href=\"https://www.covidsafe.gov.au/help-topics.html#herald\">Find out more about how weve improved COVIDSafe.</a>";
"update_description_VO" = "This update improves COVIDSafes Bluetooth performance to better capture your close contacts.\n\nThis update requests access to your #Location Services# for COVIDSafe to work. \n\nYour location is used to send you relevant COVID-19 alerts and to optimise COVIDSafe performance.\nThese alerts will contain important information and updates for your state or territory.\n\nFind out more about how weve improved COVIDSafe.";
"update_heading" = "Enable Location Services";
"update_modal_button" = "Proceed";
"upload_answer_no" = "No";
/* Upload flow */
"upload_answer_yes" = "Yes";
"upload_consent_button" = "I agree";
"upload_fail_heading" = "Your upload failed";
"upload_failed" = "Upload failed";
"upload_finished_header" = "Thank you for helping to stop the spread of COVID-19!";
"upload_finished_sub_header" = "You have successfully uploaded your information to the COVIDSafe highly secure storage system.\n\nState or territory health officials will notify other COVIDSafe users that have recorded instances of close contact with you. Your identity will remain anonymous to other users.";
@ -501,7 +519,7 @@
"NotificationsDisabled_VOLabel" = "Notifications are disabled. You will not receive a notification if COVIDSafe is not active.\nChange notification settings";
"NotificationsEnabled_VOLabel" = "Notifications are enabled. You will receive a notification if COVIDSafe is not active.\nChange notification settings";
"OS1b_TopParagraph_VOLabel" = "Bluetooth signals are used to determine when you're near another COVIDSafe user.\nEvery instance of close contact between you and other COVIDSafe users is noted to create close contact information. This information is encrypted and only stored in your phone.\nIf you test positive to COVID-19 as a COVIDSafe user, a state or territory health official will contact you. They will assist with voluntary upload of your close contact information to a highly secure information storage system.\nState or territory health officials can also contact you if you came in close contact with another COVIDSafe user who tested positive.\nFor more information please refer to the Help Topics page.";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth\n2. Notifications\n\nCOVIDSafe does not send pairing requests.";
"permission_content_iOS_VOLabel" = "COVIDSafe needs Bluetooth enabled to work. By enabling Notifications, you get updates to remind you when COVIDSafe is not active. \n\nSelect 'Proceed' to enable:";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth\n2. Notifications\n3. Location Services";
"permission_content_iOS_VOLabel" = "COVIDSafe needs Bluetooth enabled to work. COVIDSafe does not send pairing requests.\n\nBy enabling Notifications, you get updates to remind you when COVIDSafe is not active. \n\nYour iPhone requires Location permission for COVIDSafe to work. *COVIDSafe does NOT track or store your location data*\n\nSelect Proceed' to enable:";
"ShareCovidSafe_VOLabel" = "Share COVIDSafe. Invite others to help. Together, we're stronger.";
"UploadData_VOLabel" = "Has a health official contacted you? You can only upload your information if you have tested positive.";

View file

@ -1,8 +1,11 @@
"28_days" = "";
"7_days" = "";
"action_continue" = "Continua";
"action_report_an_issue" = "Segnala un problema";
"action_upload_done" = "Eseguito";
"action_verify_invalid_pin" = "PIN non valido, chiedi al funzionario sanitario d'inviarti un altro PIN.";
"action_verify_upload_pin" = "Carica i miei dati";
"active_cases" = "";
/* Figma page iOS Screens (05/06) */
"allow_bluetooth_call" = "Consenti al COVIDSafe di accedere al Bluetooth®";
/* Figma page iOS Screens (05/06) */
@ -16,6 +19,8 @@
"BluetoothOFF_content" = "Attiva il Bluetooth® del tuo cellulare. Seleziona \"Consenti nuove connessioni\" se visualizzato.";
/* Figma page iOS Screens (05/06) */
"BluetoothON" = "Bluetooth®: ON";
"cases_28_days" = "";
"cases_7_days" = "";
"change_language" = "Cambia lingua";
"change_language_content" = "Leggi la guida sull'uso di COVIDSafe in un'altra lingua.";
"collection_message" = "COVIDSafe raccoglie ora informazioni diagnostiche per aiutarti a risolvere i problemi con l'App. *Per saperne di più*";
@ -234,12 +239,12 @@
"country_region_name_zm" = "Zambia";
"country_region_name_zw" = "Zimbabwe";
"data_privacy_button" = "Avanti";
"data_privacy_content" = "Leggere *l'informativa sulla privacy* prima di eseguire la registrazione a COVIDSafe. \n\n*L'informativa sulla privacy* deve essere letta anche dai genitori/chi ne fa le veci dei minori di 16 anni. \n\nL'uso di COVIDSafe è volontario. È possibile installare o eliminare l'applicazione in qualsiasi momento. Se si elimina COVIDSafe, si può anche richiedere la cancellazione delle informazioni dal server protetto \n\nPer registrarsi a COVIDSafe, inserire nome (o pseudonimo), numero di cellulare, fascia di età e codice postale. \n\nLe informazioni inviate al momento della registrazione e quelle sull'utilizzo di COVIDSafe vengono raccolte e archiviate su un server altamente protetto. \n\nLe informazioni vengono raccolte anche per assicurarsi che COVIDSafe stia funzionando sul tuo cellulare. Le informazioni riguardano il sistema operativo, la versione App istallata, la lingua selezionata, il funzionamento di Bluetooth e se sono stati registrati dei contatti negli ultimi sette giorni.\n\nCOVIDSafe non raccoglie informazioni sulla vostra posizione. \n\nCOVIDSafe annota sul tuo dispositivo il giorno e l'ora del contatto, il codice ID anonimo degli altri utenti di COVIDSafe con cui sei entrato/a in contatto, il loro modello di cellulare e l'intensità del segnale Bluetooth. \n\nGli utenti di COVIDSafe con cui si entra in contatto vedranno sul loro dispositivo il tuo codice ID anonimo, la data e l'ora del contatto, l'intensità del segnale Bluetooth e il modello del tuo cellulare. \n\nSe un utente che risulta positivo al COVID-19 carica i propri dati di contatto, potresti essere contattato/a da un funzionario sanitario dello stato o territorio australiano interessato per ragioni di tracciabilità dei contatti. \n\nI tuoi dati di registrazione verranno utilizzati o divulgati esclusivamente per ragioni di tracciabilità dei contatti e per il corretto e legittimo funzionamento di COVIDSafe. \n\nMaggiori informazioni sono disponibili sul sito covidsafe.gov.au.\n\nPer ulteriori informazioni sui diritti dell'utente e la gestione e condivisione dei dati consultare *l'informativa sulla privacy* di COVIDSafe .";
"data_privacy_content_VO" = "Leggere *l'informativa sulla privacy* prima di eseguire la registrazione a COVIDSafe. \n\n*L'informativa sulla privacy* deve essere letta anche dai genitori/chi ne fa le veci dei minori di 16 anni. \n\nL'uso di COVIDSafe è volontario. È possibile installare o eliminare l'applicazione in qualsiasi momento. Se si elimina COVIDSafe, si può anche richiedere la cancellazione delle informazioni dal server protetto \n\nPer registrarsi a COVIDSafe, inserire nome (o pseudonimo), numero di cellulare, fascia di età e codice postale. \n\nLe informazioni inviate al momento della registrazione e quelle sull'utilizzo di COVIDSafe vengono raccolte e archiviate su un server altamente protetto. \n\nLe informazioni vengono raccolte anche per assicurarsi che COVIDSafe stia funzionando sul tuo cellulare. Le informazioni riguardano il sistema operativo, la versione App installata, la lingua selezionata, il funzionamento di Bluetooth e se sono stati registrati dei contatti negli ultimi sette giorni.\n\nCOVIDSafe non raccoglie informazioni sulla tua posizione. \n\nCOVIDSafe annota sul tuo dispositivo il giorno e l'ora del contatto, il codice ID anonimo degli altri utenti di COVIDSafe con cui sei entrato/a in contatto, il loro modello di cellulare e l'intensità del segnale Bluetooth. \n\nGli utenti di COVIDSafe con cui sei entrato/a in contatto vedranno sul loro dispositivo il tuo codice ID anonimo, la data e l'ora del contatto, l'intensità del segnale Bluetooth e il modello del tuo cellulare. \n\nSe un utente che risulta positivo al COVID-19 carica i propri dati di contatto, potresti essere contattato/a da un funzionario sanitario dello stato o territorio australiano interessato per ragioni di tracciabilità dei contatti. \n\nI tuoi dati di registrazione verranno utilizzati o divulgati esclusivamente per ragioni di tracciabilità dei contatti e per il corretto e legittimo funzionamento di COVIDSafe. \n\nUlteriori informazioni sono disponibili sul sito covidsafe.gov.au.\n\nPer ulteriori informazioni sui diritti dell'utente e la gestione e condivisione dei dati consultare *l'informativa sulla privacy* di COVIDSafe.";
"data_privacy_content" = "È importante leggere l'informativa sulla privacy * COVIDSafe * prima di registrarti a COVIDSafe.\n\nSe hai meno di 16 anni, il tuo genitore / tutore deve anche leggere l'\"informativa sulla privacy*.\n\nL'uso di COVIDSafe è completamente volontario. È possibile installare o eliminare l'applicazione in qualsiasi momento. Se elimini COVIDSafe, *potresti anche chiedere che le tue informazioni* siano eliminate dal server sicuro.\n\nPer registrarti a COVIDSafe, dovrai inserire un nome (o pseudonimo), un numero di cellulare, una fascia d'età e un codice postale.\n\nLe informazioni inviate al momento della registrazione e le informazioni sull'utilizzo di COVIDSafe vengono archiviate su un server altamente sicuro.\n\nVengono inoltre raccolte informazioni per assicurarsi che COVIDSafe funzioni correttamente sul dispositivo. Ciò include il tuo sistema operativo, la versione dell'App che hai installato, la lingua su cui è impostato il tuo dispositivo, se Bluetooth® funziona e se la tua App ha registrato incontri nei sette giorni precedenti.\n\nCOVIDSafe non raccoglierà le informazioni sulla tua posizione.\n\nCOVIDSafe noterà la data e l'ora del contatto, il codice ID anonimo di altri utenti COVIDSafe con cui si entra in contatto, la potenza del segnale Bluetooth e il modello di telefono di altri utenti, sul dispositivo.\n\nAltri utenti COVIDSafe con cui vieni in contatto noteranno il tuo codice ID anonimo, la data e l'ora di contatto con te, la potenza del segnale Bluetooth e il tuo modello di telefono, sul loro dispositivo.\n\nSe un altro utente è positivo al COVID-19, può caricare le informazioni di contatto e un funzionario sanitario dello stato o del territorio può contattarti a scopo di tracciamento dei contatti.\n\nI tuoi dati di registrazione saranno utilizzati o divulgati solo per il tracciamento dei contatti e per il corretto e legittimo funzionamento di COVIDSafe.\n\nUlteriori informazioni sono disponibili sul sito web *covidsafe.gov.au*.\n\nConsulta l'informativa sulla privacy *COVIDSafe* per ulteriori dettagli sui tuoi diritti sulle tue informazioni e su come verranno gestite e condivise.";
"data_privacy_content_VO" = "Leggi *l'informativa sulla privacy* prima di eseguire la registrazione a COVIDSafe. \n\n*L'informativa sulla privacy* deve essere letta anche dai genitori/chi ne fa le veci dei minori di 16 anni. \n\nL'uso di COVIDSafe è volontario. È possibile installare o eliminare l'applicazione in qualsiasi momento. Se si elimina COVIDSafe, si può anche richiedere la cancellazione delle informazioni dal server protetto \n\nPer registrarti a COVIDSafe, inserisci nome (o pseudonimo), numero di cellulare, fascia di età e codice postale. \n\nLe informazioni inviate al momento della registrazione e quelle sull'utilizzo di COVIDSafe vengono raccolte e archiviate su un server altamente protetto. \n\nLe informazioni vengono raccolte anche per assicurarsi che COVIDSafe stia funzionando sul tuo cellulare. Le informazioni riguardano il sistema operativo, la versione App installata, la lingua selezionata, il funzionamento di Bluetooth e se sono stati registrati dei contatti negli ultimi sette giorni.\n\nCOVIDSafe non raccoglie informazioni sulla tua posizione. \n\nCOVIDSafe annota sul tuo dispositivo il giorno e l'ora del contatto, il codice ID anonimo degli altri utenti di COVIDSafe con cui sei entrato/a in contatto, il loro modello di cellulare e l'intensità del segnale Bluetooth. \n\nGli utenti di COVIDSafe con cui sei entrato/a in contatto vedranno sul loro dispositivo il tuo codice ID anonimo, la data e l'ora del contatto, l'intensità del segnale Bluetooth e il modello del tuo cellulare. \n\nSe un utente che risulta positivo al COVID-19 carica i propri dati di contatto, potresti essere contattato/a da un funzionario sanitario dello stato o territorio australiano interessato per ragioni di tracciabilità dei contatti. \n\nI tuoi dati di registrazione verranno utilizzati o divulgati esclusivamente per ragioni di tracciabilità dei contatti e per il corretto e legittimo funzionamento di COVIDSafe. \n\nUlteriori informazioni sono disponibili sul sito covidsafe.gov.au.\n\nPer ulteriori informazioni sui diritti dell'utente e la gestione e condivisione dei dati consultare *l'informativa sulla privacy* di COVIDSafe.";
/* OnBoarding Data Privacy */
"data_privacy_headline" = "Registrazione e privacy";
"deaths" = "Decessi";
"dialog_error_uploading_message" = "Si è verificato un errore durante il caricamento delle informazioni. Riprova.";
"dialog_error_uploading_message" = "Prova a caricare di nuovo i tuoi dati. \n\nFai riferimento a questo codice di errore se un funzionario sanitario statale o territoriale lo richiede: \n%@";
"dialog_error_uploading_negative" = "Annulla";
"dialog_error_uploading_positive" = "Riprova";
"dialog_uploading_message" = "I dati di COVIDSafe sono attualmente in fase di caricamento. \n\nNon chiudere l'app.";
@ -293,18 +298,19 @@
"home_set_complete_external_link_share_title" = "Condividi COVIDSafe";
"home_setup_help" = "Guida";
"home_version_number_ios" = "Versione %@ , Build %@";
"hotspots_state_territory" = "";
"how_it_works_button" = "Avanti";
"how_it_works_content" = "I segnali Bluetooth vengono utilizzati per determinare la vicinanza ad altri utenti di COVIDSafe. \nOgni istanza di stretto contatto tra voi e gli altri utenti di COVIDSafe viene annotata per creare informazioni sui contatti. Queste informazioni sono crittografate e memorizzate solo sul vostro cellulare. \nSe risultate positivi al COVID-19, come utenti di COVIDSafe sarete contattati da un funzionario sanitario statale o del territorio per assistervi con il caricamento volontario delle vostre informazioni di stretto contatto in un sistema di archiviazione delle informazioni altamente sicuro. \nI funzionari sanitari statali o territoriali possono anche contattarvi se siete entrati in stretto contatto con un altro utente di COVIDSafe risultato positivo. \nPer ulteriori informazioni, consultare la pagina Argomenti della Guida.";
"how_it_works_content" = "I segnali Bluetooth vengono utilizzati per determinare la vicinanza ad altri utenti di COVIDSafe. \nOgni istanza di stretto contatto tra te e gli altri utenti di COVIDSafe viene annotata per creare informazioni sui contatti. Queste informazioni sono crittografate e memorizzate solo sul tuo cellulare. \nSe risulti positivo al COVID-19, come utente di COVIDSafe sarai contattato da un funzionario sanitario statale o del territorio per assisterti con il caricamento volontario delle vostre informazioni di stretto contatto in un sistema di archiviazione delle informazioni altamente sicuro. \nI funzionari sanitari statali o territoriali possono anche contattarti se sei entrato in stretto contatto con un altro utente di COVIDSafe risultato positivo. \nPer ulteriori informazioni, consulta la pagina Argomenti della Guida.";
/* OnBoarding How it works */
"how_it_works_headline" = "Come funziona il COVIDSafe";
"improve" = "Migliora le prestazioni dell'app";
"improve_heading" = "Migliora le prestazioni di COVIDSafe";
"internet_connection_content" = "Connettersi a Internet per ricevere notifiche su problemi e aggiornamenti.";
"internet_connection_content" = "Connettiti a Internet per ricevere notifiche su problemi e aggiornamenti.";
"internet_connection_heading" = "Nessuna connessione internet";
"internet_screen_content" = "COVIDSafe deve connettersi al server periodicamente per identificare eventuali problemi dell'app. \n\n La connessione Internet permette di ricevere notifiche su:";
"internet_screen_content_1" = "come risolvere problemi e riattivare nuovamente COVIDSafe ";
"internet_screen_content_2" = "quando sarà disponibile un nuovo aggiornamento della versione";
"internet_screen_heading" = "Connettersi a Internet per migliorare le prestazioni di COVIDSafe";
"internet_screen_heading" = "Connettiti a Internet per migliorare le prestazioni di COVIDSafe";
"intro_button" = "Voglio aiutare";
"intro_content" = "COVIDSafe è stata sviluppata dal Governo australiano per aiutare a proteggere la comunità dalla diffusione del coronavirus. \n\nCOVIDSafe rileva in modo sicuro i contatti che avete con altri utenti dell'app e consente ai funzionari sanitari statali e del territorio di contattarvi, se siete stati in stretto contatto con qualcuno risultato positivo al virus. \n\nInsieme possiamo aiutare a fermare la diffusione e rimanere sani.";
/* OnBoarding Intro */
@ -314,13 +320,17 @@
"invalid_australian_phone_number_error_prompt" = "I numeri di cellulare australiani contengono un massimo di 10 cifre.";
"invalid_norfolk_island_phone_number_error_prompt" = "I numeri di cellulare nell'Isola di Norfolk contengono da 5 a 6 cifre.";
"jwt_description" = "C'è un problema con i dati di registrazione.";
"jwt_heading" = "Si prega di registrarsi di nuovo";
"jwt_success" = "";
"jwt_heading" = "Registrati di nuovo";
"jwt_success" = "Registrazione rinnovata con successo";
"loading_numbers" = "Caricamento degli ultimi numeri";
"locally_acquired" = "";
"location_off" = "Posizione: OFF";
"location_off_description" = "Il tuo iPhone richiede l'autorizzazione di localizzazione affinché COVIDSafe funzioni. COVIDSafe NON memorizza né utilizza i dati relativi alla tua posizione.";
/* Splash Screen */
"migration_in_progress" = "Aggiornamento COVIDSafe in corso. \n\nAssicurarsi che il cellulare non sia spento fino al completamento dell'aggiornamento.";
"migration_in_progress" = "Aggiornamento COVIDSafe in corso. \n\nAssicurati che il cellulare non sia spento fino al completamento dell'aggiornamento.";
"national_numbers" = "Numeri nazionali";
"new_cases" = "Nuovi casi nelle ultime 24 ore";
"new_cases_total" = "";
"new_south_wales" = "New South Wales";
/* Figma page Android Screens (05/06) */
"norfolk_hint" = "Per esempio: 51234";
@ -341,11 +351,11 @@
"numbers_no_internet" = "Connettiti a Internet per ricevere notifiche sui numeri aggiornati.";
"numbers_refresh" = "Aggiorna ora";
"options_for_australia" = "Opzioni per l'Australia";
"overseas_acquired" = "";
"permission_button" = "Procedi";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS" = "COVIDSafe necessita del Bluetooth® abilitato per funzionare. Abilitando Notifiche, ricevi aggiornamenti per ricordarti quando COVIDSafe non è attivo. \n\nSeleziona 'Procedi' per abilitare:";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS_2" = "1. Bluetooth®\n2. Notifiche\n\nCOVIDSafe non invia richieste di accoppiamento.";
"permission_content_iOS" = "COVIDSafe necessita di # Bluetooth # abilitato per funzionare. COVIDSafe non invia richieste di accoppiamento. \n\nAbilitando # Notifiche #, ricevi aggiornamenti per ricordarti quando COVIDSafe non è attivo. \n\nIl tuo iPhone richiede #L'autorizzazione di localizzazione* # per far funzionare COVIDSafe. * COVIDSafe NON memorizza né utilizza i dati relativi alla tua posizione * \n\nSeleziona \"Procedi\" per abilitare:";
"permission_content_iOS_2" = "1. Bluetooth®\n2. Notifiche\n3. Servizi di localizzazione";
/* OnBoarding Permission */
"permission_headline" = "Impostazioni dell'app";
"permission_success_button" = "Eseguito";
@ -391,12 +401,15 @@
"share_this_app_content" = "Unisciti a noi per fermare la diffusione del COVID-19! Scarica il COVIDSafe, un'app del Governo australiano. #COVID19 #coronavirusaustralia #stayhomesavelives https://covidsafe.gov.au";
"show" = "Visualizza";
"south_australia" = "South Australia";
"state_number_heading" = "";
"stepCounter" = "Passo %d di %d";
"support" = "Supporto";
"support_content" = "Per assistenza su questioni non trattate dagli argomenti della Guida";
"Support_VOLabel" = "Supporto. Per assistenza su questioni non trattate dagli argomenti della Guida";
"tasmania" = "Tasmania";
"total" = "";
"total_confirmed_cases" = "Totale casi confermati";
"total_deaths" = "";
"total_per_state" = "Totale casi confermati per stato e territorio";
"UILaunchStoryboardName" = "LaunchScreen_it_IT";
"under_sixteen_content" = "Confermo il consenso dei miei genitori o del mio tutore a Digital Transformation Agency nel suo ruolo di amministratore della banca dati, in base alle disposizioni legali stabilite dal segretario del Dipartimento della sanità del governo australiano:";
@ -412,17 +425,22 @@
"update_available_dismiss_btn" = "Ricordamelo più tardi";
"update_available_message_ios" = "Abbiamo apportato dei miglioramenti a COVIDSafe. Aggiorna tramite l'App Store";
"update_available_title" = "Aggiornamento disponibile!";
"update_description" = "";
"update_description_VO" = "";
"update_heading" = "Abilita i servizi di localizzazione";
"update_modal_button" = "Procedi";
"upload_answer_no" = "No";
/* Upload flow */
"upload_answer_yes" = "Sì";
"upload_consent_button" = "Sono d'accordo";
"upload_fail_heading" = "Caricamento non riuscito";
"upload_failed" = "Caricamento non riuscito";
"upload_finished_header" = "Grazie per aver aiutato a fermare la diffusione del COVID-19!";
"upload_finished_sub_header" = "I vostri dati sono stati caricati sul sistema altamente sicuro di archiviazione di COVIDSafe. \n\nI funzionari sanitari statali o del territorio informeranno gli altri utenti COVIDSafe che hanno registrato casi di stretto contatto con voi. La vostra identità rimarrà anonima per gli altri utenti.";
"upload_step_1_body" = "Un funzionario sanitario statale o del territorio vi contatterà per aiutarvi con il caricamento volontario dei vostri dati solo se siete risultati positivi al COVID-19.\n\nDopo aver premuto 'Sì', bisogna fornire il proprio consenso per caricare i dati.";
"upload_finished_sub_header" = "I tuoi dati sono stati caricati sul sistema altamente sicuro di archiviazione di COVIDSafe. \n\nI funzionari sanitari statali o del territorio informeranno gli altri utenti COVIDSafe che hanno registrato casi di stretto contatto con te. La tua identità rimarrà anonima per gli altri utenti.";
"upload_step_1_body" = "Un funzionario sanitario statale o del territorio ti contatterà per aiutarvi con il caricamento volontario dei tuoi dati solo se sei risultato positivo al COVID-19.\n\nDopo aver premuto 'Sì', bisogna fornire il proprio consenso per caricare i dati.";
"upload_step_1_header" = "Hai ricevuto richiesta di caricamento dati da un ufficiale sanitario?";
"upload_step_4_header" = "Consenso al caricamento";
"upload_step_4_sub_header" = "I dati di stretto contatto non vengono caricati senza il vostro consenso.\n\nUna volta dato il consenso, i vostri dati di stretto contatto vengono caricati e condivisi con funzionari sanitari statali o del territorio a scopo di tracciamento di contatti.\n\nI funzionari sanitari statali e del territorio potranno accedere solo alle informazioni sui contatti stretti.\n\nLeggere *l'informativa sulla privacy* del COVIDSafe per ulteriori informazioni.";
"upload_step_4_sub_header" = "I dati di stretto contatto non vengono caricati senza il tuo consenso.\n\nUna volta dato il consenso, i tuoi dati di stretto contatto vengono caricati e condivisi con funzionari sanitari statali o del territorio a scopo di tracciamento di contatti.\n\nI funzionari sanitari statali e del territorio potranno accedere solo alle informazioni sui contatti stretti.\n\nLeggi *l'informativa sulla privacy* del COVIDSafe per ulteriori informazioni.";
"upload_step_verify_pin_header" = "Carica i tuoi dati";
"upload_step_verify_pin_sub_header" = "Un funzionario sanitario statale o del territorio invierà un PIN al tuo dispositivo tramite messaggio di testo. Inseriscilo qui sotto per caricare i tuoi dati.";
"us_consent_button" = "Sono d'accordo";
@ -442,7 +460,7 @@
"EmailPlaceholder" = "Inserisci l'indirizzo email (obbligatorio)";
/* Phone number and PIN */
"EnterPhoneReVerify" = "Inserisci il numero di cellulare per effettuare una nuova verifica";
"EnterPINSent" = "Inserire il PIN inviato a %@";
"EnterPINSent" = "Inserisci il PIN inviato a %@";
/* Message for prompt that user sees when opening feeback. Prompt asks user if he/she would like to send new feedback. */
"entryPrompt_alert_message" = "Ciao, come vorresti procedere?";
/* Title for prompt that user sees when opening feeback. Prompt asks user if he/she would like to send new feedback. */
@ -467,14 +485,14 @@
"newFeedback_deleteScreenshotConfirmation_message" = "Questo screenshot verrà eliminato definitivamente.";
"newFeedback_invalidEmail_errorMessage" = "Inserisci un indirizzo email valido!";
/* Error message displayed to users when feedback has empty description */
"newFeedback_noMessage_errorMessage" = "Diteci qualcosa prima di inviare.";
"newFeedback_noMessage_errorMessage" = "Dicci qualcosa prima di inviare.";
/* Error message displayed to users when feedback fails to send. */
"newFeedback_send_errorMessage" = "Errore durante l'invio del Feedback";
/* Navigation Bar title shown during new feedback flow. */
"newFeedbackFlow_navigationTitle" = "Segnala un problema";
"Next" = "Avanti";
/* registration succcess */
"OS2b_Item1" = "1. Quando uscite di casa, portate il cellulare con voi e assicuratevi che COVIDSafe sia attiva.";
"OS2b_Item1" = "1. Quando esci di casa, porta il cellulare con te e assicurati che COVIDSafe sia attiva.";
"OS2b_Item2" = "2. Il Bluetooth® deve rimanere ON.";
"OS2b_Item2_VOLabel" = "2. Il Bluetooth deve rimanere ON.";
"OS2b_Item3" = "3. COVIDSafe non invia richieste di accoppiamento. Per saperne di più.";
@ -497,11 +515,11 @@
"ValidationError" = "Errore di convalida";
"GetCoronaVirusApp_VOLabel" = "Scarica l'app Coronavirus. Scarica l'app governativa per le ultime notizie e consigli.";
"HelpTopics_VOLabel" = "Argomenti della Guida su come individuare e risolvere problemi tecnici.";
"LatestNews_VOLabel" = "Ultime notizie e aggiornamenti.Visitare aus.gov.au per le ultime notizie sul Coronavirus.";
"LatestNews_VOLabel" = "Ultime notizie e aggiornamenti. Visita aus.gov.au per le ultime notizie sul Coronavirus.";
"NotificationsDisabled_VOLabel" = "Le notifiche sono disabilitate. Non si ricevono notifiche se COVIDSafe non è attivo. \nModifica le impostazioni di notifica";
"NotificationsEnabled_VOLabel" = "Le notifiche sono abilitate. Riceverai una notifica se COVIDSafe non è attivo. \nModifica le impostazioni di notifica";
"OS1b_TopParagraph_VOLabel" = "I segnali Bluetooth vengono utilizzati per determinare la vicinanza ad altri utenti di COVIDSafe. \nOgni istanza di stretto contatto tra voi e gli altri utenti di COVIDSafe viene annotata per creare informazioni sui contatti. Queste informazioni sono crittografate e memorizzate solo sul vostro cellulare. \nSe risultate positivi al COVID-19, come utenti di COVIDSafe sarete contattati da un funzionario sanitario statale o del territorio per assistervi con il caricamento volontario delle vostre informazioni di stretto contatto in un sistema di archiviazione di informazioni altamente sicuro. \nI funzionari sanitari statali o territoriali possono anche contattarvi se siete entrati in stretto contatto con un altro utente di COVIDSafe risultato positivo. \nPer ulteriori informazioni, consultare la pagina Argomenti della Guida.";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth\n2. Notifiche\n\nCOVIDSafe non invia richieste di accoppiamento.";
"permission_content_iOS_VOLabel" = "COVIDSafe necessita il Bluetooth abilitato per funzionare. Abilitando Notifiche, ricevete aggiornamenti per sapere quando COVIDSafe non è attivo. \n\n Seleziona 'Procedi' per abilitare:";
"OS1b_TopParagraph_VOLabel" = "I segnali Bluetooth vengono utilizzati per determinare la vicinanza ad altri utenti di COVIDSafe. \nOgni istanza di stretto contatto tra te e gli altri utenti di COVIDSafe viene annotata per creare informazioni sui contatti. Queste informazioni sono crittografate e memorizzate solo sul tuo cellulare. \nSe risulti positivo al COVID-19, come utente di COVIDSafe sarai contattato da un funzionario sanitario statale o del territorio per assisterti con il caricamento volontario delle tue informazioni di stretto contatto in un sistema di archiviazione di informazioni altamente sicuro. \nI funzionari sanitari statali o territoriali possono anche contattarti se sei stato in stretto contatto con un altro utente di COVIDSafe risultato positivo. \nPer ulteriori informazioni, consulta la pagina Argomenti della Guida.";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth®\n2. Notifiche\n3. Servizi di localizzazione\n";
"permission_content_iOS_VOLabel" = "COVIDSafe richiede che il Bluetooth sia abilitato per funzionare. COVIDSafe non invia richieste di accoppiamento. \n\nAbilitando le notifiche, ricevi aggiornamenti per ricordarti quando COVIDSafe non è attivo. \n\nIl tuo iPhone richiede l'autorizzazione di localizzazione affinché COVIDSafe funzioni. *COVIDSafe NON memorizza né utilizza i dati relativi alla tua posizione. * \n\nSeleziona \"Procedi\" per abilitare:";
"ShareCovidSafe_VOLabel" = "Condividi COVIDSafe. Invita altri ad collaborare. Insieme, siamo più forti.";
"UploadData_VOLabel" = "Siete stati contattati da un funzionario sanitario? Potete caricare le vostre informazioni solo se siete risultati positivi.";
"UploadData_VOLabel" = "Sei stato contattato da un funzionario sanitario? Puoi caricare le tue informazioni solo se sei risultato positivo.";

View file

@ -3,6 +3,6 @@
Copyright © 2020 Australian Government. All rights reserved. */
"BluetoothUsageDesc" = "COVIDSafe는 동일한 앱을 실행하는 근처의 휴대폰과 블루투스® 신호를 교환합니다. 이 신호에는 익명의 ID가 포함되어 있으며, 이 ID는 암호화되어 있고 개인정보를 보호하기 위해 지속적으로 변경됩니다.";
"NSBluetoothAlwaysUsageDescription" = "COVIDSafe는 동일한 앱을 실행하는 주위의 휴대폰들과 블루투스® 신호들을 교환합니다. 이 신호들에는 익명의 ID가 포함되어 있으며, 이 ID는 암호화되어 있고 귀하의 개인 정보를 보호하기 위해 지속적으로 변경됩니다.";
"NSBluetoothPeripheralUsageDescription" = "COVIDSafe는 동일한 앱을 실행하는 주위의 휴대폰들과 블루투스® 신호들을 교환합니다. 이 신호들에는 익명의 ID가 포함되어 있으며, 이 ID는 암호화되어 있고 귀하의 개인 정보를 보호하기 위해 지속적으로 변경됩니다.";
"NSBluetoothAlwaysUsageDescription" = "COVIDSafe는 동일한 앱을 실행하는 주위의 휴대폰들과 블루투스® 신호 교환합니다. 이 신호들에는 익명의 ID가 포함되어 있으며, 이 ID는 암호화되어 있고 귀하의 개인 정보를 보호하기 위해 지속적으로 변경됩니다.";
"NSBluetoothPeripheralUsageDescription" = "COVIDSafe는 동일한 앱을 실행하는 주위의 휴대폰들과 블루투스® 신호 교환합니다. 이 신호들에는 익명의 ID가 포함되어 있으며, 이 ID는 암호화되어 있고 귀하의 개인 정보를 보호하기 위해 지속적으로 변경됩니다.";
"UILaunchStoryboardName" = "LaunchScreen_ko";

View file

@ -1,21 +1,26 @@
"28_days" = "";
"7_days" = "";
"action_continue" = "계속";
"action_report_an_issue" = "문제 신고하기";
"action_upload_done" = "완료";
"action_verify_invalid_pin" = "잘못된 PIN입니다. 보건 담당자에게 다른 PIN을 보내달라고 요청하십시오.";
"action_verify_upload_pin" = "내 정보 업로드하기";
"active_cases" = "";
/* Figma page iOS Screens (05/06) */
"allow_bluetooth_call" = "COVIDSafe의 블루투스® 접속을 허용해 주세요";
/* Figma page iOS Screens (05/06) */
"AllowBluetoothOFF" = "블루투스® 접속: OFF";
"AllowBluetoothOFF" = "블루투스® 접속: 꺼짐";
/* Figma page iOS Screens (05/06) */
"AllowBluetoothON" = "블루투스® 접속: ON";
"australian_capital_territory" = "호주 수도 영토";
"AllowBluetoothON" = "블루투스® 접속: 켜짐";
"australian_capital_territory" = "호주 수도 테리토리";
/* Figma page iOS Screens (05/06) */
"BluetoothOFF" = "블루투스®: OFF";
"BluetoothOFF" = "블루투스®: 꺼짐";
/* Figma page iOS Screens (05/06) */
"BluetoothOFF_content" = "휴대폰의 블루투스®를 켜십시오. '새 연결 허용'이 표시되면 선택하십시오.";
/* Figma page iOS Screens (05/06) */
"BluetoothON" = "블루투스®: ON";
"BluetoothON" = "블루투스®: 켜짐";
"cases_28_days" = "";
"cases_7_days" = "";
"change_language" = "언어를 변경하십시오";
"change_language_content" = "다른 언어로 COVIDSafe 사용에 대한 안내서를 읽으십시오.";
"collection_message" = "이제 COVIDSafe는 앱 문제를 해결하는 데 도움이 되는 진단 정보를 수집합니다. *더 알아보기*";
@ -234,12 +239,12 @@
"country_region_name_zm" = "잠비아";
"country_region_name_zw" = "짐바브웨";
"data_privacy_button" = "다음";
"data_privacy_content" = "COVIDSafe에 등록하기 전에 COVIDSafe *개인 정보 보호 정책*을 읽는 것이 중요합니다. \n\n16세 미만이라면, 그 부모 또는 보호자도 반드시 *개인 정보 보호 정책*을 읽어야 합니다. \n\nCOVIDSafe의 사용은 전적으로 자발적입니다. 언제든지 앱을 설치 또는 삭제할 수 있습니다. COVIDSafe를 삭제하면, *여러분은 보안 서버에 있는 자신의 정보 삭제*도 요청할 수 있습니다. \n\nCOVIDSafe에 등록하려면, 성명 (또는 가명), 휴대폰 번호, 연령대 및 우편 번호를 입력해야 합니다. \n\n등록 시 제출한 정보 및 여러분의 COVIDSafe 사용 정보는 보안성 높은 서버에 수집 및 저장됩니다. \n\nCOVIDSafe는 여러분의 위치 정보를 수집하지 않습니다. \n\nCOVIDSafe는 접촉 시각 및 여러분과 접촉한 다른 COVIDSafe 사용자의 익명 ID 코드, 블루투스® 신호 강도 및 다른 사용자의 전화기 모델 등을 여러분 장치에 기록합니다. \n\n여러분과 접촉한 다른 COVIDSafe 사용자의 장치에도 여러분의 익명 ID 코드 및 접촉일과 시각, 블루투스® 신호 강도 및 다른 사용자의 전화기 모델 등이 여러분 장치에 기록됩니다. \n\n다른 사용자가 COVID-19 양성 결과를 받으면, 그들은 자신의 접촉 정보를 업로드할 수 있으며, 주 또는 테리토리 보건 담당자가 접촉 추적 목적으로 여러분에게 연락을 취할 수 있습니다. \n\n여러분의 등록 정보는 접촉 추적 및 COVIDSafe의 적절하고 합법적인 기능을 위해서만 사용 또는 공개됩니다. \n\n자세한 정보는 *호주 정부 보건부* 웹사이트에서 확인할 수 있습니다. \n\n자신의 정보에 대한 권리와 개인 정보의 취급 및 공유 방법에 대한 자세한 내용은 COVIDSafe *개인 정보 보호 정책*을 참조하십시오.";
"data_privacy_content_VO" = "COVIDSafe에 등록하기 전에 COVIDSafe 개인 정보 보호 정책을 읽어야 합니다. \n\n16 세 미만인 경우, 부모/보호자 또한 개인 정보 보호 정책을 읽어야 합니다. \n\nCOVIDSafe의 사용은 전적으로 자발적입니다. 언제든지 응용 프로그램을 설치하거나 삭제할 수 있습니다. COVIDSafe를 삭제하면 보안 서버에서 정보를 삭제하도록 요청할 수도 있습니다. \n\nCOVIDSafe에 등록하려면 이름 (가명), 휴대폰 번호, 연령대 및 우편 번호를 입력해야 합니다. \n\n등록시 제출한 정보 및 COVIDSafe 사용에 관한 정보는 수집되어 매우 안전한 서버에 저장됩니다. \n\nCOVIDSafe는 위치 정보를 수집하지 않습니다. \n\nCOVIDSafe는 장치에서 연락 시간, 연락하려는 다른 COVIDSafe 사용자의 익명 ID 코드, 블루투스 신호 강도 및 다른 사용자의 전화 모델을 기록합니다. \n\n귀하가 연락하는 다른 COVIDSafe 사용자는 귀하의 익명 ID 코드, 연락 날짜 및 시간, 블루투스 신호 강도 및 전화 모델을 장치에 기록합니다. \n\n다른 사용자가 COVID-19에 대해 긍정적인 시험을 하면 연락처 정보를 업로드할 수 있으며 주 또는 테리토리 보건 당국은 연락 추적 목적으로 연락할 수 있습니다. \n\n등록 세부 정보는 접촉 추적 및 COVIDSafe의 적절하고 합법적인 기능을 위해서만 사용 또는 공개됩니다. \n\n자세한 정보는 호주 정부 보건부 웹 사이트에서 확인할 수 있습니다. \n\n여러분의 개인 정보에 대한 여러분의 권리와 정보의 취급 및 공유 방법에 대한 자세한 내용은 COVIDSafe 개인 정보 보호 정책을 참조하십시오.";
"data_privacy_content" = "COVIDSafe에 등록하기 전에 COVIDSafe *개인 정보 보호 정책*을 읽는 것이 중요합니다. \n\n16세 미만이라면, 그 부모 또는 보호자도 반드시 *개인 정보 보호 정책*을 읽어야 합니다. \n\nCOVIDSafe의 사용은 전적으로 자발적입니다. 언제든지 앱을 설치 또는 삭제할 수 있습니다. COVIDSafe를 삭제하면, *여러분은 보안 서버에 있는 자신의 정보 삭제*도 요청할 수 있습니다. \n\nCOVIDSafe에 등록하려면, 성명 (또는 가명), 휴대폰 번호, 연령대 및 우편 번호를 입력해야 합니다. \n\n등록 시 제출한 정보 및 여러분의 COVIDSafe 사용 정보는 보안성 높은 서버에 수집 및 저장됩니다. \n\nCOVIDSafe가 장치에서 제대로 작동하는지 확인하기 위해 정보도 수집됩니다. 여기에는 운영 체제, 설치한 앱 버전, 장치에 설정된 언어, 블루투스 작동여부 및 지난 7일 동안 장치에 기록된 접촉 정보가 포함됩니다. \n\nCOVIDSafe는 여러분의 위치 정보를 수집하지 않습니다. \n\nCOVIDSafe는 접촉 시각 및 여러분과 접촉한 다른 COVIDSafe 사용자의 익명 ID 코드, 블루투스® 신호 강도 및 다른 사용자의 전화기 모델 등을 여러분 장치에 기록합니다. \n\n여러분과 접촉한 다른 COVIDSafe 사용자의 장치에도 여러분의 익명 ID 코드 및 접촉날짜와 시간, 블루투스® 신호 강도 및 당신의 전화기 모델 등이 여러분 장치에 기록됩니다. \n\n다른 사용자가 COVID-19 양성 결과를 받으면, 그들은 자신의 접촉 정보를 업로드할 수 있으며, 주 또는 테리토리 보건 담당자가 접촉 추적 목적으로 여러분에게 연락을 취할 수 있습니다. \n\n여러분의 등록 정보는 접촉 추적 및 COVIDSafe의 적절하고 합법적인 기능을 위해서만 사용 또는 공개됩니다. \n\n자세한 정보는 *호주 정부 보건부* 웹사이트에서 확인할 수 있습니다. \n\n자신의 정보에 대한 권리와 개인 정보의 취급 및 공유 방법에 대한 자세한 내용은 COVIDSafe *개인 정보 보호 정책*을 참조하십시오.";
"data_privacy_content_VO" = "COVIDSafe에 등록하기 전에 COVIDSafe 개인 정보 보호 정책을 읽어야 합니다. \n\n16 세 미만인 경우, 부모/보호자 또한 개인 정보 보호 정책을 읽어야 합니다. \n\nCOVIDSafe의 사용은 전적으로 자발적입니다. 언제든지 응용 프로그램을 설치하거나 삭제할 수 있습니다. COVIDSafe를 삭제하면 보안 서버에서 정보를 삭제하도록 요청할 수도 있습니다. \n\nCOVIDSafe에 등록하려면 이름 (혹은 가명), 휴대폰 번호, 연령대 및 우편 번호를 입력해야 합니다. \n\n등록시 제출한 정보 및 COVIDSafe 사용에 관한 정보는 매우 안전한 서버에 저장됩니다. \n\nCOVIDSafe가 장치에서 제대로 작동하게끔 하기 위한 정보도 수집됩니다. 여기에는 운영 체제, 설치한 앱 버전, 장치에 설정된 언어, 블루투스 작동 여부 및 앱이 지난 7일 동안의 접촉을 기록했는지 여부가 포함됩니다. \n\nCOVIDSafe는 위치 정보를 수집하지 않습니다. \n\nCOVIDSafe는 접촉 날짜와 시간, 접촉한 다른 COVIDSafe 사용자의 익명 ID 코드, 블루투스 신호 강도 및 다른 사용자의 전화 모델을 장치에 기록합니다. \n\n귀하가 접촉한 다른 COVIDSafe 사용자는 귀하의 익명 ID 코드, 접촉 날짜와 시간, 블루투스 신호 강도 및 귀하의 전화 모델을 장치에 기록합니다. \n\n다른 사용자가 COVID-19에 양성 반응을 보이는 경우, 본인의 연락처 정보를 업로드 할 수 있으며 주 또는 테리토리 보건 당국은 연락 추적 목적으로 당신에게 연락할 수 있습니다. \n\n당신의 등록정보는 접촉 추적 및 COVIDSafe의 적절하고 합법적인 기능을 위해서만 사용 또는 공개됩니다. \n\n자세한 정보는 covidsafe.gov.au 웹사이트에서 확인할 수 있습니다. \n\n여러분의 개인 정보에 대한 권리와 정보의 취급 및 공유 방법에 대한 자세한 내용은 COVIDSafe 개인 정보 보호 정책을 참조하십시오.";
/* OnBoarding Data Privacy */
"data_privacy_headline" = "등록 및 개인정보 보호";
"deaths" = "사망자";
"dialog_error_uploading_message" = "정보를 업로드하는 동안 오류가 발생했습니다. 다시 시도하세요.";
"dialog_error_uploading_message" = "데이터 업로드를 다시 시도하십시오.\n\n주 또는 준주 보건 담당자의 요청이 있을 경우 이 에러 코드를 참조하십시오: \n%@";
"dialog_error_uploading_negative" = "취소";
"dialog_error_uploading_positive" = "다시 시도";
"dialog_uploading_message" = "당신의 COVIDSafe 정보가 현재 업로드 중입니다. \n\n앱을 닫지 마세요.";
@ -269,7 +274,7 @@
/* Home */
"home_header_active_title" = "COVIDSafe가 활성화되어 있습니다.";
/* Home */
"home_header_active_title_thanks" = "감사합니다. 이제 COVIDSafe가 활성화되어 있습니다.";
"home_header_active_title_thanks" = "감사합니다. 이제 COVIDSafe가 활성화 되었습니다.";
"home_header_inactive_check_your_permissions" = "설정을 확인하세요.";
"home_header_inactive_title" = "COVIDSafe가 활성화되어 있지 않습니다";
"home_header_uploaded_on_date_ios" = "당신의 정보가 %@에 업로드 되었습니다.";
@ -292,7 +297,8 @@
"home_set_complete_external_link_share_content" = "다른 이들에게 참여를 권유하세요. 우리는 함께할 때 더 강해집니다.";
"home_set_complete_external_link_share_title" = "COVIDSafe 공유하기";
"home_setup_help" = "도움";
"home_version_number_ios" = "버전 %@ , 빌드 %@";
"home_version_number_ios" = "버전 %@, 빌드 %@";
"hotspots_state_territory" = "";
"how_it_works_button" = "다음";
"how_it_works_content" = "블루투스® 신호는 여러분이 언제 다른 COVIDSafe 사용자 근처에 있었는지 확인하는 데 사용됩니다. \n\n여러분과 다른 COVIDSafe 사용자들 사이의 모든 근거리 접촉 상황들은 접촉 데이터로 기록됩니다. 이 정보는 암호화되어 여러분의 휴대폰에만 저장됩니다. \n\nCOVIDSafe 사용자로서 여러분이 COVID-19 양성 결과를 받으면, 주 또는 테리토리 보건 담당자가 여러분에게 연락할 것입니다. 그들은 여러분이 자신의 근거리 접촉 정보를 보안성 높은 정보 저장 시스템에 자발적으로 업로드할 수 있도록 도와줄 것입니다. \n\n또한 양성 결과가 나온 다른 COVIDSafe 사용자와 여러분이 가까이 접촉한 경우, 주 또는 테리토리 보건 담당자가 여러분에게 연락을 취할 수 있습니다. \n\n자세한 내용은 *도움말 항목* 페이지를 참조하세요.";
/* OnBoarding How it works */
@ -301,7 +307,7 @@
"improve_heading" = "COVIDSafe의 성능을 향상시키십시오";
"internet_connection_content" = "인터넷에 연결해서 문제 및 업데이트에 관한 알림을 받으십시오.";
"internet_connection_heading" = "인터넷에 연결되지 않음";
"internet_screen_content" = "COVIDSafe는 앱과 관련된 문제들을 확인하기 위해 때때로 서버에 연결되어야 합니다. \n\n 인터넷에 연결하면 다음과 같은 알림들을 받을 수 있습니다:";
"internet_screen_content" = "COVIDSafe는 앱과 관련된 문제들을 확인하기 위해 때때로 서버에 연결되어야 합니다. \n\n인터넷에 연결하면 다음과 같은 알림들을 받을 수 있습니다:";
"internet_screen_content_1" = "문제를 해결하고 COVIDSafe를 다시 활성화하는 방법";
"internet_screen_content_2" = "새 버전의 업데이트가 가능할 때";
"internet_screen_heading" = "COVIDSafe의 성능을 향상시키기 위해 인터넷에 연결하십시오";
@ -315,12 +321,16 @@
"invalid_norfolk_island_phone_number_error_prompt" = "노퍽 섬의 휴대폰 번호는 5-6자리입니다.";
"jwt_description" = "등록 세부 정보에 문제가 있습니다.";
"jwt_heading" = "다시 등록하십시오";
"jwt_success" = "";
"jwt_success" = "등록 갱신 완료";
"loading_numbers" = "최신 번호 로드 중";
"locally_acquired" = "";
"location_off" = "위치 서비스: 꺼짐";
"location_off_description" = "COVIDSafe가 제대로 실행되기 위해서는 여러분의 아이폰에서 위치 서비스 기능을 허용해야 합니다. COVIDSafe는 여러분의 위치 정보를 추적하거나 저장하지 않습니다.";
/* Splash Screen */
"migration_in_progress" = "COVIDSafe 업데이트 진행 중. \n\n업데이트가 완료될 때까지 휴대폰이 꺼지지 않도록 해주세요.";
"national_numbers" = "전국 수";
"new_cases" = "지난 24 시간 동안의 새 확진자 수";
"new_cases" = "지난 24시간 동안의 새 확진자 수";
"new_cases_total" = "";
"new_south_wales" = "뉴 사우스 웨일즈";
/* Figma page Android Screens (05/06) */
"norfolk_hint" = "예: 51234";
@ -329,7 +339,7 @@
"notification_internet_content" = "COVIDSafe 작동을 위해 앱을 열고 인터넷 연결을 확인해 주세요.";
"notification_not_active_title" = "COVIDSafe가 활성화되어 있지 않습니다.";
"notification_settings" = "앱을 열어 여러분의 설정을 확인하십시오.";
"notification_update_content" = "최신 개선 사항에 접근하려면 귀하의 앱을 업데이트하십시오.";
"notification_update_content" = "최신 개선 사항에 접근하려면 앱을 업데이트 하십시오.";
"notification_update_heading" = "COVIDSafe 업데이트가 가능함";
/* Figma page iOS Screens (05/06) */
"NotificationsBlurbLink" = "알림 설정 변경";
@ -341,11 +351,11 @@
"numbers_no_internet" = "업데이트된 번호를 얻으려면 인터넷에 연결하십시오.";
"numbers_refresh" = "지금 새로 고침";
"options_for_australia" = "호주 관련 옵션";
"overseas_acquired" = "";
"permission_button" = "진행";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS" = "COVIDSafe 작동을 위해 블루투스®가 활성화되어 있어야 합니다. 알림기능을 활성화하면, COVIDSafe가 비활성화되어 있을 때 알림을 받게 됩니다. \n\n'진행'을 선택하여 활성화 하십시오:";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS_2" = "1. 블루투스®\n2. 알림\n\nCOVIDSafe는 페어링 요청을 보내지 않습니다.";
"permission_content_iOS" = "COVIDSafe가 제대로 실행되기 위해서는 #블루투스#가 활성화되어야 합니다. COVIDSafe는 페어링 요청을 보내지 않습니다.\n\n#알림 기능#을 활성화하면, COVIDSafe가 비활성화되어 있을 때 알림을 받게 됩니다. \n\nCOVIDSafe가 제대로 실행되기 위해서는 여러분의 아이폰에서 #위치 서비스 허용#이 필요합니다.*COVIDSafe는 여러분의 위치 정보를 추적하거나 저장하지 않습니다*\n\n활성화하려면 '진행'을 선택하세요.";
"permission_content_iOS_2" = "1. 블루투스\n2. 알림\n3. 위치 서비스";
/* OnBoarding Permission */
"permission_headline" = "앱 설정";
"permission_success_button" = "완료";
@ -391,13 +401,16 @@
"share_this_app_content" = "COVID-19의 확산을 막아주세요! 호주 정부의 앱인 COVIDSafe를 다운로드하세요. # COVID19 #coronavirusaustralia #stayhomesavelives https://covidsafe.gov.au";
"show" = "보여주기";
"south_australia" = "남호주";
"state_number_heading" = "";
"stepCounter" = "%d 의 단계들 중 %d 단계";
"support" = "지원";
"support_content" = "도움말 항목에서 다루지 않는 문제에 대한 지원";
"Support_VOLabel" = "지원. 도움말 항목에서 다루지 않는 문제에 대한 지원";
"tasmania" = "태즈매니아";
"total" = "";
"total_confirmed_cases" = "총 확진자 수";
"total_per_state" = "주 및 준주 별 총 확진자";
"total_deaths" = "";
"total_per_state" = "주 및 테리토리 별 총 확진자";
"UILaunchStoryboardName" = "LaunchScreen_ko";
"under_sixteen_content" = "본인은 호주 정부 보건부 장관의 법적 결정에 따라, 데이터 저장소 관리자로서 다음 정보를 수집하는 것에 대해 본인 부모 또는 보호자가 Digital Transformation Agency에 동의한다는 것을 확인합니다:";
"under_sixteen_first_paragraph" = "나의 등록 정보.";
@ -410,19 +423,24 @@
"under-sixteen_consent_call_for_action" = "동의 확인을 위해 '동의'를 선택하세요.";
"update_available_app_store_btn" = "앱 스토어 가기";
"update_available_dismiss_btn" = "나중에 다시 알려 주세요";
"update_available_message_ios" = "저희는 COVIDSafe의 기능을 향상시키고 있습니다. 앱 스토어를 통해 업데이트하십시오.";
"update_available_message_ios" = "저희는 COVIDSafe의 기능을 향상시키고 있습니다. 앱 스토어를 통해 업데이트 하십시오.";
"update_available_title" = "업데이트 가능!";
"update_description" = "";
"update_description_VO" = "";
"update_heading" = "위치 서비스 활성화";
"update_modal_button" = "진행";
"upload_answer_no" = "아니요";
/* Upload flow */
"upload_answer_yes" = "예";
"upload_consent_button" = "동의합니다";
"upload_fail_heading" = "업로드 실패";
"upload_failed" = "업로드 실패";
"upload_finished_header" = "COVID-19의 확산을 막는 데 협조해 주셔서 감사합니다!";
"upload_finished_sub_header" = "보안성이 높은 COVIDSafe의 저장 체계에 당신의 정보를 성공적으로 업로드하였습니다. \n\n당신과 가까운 접촉기록이 있는 다른 COVIDSafe 사용자에게 주 또는 테리토리 보건 담당자가 통보할 것입니다. 다른 사용자들에게 당신의 신원은 익명으로 유지됩니다.";
"upload_step_1_body" = "COVID-19 양성 결과가 나온 경우에만 주 또는 테리토리 보건 담당자가 당신에게 연락해서 당신의 정보를 자발적으로 업로드하는 것을 도와줄 것입니다. \n\n'예'를 누르면, 본인의 정보 업로드에 동의해야 합니다.";
"upload_step_1_header" = "보건 담당자가 당신의 정보를 업로드할 것을 당신에게 요청하고 있습니까?";
"upload_step_4_header" = "동의 확인을 업로드하세요";
"upload_step_4_sub_header" = "여러분이 동의하지 않는 한, 여러분의 접촉 데이타는 업로드되지 않습니다.\n\n동의하시면, 여러분의 접촉 데이타가 업로드되고, 주 또는 테리토리 보건 담당자와 공유되어 접촉 추적 목적으로 사용될 것입니다.\n\n자세한 내용은 COVIDSafe *개인 정보 보호 정책*을 참조하십시오.";
"upload_step_4_sub_header" = "여러분이 동의하지 않는 한, 여러분의 접촉 데이타는 업로드되지 않습니다.\n\n동의하시면, 여러분의 접촉 정보가 업로드되고, 주 또는 테리토리 보건 담당자와 공유되어 접촉 추적 목적으로 사용될 것입니다.\n\n주 또는 테리토리 보건 담당자들만이 당신의 근거리 접촉자에 관한 정보를 사용할 수 있습니다. \n\n자세한 내용은 COVIDSafe *개인 정보 보호 정책*을 참조하십시오.";
"upload_step_verify_pin_header" = "본인의 정보를 업로드하세요";
"upload_step_verify_pin_sub_header" = "주 또는 테리토리 보건 담당자가 당신의 장치에 문자 메시지로 PIN을 보낼 것입니다. 본인의 정보를 업로드하려면 아래에 그 번호를 입력하십시오.";
"us_consent_button" = "동의";
@ -431,10 +449,10 @@
"western_australia" = "서호주";
"wrong_ping_number" = "잘못된 PIN 입력";
"country_region_name_au2" = "노퍽 섬";
"AllowBluetoothOFF_VOLabel" = "블루투스 접속: OFF COVIDSafe의 블루투스 접속을 허용해 주세요";
"AllowBluetoothON_VOLabel" = "블루투스 접속: ON";
"AllowBluetoothOFF_VOLabel" = "블루투스 접속: 꺼짐 COVIDSafe의 블루투스 접속을 허용해 주세요";
"AllowBluetoothON_VOLabel" = "블루투스 접속: 켜짐";
"BluetoothOFF_VOLabel" = "블루투스: OFF 휴대폰의 블루투스를 켜세요. '새 연결 허용'이 표시되면 선택하세요.";
"BluetoothON_VOLabel" = "블루투스: ON";
"BluetoothON_VOLabel" = "블루투스: 켜짐";
"CodeHasExpired" = "코드가 만료되었습니다.";
/* details */
"Done" = "완료";
@ -476,7 +494,7 @@
/* registration succcess */
"OS2b_Item1" = "1. 외출 시 휴대폰을 소지하고, COVIDSafe가 활성화되어 있도록 하세요. ";
"OS2b_Item2" = "2. 블루투스®가 켜져 있어야 합니다.";
"OS2b_Item2_VOLabel" = "2. 블루투스 켜져 있어야 합니다.";
"OS2b_Item2_VOLabel" = "2. 블루투스 켜져 있어야 합니다.";
"OS2b_Item3" = "3. COVIDSafe는 페어링 요청을 보내지 않습니다. 더 알아보세요.";
"OS2b_Item3Underline" = "더 알아보세요";
/* home */
@ -484,7 +502,7 @@
"PairingRequestsInfoUnderline" = "페어링 요청";
"PhoneNumberFormatErrorMessage" = "휴대폰 번호를 입력하세요.";
"PhoneNumberFormatErrorTitle" = "잘못된 전화번호 형식";
"PhoneVerificationErrorMessage" = "여러분의 세부 사항을 확인한 후 다시 시도하세요.";
"PhoneVerificationErrorMessage" = "여러분의 정보를 확인한 후 다시 시도하세요.";
"PhoneVerificationErrorTitle" = "전화번호 확인 중 오류";
"PINWillExpire" = "PIN이 %@후에 만료됩니다";
"ReceivePinIssue" = "PIN을 받는 데 문제가 있습니까?";
@ -499,9 +517,9 @@
"HelpTopics_VOLabel" = "도움말 항목. 기술 문제를 해결하고 수정하는 방법에 대한 안내";
"LatestNews_VOLabel" = "최신 뉴스 및 업데이트. 최신 코로나 바이러스 뉴스를 보려면 aus.gov.au를 방문하세요.";
"NotificationsDisabled_VOLabel" = "알림이 꺼졌습니다. COVIDSafe가 활성화되어 있지 않으면 알림을 받을 수 없습니다. \n알림 설정 변경하기";
"NotificationsEnabled_VOLabel" = "알림이 켜졌습니다. COVIDSafe가 활성화되어 있지 않으면 알림을 받게 됩니다. 알림 설정 변경하기";
"OS1b_TopParagraph_VOLabel" = "블루투스® 신호는 당신이 언제 다른 COVIDSafe 사용자 근처에 있었는지를 확인하는 데 사용됩니다. \n당신과 다른 COVIDSafe 사용자들 사이의 모든 근거리 접촉상황들은 근거리 접촉정보로 기록됩니다. 이 정보는 암호화되어 당신의 휴대폰에만 저장됩니다. \nCOVIDSafe 사용자로서 당신이 COVID-19에 양성 결과가 나오면, 주 또는 테리토리 보건 담당자가 당신에게 연락할 것입니다. 당신이 자신의 근거리 접촉정보를 보안성 높은 정보 저장 체계에 자발적으로 업로드할 수 있도록 도와줄 것입니다. \n또한 양성 결과가 나온 다른 COVIDSafe 사용자와 당신이 가까이 접촉한 경우, 주 또는 테리토리 보건 담당자가 당신에게 연락을 취할 수 있습니다. \n자세한 내용은 *도움말 항목* 페이지를 참조하세요.";
"permission_content_iOS_2_VOLabel" = "1. 블루투스\n2. 알림\n\nCOVIDSafe는 페어링 요청을 보내지 않습니다.";
"permission_content_iOS_VOLabel" = "COVIDSafe가 작동하려면 블루투스가 활성화되어 있어야 합니다. 알림을 켜면 COVIDSafe가 활성화되지 않았음을 알리는 업데이트를 받게 됩니다. \n\n '진행'을 선택하여 다음을 활성화 하십시오.";
"NotificationsEnabled_VOLabel" = "알림이 켜졌습니다. COVIDSafe가 활성화되어 있지 않으면 알림을 받게 됩니다. \n알림 설정 변경하기";
"OS1b_TopParagraph_VOLabel" = "블루투스 신호는 당신이 다른 COVIDSafe 사용자 근처에 있을때를 확인하는 데 사용됩니다. \n당신과 다른 COVIDSafe 사용자 간의 모든 근거리 접촉 상황들이 근거리 접촉정보로 기록됩니다. 이 정보는 암호화되어 당신의 휴대폰에만 저장됩니다. \nCOVIDSafe 사용자로서 당신이 COVID-19에 양성 결과가 나오면, 주 또는 테리토리 보건 담당자가 당신에게 연락할 것입니다. 당신이 자신의 근거리 접촉정보를 보안성 높은 정보 저장 체계에 자발적으로 업로드할 수 있도록 도와줄 것입니다. \n또한 양성 결과가 나온 다른 COVIDSafe 사용자와 당신이 가까이 접촉한 경우, 주 또는 테리토리 보건 담당자가 당신에게 연락을 취할 수 있습니다. \n자세한 내용은 *도움말 항목* 페이지를 참조하세요.";
"permission_content_iOS_2_VOLabel" = "1. 블루투스\n2. 알림\n3. 위치 서비스";
"permission_content_iOS_VOLabel" = "COVIDSafe가 제대로 실행되기 위해서는 블루투스가 활성화되어야 합니다. COVIDSafe는 페어링 요청을 보내지 않습니다.\n\n알림 기능을 활성화하면, COVIDSafe가 비활성화되어 있을 때 알림을 받게 됩니다.\n\nCOVIDSafe가 제대로 실행되기 위해서는 당신의 아이폰에서 위치 서비스 허용이 필요합니다. *COVIDSafe는 당신의 위치 정보를 추적하거나 저장하지 않습니다*\n\n활성화하려면 '진행'을 선택하세요.";
"ShareCovidSafe_VOLabel" = "COVIDSafe를 공유하세요. 다른 이들에게 참여를 권유하세요. 우리는 함께할 때 더 강해집니다.";
"UploadData_VOLabel" = "보건 담당자가 여러분에게 연락했습니까? 양성 검사 결과를 받은 경우에만 여러분의 정보를 업로드할 수 있습니다.";

View file

@ -1,8 +1,11 @@
"28_days" = "";
"7_days" = "";
"action_continue" = "ਜਾਰੀ ਰੱਖੋ";
"action_report_an_issue" = "ਸੱਮਸਿਆ ਦੀ ਸੂਚਨਾ ਦਿਓ";
"action_upload_done" = "ਕੀਤਾ";
"action_verify_invalid_pin" = "ਨਾਜਾਇਜ ਪਿੰਨ, ਕਿਰਪਾ ਕਰਕੇ ਸਿਹਤ ਅਧਿਕਾਰੀ ਨੂੰ ਤੁਹਾਨੂੰ ਇੱਕ ਹੋਰ ਪਿੰਨ ਭੇਜਣ ਲਈ ਕਹੋ।";
"action_verify_upload_pin" = "ਮੇਰੀ ਜਾਣਕਾਰੀ ਅੱਪਲੋਡ ਕਰੋ";
"active_cases" = "";
/* Figma page iOS Screens (05/06) */
"allow_bluetooth_call" = "COVIDSafe ਨੂੰ Bluetooth® ਤੱਕ ਪਹੁੰਚ ਦਿਓ";
/* Figma page iOS Screens (05/06) */
@ -16,6 +19,8 @@
"BluetoothOFF_content" = "ਆਪਣੇ ਫ਼ੋਨ ਦੇ Bluetooth® ਨੂੰ ਔਨ ਕਰੋ। ਜੇਕਰ ਡਿਸਪਲੇ ਹੈ ਤਾਂ 'ਨਵੇਂ ਕੁਨੈਕਸ਼ਨ ਆਉਣ ਦਿਓ - Allow New Connections' ਚੁਣੋ ।";
/* Figma page iOS Screens (05/06) */
"BluetoothON" = "Bluetooth®: ਔਨ";
"cases_28_days" = "";
"cases_7_days" = "";
"change_language" = "ਭਾਸ਼ਾ ਬਦਲੋ";
"change_language_content" = "COVIDSafe ਦੀ ਵਰਤੋਂ ਕਿਸੇ ਵੱਖਰੀ ਭਾਸ਼ਾ ਵਿੱਚ ਕਰਨ ਲਈ ਇਹ ਗਾਈਡ ਪੜ੍ਹੋ।";
"collection_message" = "COVIDSafe ਐਪ ਦੀ ਟ੍ਰਬਲਸ਼ੂਟ ਲਈ ਤੁਹਾਡੀ ਸਹਾਇਤਾ ਵਜੋਂ ਹੁਣ ਤੁਹਾਡੀ ਡਾਇਗਨੌਸਟਿਕ ਜਾਣਕਾਰੀ ਇਕੱਤਰ ਕਰਦਾ ਹੈ। *ਵਧੇਰੇ ਜਾਣੋ*";
@ -239,7 +244,7 @@
/* OnBoarding Data Privacy */
"data_privacy_headline" = "ਪੰਜੀਕਰਨ ਅਤੇ ਪ੍ਰਾਈਵੇਸੀ";
"deaths" = "ਮੌਤਾਂ";
"dialog_error_uploading_message" = "ਤੁਹਾਡੀ ਜਾਣਕਾਰੀ ਨੂੰ ਅੱਪਲੋਡ ਕਰਨ ਦੌਰਾਨ ਇੱਕ ਗਲਤੀ ਆਈ, ਕਿਰਪਾ ਕਰਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।";
"dialog_error_uploading_message" = "ਆਪਣਾ ਡਾਟਾ ਦੁਬਾਰਾ ਤੋਂ ਅੱਪਲੋਡ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ।\n\nਜੇ ਕਿਸੇ ਸੂਬੇ ਜਾਂ ਖੇਤਰ ਦਾ ਸਿਹਤ ਅਧਿਕਾਰੀ ਇਸ ਦੀ ਮੰਗ ਕਰਦਾ ਹੈ ਤਾਂ ਇਹ ਗਲਤੀ ਦਾ ਕੋਡ ਵੇਖੋ: \n%@";
"dialog_error_uploading_negative" = "ਰੱਦ";
"dialog_error_uploading_positive" = "ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ";
"dialog_uploading_message" = "ਤੁਹਾਡੀ COVIDSafe ਜਾਣਕਾਰੀ ਨੂੰ ਇਸ ਵੇਲੇ ਅੱਪਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ।\n\nਕਿਰਪਾ ਕਰਕੇ ਐਪ ਬੰਦ ਨਾ ਕਰੋ।";
@ -293,6 +298,7 @@
"home_set_complete_external_link_share_title" = "COVIDSafe ਸਾਂਝਾ ਕਰੋ";
"home_setup_help" = "ਸਹਾਇਤਾ";
"home_version_number_ios" = "ਵਰਜਨ %@, ਬਿਲਡ %@";
"hotspots_state_territory" = "";
"how_it_works_button" = "ਅੱਗੇ";
"how_it_works_content" = "Bluetooth ਸਿਗਨਲਾਂ ਦੀ ਵਰਤੋਂ ਇਹ ਦੱਸਦੀ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਕਿਸੇ ਹੋਰ COVIDSafe ਵਰਤੋਂਕਾਰ ਦੇ ਨੇੜੇ ਹੁੰਦੇ ਹੋ।\n\nਤੁਹਾਡੇ ਅਤੇ ਹੋਰ COVIDSafe ਵਰਤੋਂਕਾਰਾਂ ਵਿਚਕਾਰ ਨਜ਼ਦੀਕੀ ਸੰਪਰਕ ਦੀ ਹਰੇਕ ਇਕਾਈ ਨੂੰ ਨਜ਼ਦੀਕੀ ਸੰਪਰਕ ਜਾਣਕਾਰੀ ਲਈ ਨੋਟ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇਹ ਜਾਣਕਾਰੀ ਏਨਕ੍ਰਿਪਟਿਡ ਹੁੰਦੀ ਹੈ ਅਤੇ ਕੇਵਲ ਤੁਹਾਡੇ ਫ਼ੋਨ ਵਿੱਚ ਹੀ ਸਟੋਰ ਕੀਤੀ ਜਾਂਦੀ ਹੈ।\n\nਜੇ ਤੁਸੀਂ COVIDSafe ਵਰਤੋਂਕਾਰ ਵਜੋਂ ਕੋਵਿਡ-19 ਪਾਜ਼ੇਟਿਵ ਟੈਸਟ ਕਰਦੇ ਹੋ, ਤਾਂ ਕੋਈ ਪ੍ਰਾਂਤਿਕ ਜਾਂ ਹਲਕਾ ਸਿਹਤ ਅਧਿਕਾਰੀ ਤੁਹਾਡੇ ਨਾਲ ਸੰਪਰਕ ਕਰੇਗਾ। ਉਹ ਤੁਹਾਡੀ ਨਜ਼ਦੀਕੀ ਸੰਪਰਕ ਜਾਣਕਾਰੀ ਨੂੰ ਇੱਕ ਬੇਹੱਦ ਸੁਰੱਖਿਅਤ ਜਾਣਕਾਰੀ ਸਟੋਰੇਜ ਸਿਸਟਮ ਵਿੱਚ ਅੱਪਲੋਡ ਕਰਨ ਵਿੱਚ ਸਹਾਇਤਾ ਕਰਨਗੇ। \n\nਜੇ ਤੁਸੀਂ ਕਿਸੇ ਹੋਰ COVIDSafe ਵਰਤੋਂਕਾਰ ਜੋ ਪਾਜ਼ੇਟਿਵ ਟੈਸਟ ਹੋਇਆ ਸੀ ਦੇ ਸੰਪਰਕ ਵਿੱਚ ਆਏ ਤਾਂ ਭੀ ਪ੍ਰਾਂਤਿਕ ਜਾਂ ਹਲਕਾ ਸਿਹਤ ਅਧਿਕਾਰੀ ਤੁਹਾਡੇ ਨਾਲ ਸੰਪਰਕ ਕਰ ਸਕਦੇ ਹਨ|\n\nਵਧੇਰੇ ਜਾਣਕਾਰੀ ਲਈ ਕਿਰਪਾ ਕਰਕੇ *ਸਹਾਇਤਾ ਵਿਸ਼ੇ* ਪੰਨਾ ਦੇਖੋ।";
/* OnBoarding How it works */
@ -313,14 +319,18 @@
"IntroLabel" = "ਫੈਲਣ ਨੂੰ ਰੋਕਣ ਵਿੱਚ ਸਹਾਇਤਾ ਕਰੋ ਅਤੇ ਜ਼ਿੰਦਗੀਆਂ ਬਚਾਓ।";
"invalid_australian_phone_number_error_prompt" = "ਆਸਟਰੇਲੀਆਈ ਮੋਬਾਈਲ ਨੰਬਰਾਂ ਵਿੱਚ ਅਧਿਕਤਮ 10 ਅੰਕ ਹੁੰਦੇ ਹਨ।";
"invalid_norfolk_island_phone_number_error_prompt" = "ਨੋਰਫੋਕ ਆਈਲੈਂਡਜ਼ ਵਿਖੇ ਮੋਬਾਈਲ ਨੰਬਰਾਂ ਦੇ 5 ਤੋਂ 6 ਅੰਕ ਹੁੰਦੇ ਹਨ।";
"jwt_description" = "ਤੁਹਾਡੇ ਪੰਜੀਕਰਨ (ਰਜ਼ਿਸਟ੍ਰੇਸ਼ਨ) ਦੇ ਵਿਸਥਾਰਾਂ ਵਿੱਚ ਕੋਈ ਸਮੱਸਿਆ ਹੈ।";
"jwt_description" = "ਤੁਹਾਡੇ ਪੰਜੀਕਰਨ (ਰਜ਼ਿਸਟ੍ਰੇਸ਼ਨ) ਦੇ ਵੇਰਵਿਆਂ ਵਿੱਚ ਕੋਈ ਸਮੱਸਿਆ ਹੈ।";
"jwt_heading" = "ਕਿਰਪਾ ਕਰਕੇ ਦੁਬਾਰਾ ਰਜਿਸਟਰ ਕਰੋ";
"jwt_success" = "";
"jwt_success" = "ਰਜਿਸਟਰੇਸ਼ਨ ਦਾ ਸਫਲਤਾਪੂਰਵਕ ਨਵੀਨੀਕਰਨ ਕੀਤਾ ਗਿਆ ਹੈ ";
"loading_numbers" = "ਤਾਜ਼ਾ ਅੰਕੜੇ ਲੋਡ ਕੀਤੇ ਜਾ ਰਹੇ ਹਨ";
"locally_acquired" = "";
"location_off" = "ਲੋਕੇਸ਼ਨ: ਔਫ";
"location_off_description" = "COVIDSafe ਕੰਮ ਕਰ ਸਕੇ, ਇਸ ਦੇ ਲਈ ਤੁਹਾਡੇ iPhone ਨੂੰ ਲੋਕੇਸ਼ਨ ਆਗਿਆ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। COVIDSafe ਤੁਹਾਡੇ ਲੋਕੇਸ਼ਨ ਦੇ ਡਾਟਾ ਉੱਪਰ ਨਾ ਹੀ ਨਜ਼ਰ ਰੱਖਦਾ ਹੈ ਨਾ ਹੀ ਉਸ ਨੂੰ ਸਟੋਰ ਕਰਦਾ ਹੈ";
/* Splash Screen */
"migration_in_progress" = "COVIDSafe ਅੱਪਡੇਟ ਚੱਲ ਰਿਹਾ ਹੈ। \n\nਕਿਰਪਾ ਕਰਕੇ ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਤੁਹਾਡਾ ਫ਼ੋਨ ਤਦ ਤੱਕ ਬੰਦ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ ਜਦ ਤੱਕ ਅੱਪਡੇਟ ਪੂਰਾ ਨਹੀਂ ਹੋ ਜਾਂਦਾ।";
"national_numbers" = "ਰਾਸ਼ਟਰੀ ਅੰਕੜੇ";
"new_cases" = "ਪਿਛਲੇ 24 ਘੰਟਿਆਂ ਦੌਰਾਨ ਨਵੇਂ ਕੇਸ";
"new_cases_total" = "";
"new_south_wales" = "ਨਿਊ ਸਾਊਥ ਵੇਲਜ਼";
/* Figma page Android Screens (05/06) */
"norfolk_hint" = "ਉਦਾਹਰਣ ਵਜੋਂ: 51234";
@ -341,11 +351,11 @@
"numbers_no_internet" = "ਇਹ ਯਕੀਨੀ ਬਣਾਉਣ ਲਈ ਕਿ ਤੁਹਾਨੂੰ ਅੱਪਡੇਟਿਡ ਨੰਬਰ ਪ੍ਰਾਪਤ ਹੋਣ ਇੰਟਰਨੈੱਟ ਨਾਲ ਕਨੈਕਟ ਕਰੋ|";
"numbers_refresh" = "ਹੁਣ ਰਿਫਰੈਸ਼ ਕਰੋ";
"options_for_australia" = "ਆਸਟਰੇਲੀਆ ਲਈ ਵਿਕਲਪ";
"overseas_acquired" = "";
"permission_button" = "ਅੱਗੇ ਵਧੋ";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS" = "COVIDSafe ਲਈ ਬਲੂਟੁਥ ਅਨੇਬਲ ਕਰੋ| ਸੂਚਨਾਵਾਂ ਨੂੰ ਅਨੇਬਲ ਕਰਣ ਤੇ ਤੁਹਾਨੂੰ ਅੱਪਡੇਟ ਪ੍ਰਾਪਤ ਹੁੰਦੇ ਹਨ ਜੋ ਯਾਦ ਕਰਾਉਂਦੇ ਹਨ ਜਦੋਂ COVIDSafe ਐਕਟਿਵ ਨਹੀਂ ਹੁੰਦਾ|\n\nਚਲਾਉਣ ਲਈ 'ਅੱਗੇ ਵਧੋ' ਚੁਣੋ:";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS_2" = "1. Bluetooth®\n2. ਸੂਚਨਾਵਾਂ\n\nCOVIDSafe ਪੇਅਰਿੰਗ ਦੀਆਂ ਬੇਨਤੀਆਂ ਨਹੀਂ ਭੇਜਦੀ।";
"permission_content_iOS" = "COVIDSafe ਨੂੰ ਕੰਮ ਕਰਨ ਦੇ ਲਈ #ਬਲੂਟੁਥ# ਚੱਲਦਾ ਚਾਹੀਦਾ ਹੁੰਦਾ ਹੈ। COVIDSafe ਪੇਅਰਿਂਗ ਦੀਆਂ ਬੇਨਤੀਆਂ ਨਹੀਂ ਭੇਜਦੀ।\n\n#ਨੋਟੀਫੀਕੇਸ਼ਨਾਂ# ਨੂੰ ਚਾਲੂ ਕਰ ਕੇ, ਤੁਹਾਨੂੰ ਅੱਪਡੇਟ ਮਿਲਦੇ ਹਨ ਜੋ ਤੁਹਾਨੂੰ ਯਾਦ ਦਿਲਾਉਂਦੇ ਹਨ ਕਿ COVIDSafe ਐਕਟਿਵ ਨਹੀਂ ਹੈ।\n\nਤੁਹਾਡੇ ਆਈਫੋਨ ਨੂੰ #ਲੋਕੇਸ਼ਨ ਆਗਿਆ (ਪਰਮੀਸ਼ਨ)# ਚਾਹੀਦੀ ਹੁੰਦੀ ਹੈ, ਤਾਂ ਜੋ COVIDSafe ਚੱਲ ਸਕੇ। *COVIDSafe ਤੁਹਾਡੀ ਲੋਕੇਸ਼ਨ (ਤੁਸੀਂ ਕਿੱਥੇ ਹੋ) ਦੇ ਡਾਟੇ ਉੱਤੇ ਨਜ਼ਰ ਨਹੀਂ ਰੱਖਦਾ ਨਾ ਹੀ ਉਸ ਨੂੰ ਸਟੋਰ ਕਰਦਾ ਹੈ*\n\nਇਸ ਨੂੰ ਚਾਲੂ ਕਰਨ ਲਈ 'Proceed' (ਅੱਗੇ ਵੱਧੋ) ਦੀ ਚੋਣ ਕਰੋ:";
"permission_content_iOS_2" = "1. Bluetooth®\n2. ਸੂਚਨਾਵਾਂ (ਨੋਟੀਫੀਕੇਸ਼ਨਾਂ)\n3. ਲੋਕੇਸ਼ਨ ਸੇਵਾਵਾਂ";
/* OnBoarding Permission */
"permission_headline" = "ਐਪ ਸੈਟਿੰਗਾਂ";
"permission_success_button" = "ਕੀਤਾ";
@ -391,12 +401,15 @@
"share_this_app_content" = "ਕੋਵਿਡ-19 ਦੇ ਫੈਲਾਅ ਨੂੰ ਰੋਕਣ ਲਈ ਮੇਰੇ ਨਾਲ ਜੁੜੋ! COVIDSafe ਨੂੰ ਡਾਊਨਲੋਡ ਕਰੋ, ਜੋ ਕਿ ਆਸਟਰੇਲੀਆਈ ਸਰਕਾਰ ਦੀ ਐਪ ਹੈ। #COVID19 #coronavirusaustralia #stayhomesavelives https://covidsafe.gov.au";
"show" = "ਦਿਖਾਓ";
"south_australia" = "ਦੱਖਣੀ ਆਸਟਰੇਲੀਆ";
"state_number_heading" = "";
"stepCounter" = "ਸਟੈਪ %d ਦਾ%d";
"support" = "ਸਹਾਇਤਾ";
"support_content" = "ਜੋ ਮੁੱਦੇ ਸਹਾਇਤਾ ਵਿਸ਼ੇ ਦੁਆਰਾ ਕਵਰ ਨਹੀਂ ਹੁੰਦੇ ਉਨ੍ਹਾਂ ਲਈ ਸਹਾਇਤਾ";
"Support_VOLabel" = "ਸਹਾਇਤਾ। ਜੋ ਮੁੱਦੇ ਸਹਾਇਤਾ ਵਿਸ਼ੇ ਦੁਆਰਾ ਕਵਰ ਨਹੀਂ ਹੁੰਦੇ ਉਨ੍ਹਾਂ ਲਈ ਸਹਾਇਤਾ";
"tasmania" = "ਤਸਮਾਨੀਆ";
"total" = "";
"total_confirmed_cases" = "ਕੁੱਲ ਪੁਸ਼ਟੀ ਕੀਤੇ ਕੇਸ";
"total_deaths" = "";
"total_per_state" = "ਪ੍ਰਾਂਤ ਅਤੇ ਹਲਕੇ ਅਨੁਸਾਰ ਕੁੱਲ ਪੁਸ਼ਟੀ ਕੀਤੇ ਕੇਸ";
"UILaunchStoryboardName" = "";
"under_sixteen_content" = "ਮੈਂ ਆਸਟਰੇਲੀਆਈ ਸਰਕਾਰ ਦੇ ਸਿਹਤ ਵਿਭਾਗ ਦੇ ਸਕੱਤਰ ਦੁਆਰਾ ਕੀਤੇ ਕਨੂੰਨੀ ਨਿਰਣੇ ਦੇ ਤਹਿਤ, ਡੇਟਾ ਸਟੋਰ ਪ੍ਰਸ਼ਾਸ਼ਕ ਵਜੋਂ ਡਿਜ਼ਿਟਲ ਤਬਦੀਲੀ ਏਜੰਸੀ ਨੂੰ ਮੇਰੇ ਮਾਪੇ ਜਾਂ ਸਰਪ੍ਰਸਤ ਦੀਆਂ ਸਹਿਮਤੀਆਂ ਦੀ ਪੁਸ਼ਟੀ ਕਰਦਾ/ਕਰਦੀ ਹਾਂ:";
@ -412,10 +425,15 @@
"update_available_dismiss_btn" = "ਮੈਨੂੰ ਬਾਅਦ ਵਿੱਚ ਯਾਦ ਕਰਵਾਓ";
"update_available_message_ios" = "ਅਸੀਂ COVIDSafe ਵਿੱਚ ਸੁਧਾਰ ਕਰਦੇ ਆ ਰਹੇ ਹਾਂ| ਐਪ ਸਟੋਰ ਰਾਹੀਂ ਅਪਡੇਟ ਕਰੋ।";
"update_available_title" = "ਅੱਪਡੇਟ ਉਪਲਬਧ ਹੈ|";
"update_description" = "";
"update_description_VO" = "";
"update_heading" = "ਲੋਕੇਸ਼ਨ ਸੇਵਾਵਾਂ ਨੂੰ ਚਾਲੂ (Enable) ਕਰ ਦਿਓ";
"update_modal_button" = "ਅੱਗੇ ਵਧੋ";
"upload_answer_no" = "ਨਹੀਂ";
/* Upload flow */
"upload_answer_yes" = "ਹਾਂ";
"upload_consent_button" = "ਮੈਂ ਸਹਿਮਤ ਹਾਂ";
"upload_fail_heading" = "ਤੁਹਾਡਾ ਅੱਪਲੋਡ ਅਸਫਲ ਰਿਹਾ ਹੈ";
"upload_failed" = "ਅੱਪਲੋਡ ਅਸਫਲ";
"upload_finished_header" = "COVIDSafe ਦੇ ਫੈਲਾਅ ਨੂੰ ਰੋਕਣ ਵਿੱਚ ਸਹਾਇਤਾ ਲਈ ਤੁਹਾਡਾ ਧੰਨਵਾਦ!";
"upload_finished_sub_header" = "ਤੁਸੀਂ ਆਪਣੀ ਜਾਣਕਾਰੀ ਨੂੰ COVIDSafe ਦੇ ਬੇਹੱਦ ਸੁਰੱਖਿਅਤ ਸਟੋਰੇਜ ਸਿਸਟਮ 'ਤੇ ਸਫਲਤਾ ਪੂਰਵਕ ਅੱਪਲੋਡ ਕਰ ਦਿੱਤਾ ਹੈ।\n\nਰਾਜ ਜਾਂ ਹਲਕਾ ਸਿਹਤ ਅਧਿਕਾਰੀ ਹੋਰ COVIDSafe ਵਰਤੋਂਕਾਰਾਂ ਨੂੰ ਸੂਚਿਤ ਕਰਨਗੇ ਜਿੰਨ੍ਹਾਂ ਨੇ ਤੁਹਾਡੇ ਨਾਲ ਨਜ਼ਦੀਕੀ ਸੰਪਰਕ ਦੀਆਂ ਉਦਾਹਰਨਾਂ ਰਿਕਾਰਡ ਕੀਤੀਆਂ ਹਨ। ਤੁਹਾਡੀ ਪਛਾਣ ਹੋਰਨਾਂ ਵਰਤੋਂਕਾਰਾਂ ਵਾਸਤੇ ਗੁੰਮਨਾਮ ਰਹੇਗੀ।";
@ -501,7 +519,7 @@
"NotificationsDisabled_VOLabel" = "ਸੂਚਨਾਵਾਂ ਬੰਦ (ਡਿਸੇਬਲੇਡ) ਹਨ। ਜੇ COVIDSafe ਐਕਟਿਵ ਨਹੀਂ ਤਾਂ ਤੁਹਾਨੂੰ ਕੋਈ ਸੂਚਨਾ ਨਹੀਂ ਮਿਲੇਗੀ|\nਸੂਚਨਾ ਸੈਟਿੰਗਜ਼ ਨੂੰ ਬਦਲੋ";
"NotificationsEnabled_VOLabel" = "ਸੂਚਨਾਵਾਂ ਚਾਲੂ (ਅਨੇਬਲਡ) ਹਨ। ਜੇ COVIDSafe ਐਕਟਿਵ ਨਹੀਂ ਤਾਂ ਤੁਹਾਨੂੰ ਸੂਚਨਾ ਮਿਲੇਗੀ|\nਸੂਚਨਾ ਸੈਟਿੰਗਜ਼ ਨੂੰ ਬਦਲੋ";
"OS1b_TopParagraph_VOLabel" = "ਬਲੂਟੁੱਥ ਸਿਗਨਲਾਂ ਦੀ ਵਰਤੋਂ ਇਹ ਦੱਸਦੀ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਕਿਸੇ ਹੋਰ COVIDSafe ਵਰਤੋਂਕਾਰ ਦੇ ਨੇੜੇ ਹੁੰਦੇ ਹੋ।\nਤੁਹਾਡੇ ਅਤੇ ਹੋਰ COVIDSafe ਵਰਤੋਂਕਾਰਾਂ ਵਿਚਕਾਰ ਨਜ਼ਦੀਕੀ ਸੰਪਰਕ ਦੀ ਹਰੇਕ ਉਦਾਹਰਨ ਨੂੰ ਨਜ਼ਦੀਕੀ ਸੰਪਰਕ ਜਾਣਕਾਰੀ ਲਈ ਨੋਟ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇਹ ਜਾਣਕਾਰੀ ਏਨਕ੍ਰਿਪਟਿਡ ਹੁੰਦੀ ਹੈ ਅਤੇ ਕੇਵਲ ਤੁਹਾਡੇ ਫ਼ੋਨ ਵਿੱਚ ਹੀ ਸਟੋਰ ਕੀਤੀ ਜਾਂਦੀ ਹੈ।\nਜੇ COVIDSafe ਵਰਤੋਂਕਾਰ ਵਜੋਂ ਕੋਵਿਡ-19 ਲਈ ਤੁਹਾਡਾ ਟੈਸਟ ਪਾਜੇਟਿਵ ਆਉਂਦਾ ਹੈ, ਤਾਂ ਕੋਈ ਰਾਜ ਜਾਂ ਹਲਕਾ ਸਿਹਤ ਅਧਿਕਾਰੀ ਤੁਹਾਡੇ ਨਾਲ ਸੰਪਰਕ ਕਰੇਗਾ। ਉਹ ਤੁਹਾਡੀ ਨਜ਼ਦੀਕੀ ਸੰਪਰਕ ਜਾਣਕਾਰੀ ਨੂੰ ਇੱਕ ਬੇਹੱਦ ਸੁਰੱਖਿਅਤ ਜਾਣਕਾਰੀ ਸਟੋਰੇਜ ਸਿਸਟਮ ਵਿੱਚ ਅੱਪਲੋਡ ਕਰਨ ਵਿੱਚ ਸਹਾਇਤਾ ਕਰਨਗੇ।\nਜੇ ਤੁਸੀਂ ਕਿਸੇ ਹੋਰ COVIDSafe ਵਰਤੋਂਕਾਰ ਜਿਸਦਾ ਪਾਜ਼ੇਟਿਵ ਟੈਸਟ ਹੋਇਆ ਸੀ, ਦੇ ਸੰਪਰਕ ਵਿੱਚ ਆਏ ਹੋ ਤਾਂ ਵੀ ਰਾਜ ਜਾਂ ਹਲਕਾ ਸਿਹਤ ਅਧਿਕਾਰੀ ਤੁਹਾਡੇ ਨਾਲ ਸੰਪਰਕ ਕਰ ਸਕਦੇ ਹਨ|\nਵਧੇਰੇ ਜਾਣਕਾਰੀ ਲਈ ਕਿਰਪਾ ਕਰਕੇ ਸਹਾਇਤਾ ਵਿਸ਼ੇ ਪੰਨਾ ਦੇਖੋ।";
"permission_content_iOS_2_VOLabel" = "1. ਬਲੂਟੁੱਥ\n2. ਸੂਚਨਾਵਾਂ\n\nCOVIDSafe ਪੇਅਰਿੰਗ ਦੀਆਂ ਬੇਨਤੀਆਂ ਨਹੀਂ ਭੇਜਦੀ।";
"permission_content_iOS_VOLabel" = "COVIDSafe ਦੇ ਕੰਮ ਕਰਨ ਲਈ ਬਲੂਟੁਥ ਚਾਲੂ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ| ਸੂਚਨਾਵਾਂ ਨੂੰ ਚਾਲੂ ਕਰਣ ਤੇ ਤੁਹਾਨੂੰ ਅੱਪਡੇਟ ਪ੍ਰਾਪਤ ਹੁੰਦੇ ਹਨ ਜੋ ਯਾਦ ਕਰਾਉਂਦੇ ਹਨ ਜਦੋਂ COVIDSafe ਐਕਟਿਵ ਨਹੀਂ ਹੁੰਦਾ|\n\nਚਲਾਉਣ ਲਈ 'ਅੱਗੇ ਵਧੋ' ਚੁਣੋ:";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth®\n2. ਸੂਚਨਾਵਾਂ (ਨੋਟੀਫੀਕੇਸ਼ਨਾਂ)\n3. ਲੋਕੇਸ਼ਨ ਸੇਵਾਵਾਂ";
"permission_content_iOS_VOLabel" = "COVIDSafe ਨੂੰ ਕੰਮ ਕਰਨ ਦੇ ਲਈ ਬਲੂਟੁਥ ਚੱਲਦਾ ਚਾਹੀਦਾ ਹੁੰਦਾ ਹੈ। COVIDSafe ਪੇਅਰਿਂਗ ਦੀਆਂ ਬੇਨਤੀਆਂ ਨਹੀਂ ਭੇਜਦੀ।\n\nਨੋਟੀਫੀਕੇਸ਼ਨਾਂ ਨੂੰ ਚਾਲੂ ਕਰ ਕੇ, ਤੁਹਾਨੂੰ ਅੱਪਡੇਟ ਮਿਲਦੇ ਹਨ ਜੋ ਤੁਹਾਨੂੰ ਯਾਦ ਦਿਲਾਉਂਦੇ ਹਨ ਕਿ COVIDSafe ਐਕਟਿਵ ਨਹੀਂ ਹੈ।\n\nਤੁਹਾਡੇ ਆਈਫੋਨ ਨੂੰ ਲੋਕੇਸ਼ਨ ਆਗਿਆ (ਪਰਮੀਸ਼ਨ) ਚਾਹੀਦੀ ਹੁੰਦੀ ਹੈ, ਤਾਂ ਜੋ COVIDSafe ਚੱਲ ਸਕੇ। *COVIDSafe ਤੁਹਾਡੀ ਲੋਕੇਸ਼ਨ (ਤੁਸੀਂ ਕਿੱਥੇ ਹੋ) ਦੇ ਡਾਟੇ ਉੱਤੇ ਨਜ਼ਰ ਨਹੀਂ ਰੱਖਦਾ ਨਾ ਹੀ ਉਸ ਨੂੰ ਸਟੋਰ ਕਰਦਾ ਹੈ*\n\nਇਸ ਨੂੰ ਚਾਲੂ ਕਰਨ ਲਈ 'Proceed' (ਅੱਗੇ ਵੱਧੋ) ਦੀ ਚੋਣ ਕਰੋ:";
"ShareCovidSafe_VOLabel" = "COVIDSafe ਸਾਂਝਾ ਕਰੋ। ਹੋਰਨਾਂ ਨੂੰ ਸਹਾਇਤਾ ਲਈ ਬੁਲਾਓ । ਇਕੱਠੇ ਅਸੀਂ ਵਧੇਰੇ ਮਜ਼ਬੂਤ ਹਾਂ।";
"UploadData_VOLabel" = "ਕੀ ਕਿਸੇ ਸਿਹਤ ਅਧਿਕਾਰੀ ਨੇ ਤੁਹਾਡੇ ਨਾਲ ਸੰਪਰਕ ਕੀਤਾ ਹੈ? ਤੁਸੀਂ ਆਪਣੀ ਜਾਣਕਾਰੀ ਨੂੰ ਕੇਵਲ ਤਾਂ ਹੀ ਅੱਪਲੋਡ ਕਰ ਸਕਦੇ ਹੋ ਜੇਕਰ ਤੁਹਾਡਾ ਟੈਸਟ ਪਾਜ਼ੇਟਿਵ ਆਇਆ ਹੋਵੇ|";

View file

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>GitBranchInfo</key>
<string>feature/api_auth_int - Commit: 43b7ab0</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>COVIDSafe exchanges Bluetooth® signals with nearby phones running the same app. These signals contain an anonymised ID, which is encrypted and changes continually to ensure your privacy.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>COVIDSafe exchanges Bluetooth® signals with nearby phones running the same app. These signals contain an anonymised ID, which is encrypted and changes continually to ensure your privacy.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>COVIDSafe exchanges Bluetooth® signals with nearby phones running the same app. Location permission is required for best performance, your location is not recorded to ensure your privacy.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>COVIDSafe exchanges Bluetooth® signals with nearby phones running the same app. Location permission is required for best performance, your location is not recorded to ensure your privacy.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>COVIDSafe exchanges Bluetooth® signals with nearby phones running the same app. Location permission is required for best performance, your location is not recorded to ensure your privacy.</string>
<key>TRACER_SVC_ID</key>
<string>${SERVICE_UUID}</string>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>bluetooth-peripheral</string>
<string>location</string>
<string>remote-notification</string>
</array>
<key>UIFileSharingEnabled</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen_en</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIRequiresFullScreen</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
</dict>
</plist>

View file

@ -1,8 +1,11 @@
"28_days" = "";
"7_days" = "";
"action_continue" = "Devam";
"action_report_an_issue" = "Sorun bildir";
"action_upload_done" = "Bitti";
"action_verify_invalid_pin" = "Geçersiz PIN, lütfen sağlık yetkilisinden size başka bir PIN göndermesini isteyiniz.";
"action_verify_upload_pin" = "Bilgilerimi sisteme yükle";
"active_cases" = "";
/* Figma page iOS Screens (05/06) */
"allow_bluetooth_call" = "COVIDSafe'in Bluetooth®'a erişmesine izin verin";
/* Figma page iOS Screens (05/06) */
@ -16,6 +19,8 @@
"BluetoothOFF_content" = "Telefonunuzun Bluetooth® özelliğini açın. Görüntüleniyorsa 'Yeni Bağlantılara İzin Ver'i seçin.";
/* Figma page iOS Screens (05/06) */
"BluetoothON" = "Bluetooth®: AÇIK";
"cases_28_days" = "";
"cases_7_days" = "";
"change_language" = "Dili değiştir";
"change_language_content" = "COVIDSafe'i farklı bir dilde kullanmak için ilgili kılavuzu okuyunuz.";
"collection_message" = "COVIDSafe artık uygulamanızla ilgili sorunları gidermenize yardımcı olmak için tanılama bilgileri toplamaktadır. *Daha fazla bilgi edin*";
@ -239,7 +244,7 @@
/* OnBoarding Data Privacy */
"data_privacy_headline" = "Kayıt ve gizlilik";
"deaths" = "Ölümler";
"dialog_error_uploading_message" = "Bilgileriniz sisteme yüklerken bir hata oluştu, lütfen tekrar deneyin.";
"dialog_error_uploading_message" = "Verilerinizi karşıya yüklemeyi yeniden deneyin.\n\nBir eyalet veya bölge sağlık yetkilisi isterse bu hata koduna başvurun: \n%@";
"dialog_error_uploading_negative" = "İptal";
"dialog_error_uploading_positive" = "Tekrar deneyin";
"dialog_uploading_message" = "COVIDSafe bilgileriniz şu anda sisteme yükleniyor. \n\n Lütfen uygulamayı kapatmayın.";
@ -293,6 +298,7 @@
"home_set_complete_external_link_share_title" = "COVIDSafe'i paylaşın";
"home_setup_help" = "Yardım";
"home_version_number_ios" = "Sürüm %@, Derleme %@";
"hotspots_state_territory" = "";
"how_it_works_button" = "Sonraki";
"how_it_works_content" = "Bluetooth® sinyalleri başka bir COVIDSafe kullanıcısının yakınınızda olduğunuzu belirlemek için kullanılır. \n\nSizinle diğer COVIDSafe kullanıcıları arasındaki her yakın temas, yakın temas bilgileri oluşturmak için not edilir. Bu bilgiler şifrelenir ve yalnızca telefonunuzda saklanır. \n\nCOVIDSafe kullanıcısı olarak COVID-19 testinizin pozitif çıkması halinde bir eyalet veya bölge sağlık yetkilisi sizinle iletişime geçecektir. İletişim bilgilerinizin son derece güvenli bir bilgi depolama sistemine gönüllü olarak yüklemenize yardımcı olacaklardır.\n\nEyalet veya bölge sağlık yetkilileri testi pozitif çıkan başka bir COVIDSafe kullanıcısıyla yakın temas kurduğunuz hallerde sizinle iletişime geçebilir.\n\nDaha fazla bilgi için lütfen *Yardım Konuları* sayfasına bakınız.";
/* OnBoarding How it works */
@ -315,12 +321,16 @@
"invalid_norfolk_island_phone_number_error_prompt" = "Norfolk Adası'ndaki cep telefonu numaraları 5 ila 6 rakam içerir.";
"jwt_description" = "Kayıt bilgilerinizle ilgili bir sorun var.";
"jwt_heading" = "Lütfen tekrar kayıt olun";
"jwt_success" = "";
"jwt_success" = "Kayıt başarıyla yenilendi";
"loading_numbers" = "En son sayılar yükleniyor";
"locally_acquired" = "";
"location_off" = "Konum: KAPALI";
"location_off_description" = "iPhoneunuz, COVIDSafein çalışması için Konum iznine ihtiyaç duyar. COVIDSafe konum verilerinizi İZLEMEZ veya SAKLAMAZ.";
/* Splash Screen */
"migration_in_progress" = " COVIDSafe güncellemesi devam ediyor. \n\n Lütfen güncelleme tamamlanana kadar telefonunuzu kapatmayın.";
"national_numbers" = "Ulusal sayılar";
"new_cases" = "Son 24 saatte çıkan yeni vakalar";
"new_cases_total" = "";
"new_south_wales" = "Yeni Güney Galler";
/* Figma page Android Screens (05/06) */
"norfolk_hint" = "Örneğin: 51234";
@ -341,11 +351,11 @@
"numbers_no_internet" = "Güncellenen sayıları aldığınızdan emin olmak için internete bağlanın.";
"numbers_refresh" = "Şimdi yenile";
"options_for_australia" = "Avustralya için seçenekler";
"overseas_acquired" = "";
"permission_button" = "İlerle";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS" = "COVIDSafe'nin çalışması için Bluetooth® etkinleştirilmiş olmalıdır. Bildirimleri etkinleştirerek COVIDSafe aktif olmadığında size hatırlatan güncellemeleri alabilirsiniz. \n\nEtkinleştirmek için 'Devam Et'i seçin:";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS_2" = "1. Bluetooth®\n2. Bildirimler \n\nCOVIDSafe eşleştirme istekleri göndermez.";
"permission_content_iOS" = "COVIDSafein çalışması için #Bluetooth#un etkinleştirilmesi gerekir. COVIDSafe, eşleştirme istekleri göndermez. \n\n#Bildirimleri# etkinleştirerek, COVIDSafe etkin olmadığında hatırlatmak amaçlı güncellemeler alırsınız. \n\nİPhoneunuzun COVIDSafein çalışması için #Konum iznine# ihtiyacı vardır. *COVIDSafe konum verilerinizi İZLEMEZ veya SAKLAMAZ* \n\nEtkinleştirmek için Devam Eti seçin:";
"permission_content_iOS_2" = "1. Bluetooth\n2. Bildirimler \n3. Konum Hizmetleri";
/* OnBoarding Permission */
"permission_headline" = "Uygulama ayarları";
"permission_success_button" = "Bitti";
@ -391,12 +401,15 @@
"share_this_app_content" = "COVID-19'un yayılmasını durdurmada bana katılın! Avustralya Hükümeti'nin bir uygulaması olan COVIDSafe'i indirin. # COVID19 #coronavirusaustralia #stayhomesavelives https://covidsafe.gov.au";
"show" = "Göster";
"south_australia" = "Güney Avustralya";
"state_number_heading" = "";
"stepCounter" = "Adım %d / %d";
"support" = "Destek";
"support_content" = "Yardım konuları bölümünde belirtilmemiş hususlarda destek için";
"Support_VOLabel" = "Destek. Yardım konuları bölümünde belirtilmemiş hususlarda destek için";
"tasmania" = "Tazmanya";
"total" = "";
"total_confirmed_cases" = "Toplam doğrulanmış vakalar";
"total_deaths" = "";
"total_per_state" = "Eyalet ve bölgelere göre toplam doğrulanmış vakalar";
"UILaunchStoryboardName" = "";
"under_sixteen_content" = "Ebeveynimin ya da vasimin aşağıdaki bilgileri toplayan Dijital Dönüşüm Ajansı'na veri deposu yöneticisi olarak Avustralya Hükümeti Sağlık Bakanının yasal olarak belirlediğine uygun olarak rızası olduğunu onaylıyorum:";
@ -412,10 +425,15 @@
"update_available_dismiss_btn" = "Daha sonra hatırlat";
"update_available_message_ios" = "COVIDSafe'de iyileştirmeler yapmaktayız. App Store üzerinden güncelleme yapın.";
"update_available_title" = "Güncelleme mevcut!";
"update_description" = "";
"update_description_VO" = "";
"update_heading" = "Konum Hizmetlerini Etkinleştir";
"update_modal_button" = "İlerle";
"upload_answer_no" = "Hayır";
/* Upload flow */
"upload_answer_yes" = "Evet";
"upload_consent_button" = "Kabul ediyorum";
"upload_fail_heading" = "Karşıya yükleme başarısız";
"upload_failed" = "Karşıya yükleme başarısız";
"upload_finished_header" = "COVID-19'un yayılmasını durdurmaya yardım ettiğiniz için teşekkür ederiz!";
"upload_finished_sub_header" = "Bilgilerinizi yüksek güvenlikli COVIDSafe depolama sistemine başarıyla yüklediniz. \n\nEyalet veya bölge sağlık yetkilileri sizinle yakın temas durumlarını kaydeden diğer COVIDSafe kullanıcılarını bilgilendirecektir. Kimliğiniz diğer kullanıcılar için gizli kalacaktır.";
@ -501,7 +519,7 @@
"NotificationsDisabled_VOLabel" = "Bildirimler devre dışı bırakıldı. COVIDSafe aktif olmadığında bildirim almayacaksınız. \nBildirim ayarlarını değiştirin";
"NotificationsEnabled_VOLabel" = "Bildirimler etkinleştirildi. COVIDSafe aktif olmadığı durumda bildirim alacaksınız. \nBildirim ayarlarını değiştirin";
"OS1b_TopParagraph_VOLabel" = "Bluetooth sinyalleri başka bir COVIDSafe kullanıcısının yakınınızda olduğunuzu belirlemek için kullanılır. \nSizinle diğer COVIDSafe kullanıcıları arasındaki her yakın temas, yakın temas bilgileri oluşturmak için not edilir. Bu bilgiler şifrelenir ve yalnızca telefonunuzda saklanır. \nCOVIDSafe kullanıcısı olarak COVID-19 testinizin pozitif çıkması durumunda eyalet veya bölge sağlık yetkilisi sizinle iletişime geçecektir. Yakın iletişim bilgilerinizin son derece güvenli bir bilgi depolama sistemine gönüllü olarak yüklemenize yardımcı olacaklardır. \nEyalet veya bölge sağlık yetkilileri testi pozitif çıkan başka bir COVIDSafe kullanıcısıyla yakın temas kurdugunuz durumda yine sizinle iletişime geçebilir. \nDaha fazla bilgi için lütfen Yardım Konuları sayfasına bakınız.";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth\n2. Bildirimler \n\nCOVIDSafe eşleştirme istekleri göndermez.";
"permission_content_iOS_VOLabel" = "COVIDSafe'nin çalışabilmesi için Bluetooth etkinleştirilmiş olmalıdır. Bildirimleri etkinleştirerek COVIDSafe aktif olmadığında size hatırlatan güncellemeleri alabilirsiniz. \n\nEtkinleştirmek için 'Devam Et'i seçiniz:";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth\n2. Bildirimler \n3. Konum Hizmetleri";
"permission_content_iOS_VOLabel" = "COVIDSafein çalışması için Bluetoothun etkinleştirilmesi gerekir. COVIDSafe, eşleştirme istekleri göndermez. \n\nBildirimleri etkinleştirerek, COVIDSafe etkin olmadığında hatırlatmak amaçlı güncellemeler alırsınız. \n\nİPhoneunuzun COVIDSafein çalışması için Konum iznine ihtiyacı vardır. *COVIDSafe konum verilerinizi İZLEMEZ veya SAKLAMAZ* \n\nEtkinleştirmek için Devam Eti seçin:";
"ShareCovidSafe_VOLabel" = "COVIDSafe'i paylaşın. Başkalarını yardıma davet edin. Birlikte daha da güçlüyüz.";
"UploadData_VOLabel" = "Bir sağlık yetkilisi sizinle iletişime geçti mi? Bilgilerinizi yalnızca testiniz pozitifse sisteme yükleyebilirsiniz.";

View file

@ -1,8 +1,11 @@
"28_days" = "";
"7_days" = "";
"action_continue" = "Tiếp tục";
"action_report_an_issue" = "Báo cáo sự cố";
"action_upload_done" = "Hoàn tất";
"action_verify_invalid_pin" = "MÃ PIN không hợp lệ, vui lòng yêu cầu nhân viên y tế gửi cho bạn một mã PIN khác.";
"action_verify_upload_pin" = "Đăng tải thông tin của tôi";
"active_cases" = "";
/* Figma page iOS Screens (05/06) */
"allow_bluetooth_call" = "Cho phép COVIDSafe truy cập vào Bluetooth®";
/* Figma page iOS Screens (05/06) */
@ -16,6 +19,8 @@
"BluetoothOFF_content" = "Bật Bluetooth® trên điện thoại của bạn. Chọn Cho phép Kết nối mới nếu có hiển thị.";
/* Figma page iOS Screens (05/06) */
"BluetoothON" = "Bluetooth®: BẬT";
"cases_28_days" = "";
"cases_7_days" = "";
"change_language" = "Thay đổi ngôn ngữ";
"change_language_content" = "Đọc hướng dẫn về cách sử dụng COVIDSafe bằng ngôn ngữ khác.";
"collection_message" = "COVIDSafe hiện đang thu thập thông tin chẩn đoán để giúp bạn khắc phục sự cố với Ứng dụng của mình. *Tìm hiểu thêm *";
@ -239,7 +244,7 @@
/* OnBoarding Data Privacy */
"data_privacy_headline" = "Đăng ký và bảo mật";
"deaths" = "Tử vong";
"dialog_error_uploading_message" = "Bị lỗi trong khi đăng tải thông tin của bạn, vui lòng thử lại.";
"dialog_error_uploading_message" = "Hãy thử đăng tải lại dữ liệu của bạn. \n\n Tham khảo mã lỗi này nếu nhân viên y tế của tiểu bang hoặc vùng lãnh thổ yêu cầu: \n%@";
"dialog_error_uploading_negative" = "Hủy bỏ";
"dialog_error_uploading_positive" = "Thử lại";
"dialog_uploading_message" = "Thông tin COVIDSafe của bạn hiện đang được đăng tải. \n\n Vui lòng không đóng ứng dụng.";
@ -247,7 +252,7 @@
"done_success" = "Hoàn tất";
"enter_number_button" = "Nhận mã PIN";
"enter_number_content" = "Chúng tôi sẽ gửi cho bạn mã PIN gồm 6 chữ số để xác minh số điện thoại của bạn.";
"enter_number_headline" = "Nhập sô điện thoại di động của bạn";
"enter_number_headline" = "Nhập s điện thoại di động của bạn";
"enter_number_relative" = "Đăng ký dùm bạn bè hoặc người thân? \n\nHọ cần phải đăng ký bằng máy điện thoại và số điện thoại của riêng họ để COVIDSafe có thể hoạt động cho họ.";
"enter_pin_button" = "Xác minh";
/* OnBoarding Enter PIN */
@ -293,6 +298,7 @@
"home_set_complete_external_link_share_title" = "Chia sẻ COVIDSafe";
"home_setup_help" = "Trợ giúp";
"home_version_number_ios" = "Phiên bản %@ , Xây dựng %@";
"hotspots_state_territory" = "";
"how_it_works_button" = "Tiếp theo";
"how_it_works_content" = "Tín hiệu Bluetooth® được sử dụng để xác định khi bạn đang ở gần người dùng COVIDSafe khác. \n\nMọi trường hợp tiếp xúc gần giữa bạn và những người dùng COVIDSafe khác đều được ghi nhận để tạo dữ liệu tiếp xúc. Thông tin này được mã hóa và chỉ được lưu trữ trong điện thoại của bạn. \n\nNếu bạn xét nghiệm dương tính với COVID-19 và là người dùng COVIDSafe, nhân viên y tế của tiểu bang hoặc lãnh thổ sẽ liên hệ với bạn. Họ sẽ tự nguyện hỗ trợ đăng tải dữ liệu tiếp xúc của bạn lên hệ thống lưu trữ thông tin có độ an toàn cao.\n\nCác nhân viên y tế của tiểu bang hoặc lãnh thổ cũng có thể liên hệ với bạn nếu bạn đã tiếp xúc gần với một người dùng COVIDSafe khác có kết quả xét nghiệm dương tính. \n\nĐể biết thêm thông tin, vui lòng tham khảo trang *Chủ đề Trợ giúp*";
/* OnBoarding How it works */
@ -315,12 +321,16 @@
"invalid_norfolk_island_phone_number_error_prompt" = "Số điện thoại di động ở Đảo Norfolk chứa từ 5 đến 6 chữ số.";
"jwt_description" = "Có vấn đề với thông tin đăng ký của bạn.";
"jwt_heading" = "Vui lòng đăng ký lại";
"jwt_success" = "";
"jwt_success" = "Đăng ký gia hạn thành công";
"loading_numbers" = "Đang tải các số mới nhất";
"locally_acquired" = "";
"location_off" = "Vị trí: TẮT";
"location_off_description" = "iPhone của bạn yêu cầu quyền truy cập Vị trí để COVIDSafe hoạt động. COVIDSafe KHÔNG theo dõi hoặc lưu trữ dữ liệu vị trí của bạn.";
/* Splash Screen */
"migration_in_progress" = "COVIDSafe trong tiến trình cập nhật. \n\nVui lòng đảm bảo điện thoại của bạn không bị tắt cho đến khi cập nhật hoàn tất.";
"national_numbers" = "Số ca nhiễm trên toàn quốc";
"new_cases" = "Các ca mới trong vòng 24 giờ qua";
"new_cases_total" = "";
"new_south_wales" = "New South Wales";
/* Figma page Android Screens (05/06) */
"norfolk_hint" = "Ví dụ: 51234";
@ -341,11 +351,11 @@
"numbers_no_internet" = "Kết nối với internet để đảm bảo bạn nhận được các số cập nhật.";
"numbers_refresh" = "Làm mới ngay";
"options_for_australia" = "Tùy chọn cho Úc";
"overseas_acquired" = "";
"permission_button" = "Kích hoạt";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS" = "Cần bật Bluetooth® để COVIDSafe hoạt động. Khi bật Thông báo, bạn nhận được các cập nhật để nhắc nhở khi COVIDSafe không hoạt động. \n\n Chọn 'Kích hoạt' để bật:";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS_2" = "1. Bluetooth® \n2. Thông báo \n\n COVIDSafe không gửi yêu cầu kết nối.";
"permission_content_iOS" = "COVIDSafe cần bật #Bluetooth# để hoạt động. COVIDSafe không gửi yêu cầu ghép nối. \n\nKhi bật #Thông báo#, bạn sẽ nhận được thông tin cập nhật nhắc bạn khi COVIDSafe không hoạt động. \n\niPhone của bạn cần #Quyền truy cập vị trí# để COVIDSafe hoạt động. *COVIDSafe KHÔNG theo dõi hoặc lưu trữ dữ liệu vị trí của bạn* \n\n Chọn 'Kích hoạt' để bật:";
"permission_content_iOS_2" = "1. Bluetooth\n 2. Thông báo\n 3. Dịch vụ Vị trí";
/* OnBoarding Permission */
"permission_headline" = "Cài đặt ứng dụng";
"permission_success_button" = "Hoàn tất";
@ -391,12 +401,15 @@
"share_this_app_content" = "Hãy cùng tôi ngăn chặn sự lây lan của COVID-19! Tải COVIDSafe, ứng dụng của Chính phủ Úc. # COVID19 #coronavirusaustralia #stayhomesavelives https://covidsafe.gov.au";
"show" = "Hiển thị";
"south_australia" = "Nam Úc";
"state_number_heading" = "";
"stepCounter" = "Bước %d của %d";
"support" = "Hỗ trợ";
"support_content" = "Để được trợ giúp về các vấn đề không có trong chủ đề Trợ giúp";
"Support_VOLabel" = "Hỗ trợ. Để được trợ giúp về các vấn đề không có trong chủ đề Trợ giúp";
"tasmania" = "Tasmania";
"total" = "";
"total_confirmed_cases" = "Tổng số ca nhiễm";
"total_deaths" = "";
"total_per_state" = "Tổng số ca nhiễm theo tiểu bang và lãnh thổ";
"UILaunchStoryboardName" = "LaunchScreen_vi";
"under_sixteen_content" = "Tôi xác nhận cha mẹ hoặc người giám hộ của tôi đồng ý cho Cơ quan Chuyển đổi Kỹ thuật số với tư cách là nhà quản trị cửa hàng dữ liệu, theo quyết định pháp lý của Bộ trưởng Bộ Y tế Chính phủ Úc thu thập:";
@ -412,10 +425,15 @@
"update_available_dismiss_btn" = "Nhắc tôi sau";
"update_available_message_ios" = "Chúng tôi đã và đang cải thiện COVIDSafe. Cập nhật qua Kho Ứng dụng (App Store).";
"update_available_title" = "Cập nhật có sẵn!";
"update_description" = "";
"update_description_VO" = "";
"update_heading" = "Bật Dịch vụ Vị trí";
"update_modal_button" = "Kích hoạt";
"upload_answer_no" = "Không";
/* Upload flow */
"upload_answer_yes" = "Có";
"upload_consent_button" = "Tôi đồng ý";
"upload_fail_heading" = "Đăng tải thất bại";
"upload_failed" = "Đăng tải thất bại";
"upload_finished_header" = "Cảm ơn bạn đã giúp ngăn chặn sự lây lan của COVID-19!";
"upload_finished_sub_header" = "Bạn đã đăng tải thành công thông tin của mình vào hệ thống lưu trữ bảo mật cao của COVIDSafe. \n\nCác nhân viên y tế của tiểu bang hoặc lãnh thổ sẽ thông báo cho những người sử dụng COVIDSafe khác khi tiếp xúc gần với bạn. Danh tính của bạn sẽ được ẩn danh đối với người sử dụng khác.";
@ -501,7 +519,7 @@
"NotificationsDisabled_VOLabel" = "Thông báo bị vô hiệu hóa. Bạn sẽ không nhận được thông báo nếu COVIDSafe không hoạt động. \nThay đổi cài đặt thông báo";
"NotificationsEnabled_VOLabel" = "Thông báo được kích hoạt. Bạn sẽ nhận được thông báo nếu COVIDSafe không hoạt động. \nThay đổi cài đặt thông báo";
"OS1b_TopParagraph_VOLabel" = "Tín hiệu Bluetooth được sử dụng để xác định khi bạn ở gần người dùng COVIDSafe khác. \nMọi trường hợp tiếp xúc gần giữa bạn và những người dùng COVIDSafe khác đều được ghi nhận để tạo thông tin về mối tiếp xúc gần. Thông tin này sẽ được mã hóa và chỉ lưu trữ trong điện thoại của bạn. \nNếu bạn xét nghiệm dương tính với COVID-19 và là người dùng COVIDSafe, nhân viên y tế của tiểu bang hoặc lãnh thổ sẽ liên hệ với bạn. Họ sẽ tự nguyện hỗ trợ đăng tải thông tin về tiếp xúc gần của bạn vào hệ thống lưu trữ thông tin có độ an toàn cao \nCác nhân viên y tế của tiểu bang hoặc lãnh thổ cũng có thể liên hệ với bạn nếu bạn tiếp xúc gần với một người dùng COVIDSafe khác có kết quả xét nghiệm dương tính. \nĐể biết thêm thông tin, vui lòng tham khảo trang Chủ đề Trợ giúp";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth \n2. Thông báo \n\n COVIDSafe không gửi yêu cầu ghép nối.";
"permission_content_iOS_VOLabel" = "Cần bật Bluetooth để COVIDSafe hoạt động. Khi bật Thông báo, bạn nhận được các cập nhật để nhắc nhở khi COVIDSafe không hoạt động. \n\n Chọn 'Kích hoạt' để bật:";
"permission_content_iOS_2_VOLabel" = "1. Bluetooth\n 2. Thông báo\n 3. Dịch vụ Vị trí";
"permission_content_iOS_VOLabel" = "COVIDSafe cần bật Bluetooth để hoạt động. COVIDSafe không gửi yêu cầu ghép nối. \n\nKhi bật Thông báo, bạn sẽ nhận được cập nhật nhắc bạn khi COVIDSafe không hoạt động. \n\n iPhone của bạn yêu cầu quyền truy cập Vị trí để COVIDSafe hoạt động. *COVIDSafe KHÔNG theo dõi hoặc lưu trữ dữ liệu vị trí của bạn* \n\n Chọn 'Kích hoạt' để bật:";
"ShareCovidSafe_VOLabel" = "Chia sẻ COVIDSafe. Mời người khác giúp đỡ. Cùng nhau, chúng ta mạnh mẽ hơn.";
"UploadData_VOLabel" = "Nhân viên y tế có liên lạc với bạn không? Bạn chỉ có thể đăng tải thông tin của mình nếu bạn có kết quả xét nghiệm dương tính.";

View file

@ -1,8 +1,11 @@
"28_days" = "";
"7_days" = "";
"action_continue" = "继续";
"action_report_an_issue" = "报告问题";
"action_upload_done" = "完成";
"action_verify_invalid_pin" = "PIN码无效请要求卫生官员向您再发送一个PIN码。";
"action_verify_upload_pin" = "上传个人信息";
"active_cases" = "";
/* Figma page iOS Screens (05/06) */
"allow_bluetooth_call" = "允许COVIDSafe 访问蓝牙®";
/* Figma page iOS Screens (05/06) */
@ -16,6 +19,8 @@
"BluetoothOFF_content" = "打开手机蓝牙® 选择「允许新的连接」";
/* Figma page iOS Screens (05/06) */
"BluetoothON" = "蓝牙®:开";
"cases_28_days" = "";
"cases_7_days" = "";
"change_language" = "更改语言";
"change_language_content" = "阅读其他语言版本的COVIDSafe使用指南。";
"collection_message" = "COVIDSafe现在会收集诊断信息以帮助您解决应用程序中的问题。*了解更多信息*";
@ -239,7 +244,7 @@
/* OnBoarding Data Privacy */
"data_privacy_headline" = "注册及隐私";
"deaths" = "死亡人数";
"dialog_error_uploading_message" = "个人信息上传时发生错误,请重试。";
"dialog_error_uploading_message" = "请尝试再次上传数据。\n\n如果州或领地卫生官员有要求请提供此错误代码\n %@";
"dialog_error_uploading_negative" = "取消";
"dialog_error_uploading_positive" = "重试";
"dialog_uploading_message" = "您的COVIDSafe信息正在上传中。 \n\n请不要关闭该应用程序。";
@ -293,6 +298,7 @@
"home_set_complete_external_link_share_title" = "分享COVIDSafe";
"home_setup_help" = "帮助";
"home_version_number_ios" = "发布版本号%@,构建版本号 %@";
"hotspots_state_territory" = "";
"how_it_works_button" = "下一步";
"how_it_works_content" = "COVIDSafe需要使用蓝牙®信号以确定您在何时靠近另一个COVIDSafe用户。 \n\n您与其他COVIDSafe用户之间的每一次密切接触都会被记录下来以创建接触者数据。该信息经过加密且仅储存在您的手机中。 \n\n如果您是COVIDSafe用户并且COVID-19检测呈阳性则州或领地卫生官员将与您联系。他们会协助您自愿将密切接触者数据上传至高度安全的信息储存系统。\n\n如果您与检测结果呈阳性的其他COVIDSafe用户发生过密切接触则州或领地卫生官员也会与您联系。 \n\n如需更多信息请参阅*帮助主题*页。";
/* OnBoarding How it works */
@ -315,12 +321,16 @@
"invalid_norfolk_island_phone_number_error_prompt" = "诺福克岛的手机号码包含5到6位数。";
"jwt_description" = "您的注册信息存在问题。";
"jwt_heading" = "请重新注册";
"jwt_success" = "";
"jwt_success" = "已成功更新注册";
"loading_numbers" = "正在加载最新统计数字";
"locally_acquired" = "";
"location_off" = "位置:关闭";
"location_off_description" = "您的 iPhone 要求COVIDSafe 获得位置权限才能运行。COVIDSafe 不会跟踪或存储您的位置数据。";
/* Splash Screen */
"migration_in_progress" = "COVIDSafe正在更新。 \n\n请保持开机状态直至更新完成。";
"national_numbers" = "全国统计数字";
"new_cases" = "过去24小时内新增病例";
"new_cases_total" = "";
"new_south_wales" = "新南威尔士州";
/* Figma page Android Screens (05/06) */
"norfolk_hint" = "例如51234";
@ -341,11 +351,11 @@
"numbers_no_internet" = "请连接到互联网,以获取最新统计数字。";
"numbers_refresh" = "请刷新";
"options_for_australia" = "澳大利亚选项";
"overseas_acquired" = "";
"permission_button" = "继续";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS" = "COVIDSafe需要启用蓝牙®才能运行。通过启用通知您将在COVIDSafe未激活时收到提醒。 \n\n选择“继续”以启用";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS_2" = "1. 蓝牙®\n2. 通知\n\nCOVIDSafe 不会发送配对请求。";
"permission_content_iOS" = "COVIDSafe需要启用Bluetooth才能运行。 COVIDSafe不会发送配对请求。 \n\n通过启用通知您将在COVIDSafe未激活时收到提示信息。 \n\n您的iPhone要求COVIDSafe获得#位置权限#才能运行。 * COVIDSafe不会跟踪或存储您的位置数据* \n\n选择“继续”以启用";
"permission_content_iOS_2" = "1. 蓝牙\n2. 通知\n3. 位置服务";
/* OnBoarding Permission */
"permission_headline" = "应用设置";
"permission_success_button" = "完成";
@ -391,12 +401,15 @@
"share_this_app_content" = "让我们一起阻止COVID-19的传播下载 COVIDSafe—澳大利亚政府开发的一款应用程序。 #COVID19 #coronavirusaustralia #stayhomesavelives https://covidsafe.gov.au";
"show" = "显示";
"south_australia" = "南澳大利亚州";
"state_number_heading" = "";
"stepCounter" = "第%d步共%d步";
"support" = "支持";
"support_content" = "获取帮助,解决帮助主题未涵盖的问题";
"Support_VOLabel" = "支持。获取帮助,解决帮助主题未涵盖的问题";
"tasmania" = "塔斯马尼亚州";
"total" = "";
"total_confirmed_cases" = "确诊病例总数";
"total_deaths" = "";
"total_per_state" = "各州和领地确诊病例总数";
"UILaunchStoryboardName" = "LaunchScreen_zh_Hans";
"under_sixteen_content" = "我确认我的父母或监护人同意数字转换局作为数据存储管理方可以根据澳大利亚政府卫生部常务副部长依法作出的决议,收集以下信息:";
@ -412,10 +425,15 @@
"update_available_dismiss_btn" = "稍后提醒我";
"update_available_message_ios" = "我们一直在改进 COVIDSafe。请通过应用商店更新。";
"update_available_title" = "更新可用!";
"update_description" = "";
"update_description_VO" = "";
"update_heading" = "启用位置服务";
"update_modal_button" = "继续";
"upload_answer_no" = "否";
/* Upload flow */
"upload_answer_yes" = "是";
"upload_consent_button" = "我同意";
"upload_fail_heading" = "上传失败";
"upload_failed" = "上传失败";
"upload_finished_header" = "感谢您协助阻止COVID-19的传播";
"upload_finished_sub_header" = "您已成功将个人信息上传到COVIDSafe高度安全的储存系统。 \n\n根据记录其他COVIDSafe用户与您如有密切接触州或领地卫生官员将通知他们。您的身份对其他用户将保持匿名状态。";
@ -501,7 +519,7 @@
"NotificationsDisabled_VOLabel" = "通知已禁用。如果COVIDSafe未激活您将不会收到通知。 \n更改通知设置";
"NotificationsEnabled_VOLabel" = "通知已启用。如果COVIDSafe未激活您将收到通知。 \n更改通知设置";
"OS1b_TopParagraph_VOLabel" = "COVIDSafe需要使用蓝牙®信号以确定您靠近另一个COVIDSafe用户的时间。 \n\n您与其他COVIDSafe用户之间的每一次密切接触都被记录下来以创建密切接触信息。该信息经过加密且仅储存在您的手机中。 \n\n如果您使用COVIDSafe并对COVID-19测试呈阳性则州或领地卫生官员将与您联系。经您同意他们会协助您将密切接触者信息上传至高度安全的信息储存系统。\n\n如果您与测试结果呈阳性的其他COVIDSafe用户发生密切接触则州或领地卫生官员也会与您联系。 \n\n如需更多信息请参阅*帮助主题*页。";
"permission_content_iOS_2_VOLabel" = "1. 蓝牙®\n2. 通知\n\nCOVIDSafe不会发送配对请求。";
"permission_content_iOS_VOLabel" = "COVIDSafe需要启用蓝牙®才能运行。通过启用通知您将在COVIDSafe未激活时收到提醒。 \n\n选择“继续”以启用";
"permission_content_iOS_2_VOLabel" = "1. 蓝牙\n2. 通知\n3. 位置服务";
"permission_content_iOS_VOLabel" = "COVIDSafe需要启用蓝牙才能运行。 COVIDSafe不会发送配对请求。 \n\n通过启用通知您将在COVIDSafe未激活时收到提示信息。 \n\n您的iPhone要求COVIDSafe获得位置权限才能运行。 * COVIDSafe不会跟踪或存储您的位置数据* \n\n选择“继续”以启用";
"ShareCovidSafe_VOLabel" = "共享COVIDSafe。邀请他人助力。万众一心其利断金。";
"UploadData_VOLabel" = "是否有卫生官员联系过您?只有在您的病毒检测呈阳性时,才能上传个人信息。";

View file

@ -1,8 +1,11 @@
"28_days" = "";
"7_days" = "";
"action_continue" = "繼續";
"action_report_an_issue" = "報告問題";
"action_upload_done" = "完成";
"action_verify_invalid_pin" = "PIN 碼無效,請要求衛生部官員再傳送另一條 PIN 給你。";
"action_verify_upload_pin" = "上傳我的資料";
"active_cases" = "";
/* Figma page iOS Screens (05/06) */
"allow_bluetooth_call" = "允許 COVIDSafe 接入藍牙®";
/* Figma page iOS Screens (05/06) */
@ -16,6 +19,8 @@
"BluetoothOFF_content" = "請打開手機藍牙®。如有顯示,請選擇「允許新連接」選項。";
/* Figma page iOS Screens (05/06) */
"BluetoothON" = "藍牙®:開";
"cases_28_days" = "";
"cases_7_days" = "";
"change_language" = "更換語言";
"change_language_content" = "用其他語言閱讀COVIDSafe使用指南。";
"collection_message" = "COVIDSafe現在會收集診斷資訊以幫助你解決應用程式中的問題。 *了解更多*";
@ -239,7 +244,7 @@
/* OnBoarding Data Privacy */
"data_privacy_headline" = "註册及私隱權";
"deaths" = "死亡人數";
"dialog_error_uploading_message" = "個人資料上傳時發生錯誤,請重試。";
"dialog_error_uploading_message" = "再次上傳你的數據。\n\n如果州或領地衛生官員有要求請出示此錯誤代碼。\n%@";
"dialog_error_uploading_negative" = "取消";
"dialog_error_uploading_positive" = "重試";
"dialog_uploading_message" = "正在上傳你的 COVIDSafe 資料。 \n\n請勿關閉應用程式。";
@ -293,6 +298,7 @@
"home_set_complete_external_link_share_title" = "齊齊使用 COVIDSafe";
"home_setup_help" = "求助";
"home_version_number_ios" = "發行版本%@ ,建構版本%@";
"hotspots_state_territory" = "";
"how_it_works_button" = "下一步";
"how_it_works_content" = "COVIDSafe 需要使用藍牙®訊號以確定你在何時接近另一名用戶。 \n\n你與其他COVIDSafe用戶之間的每一次密切接觸都會被記錄下來以建立密切接觸者資料。該資料會被加密並僅儲存在你的手機内。\n\n如果你是COVIDSafe用户並被驗出 COVID-19 陽性,州或領地衛生部官員將會與你聯絡。他們會協助你自願地將密切接觸者資料上傳到高度保密的資料儲存系統。\n\n如果你曾密切接觸過另一名被驗出 COVID-19 陽性的 COVIDSafe 用戶,則州或領地的衛生部官員亦會聯絡你。 \n\n欲知詳情請參閱*求助主題*頁";
/* OnBoarding How it works */
@ -315,12 +321,16 @@
"invalid_norfolk_island_phone_number_error_prompt" = "諾福克島的手機號碼是個5至6位數字。";
"jwt_description" = "你的註冊詳情存在問題。";
"jwt_heading" = "請重新註冊。";
"jwt_success" = "";
"jwt_success" = "成功更新註冊。";
"loading_numbers" = "正在載入最新統計數字";
"locally_acquired" = "";
"location_off" = "位置:關閉";
"location_off_description" = "你的iPhone要求COVIDSafe獲得位置許可權才能正常運作。COVIDSafe不會跟蹤或者存儲你的位置資料。";
/* Splash Screen */
"migration_in_progress" = "正在更新 COVIDSafe。\n\n請保持開機狀態直至更新完成為止。";
"national_numbers" = "全國統計數字";
"new_cases" = "過去24小時內新增病例";
"new_cases_total" = "";
"new_south_wales" = "新南威爾士州";
/* Figma page Android Screens (05/06) */
"norfolk_hint" = "例如51234";
@ -341,11 +351,11 @@
"numbers_no_internet" = "請連接到互聯網,以獲取最新統計數字。";
"numbers_refresh" = "現在刷新";
"options_for_australia" = "澳洲適用選項";
"overseas_acquired" = "";
"permission_button" = "繼續";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS" = "COVIDSafe 需要開啓藍牙®才能執行。透過開啟「通知傳送」功能,你會在 COVIDSafe 仍未處於啟用狀態時收到提醒。\n\n請選擇「繼續」來開啟";
/* Figma page iOS Screens (05/06) */
"permission_content_iOS_2" = "1. 藍牙®\n2. 通知\n\nCOVIDSafe 不會傳送配對請求。";
"permission_content_iOS" = "COVIDSafe需要啟用#藍牙#才能運作。COVIDSafe不會發出配對請求。\n\n通過允許#通知#當COVIDSafe未在啟用狀態時你會收到提示。\n\n你的iPhone要求COVIDSafe獲得#位置許可權#才能運作。*COVIDSafe不會跟蹤或者存儲你的位置資料。*\n\n\n選擇 '繼續’來啟用:";
"permission_content_iOS_2" = "1. 藍牙\n2. 通知\n3. 位置服務";
/* OnBoarding Permission */
"permission_headline" = "應用程式設定";
"permission_success_button" = "完成";
@ -391,12 +401,15 @@
"share_this_app_content" = "同我一齊遏止 COVID-19 的擴散!下載澳洲政府提供的 COVIDSafe 應用程式。 COVID19 #coronavirusaustralia #stayhomesavelives https://covidsafe.gov.au";
"show" = "顯示";
"south_australia" = "南澳";
"state_number_heading" = "";
"stepCounter" = "第%d步共%d步";
"support" = "支持";
"support_content" = "獲取幫助,解決求助主題未涵蓋的問題。";
"Support_VOLabel" = "支持。獲取幫助,解決求助主題未涵蓋的問題。";
"tasmania" = "塔斯曼尼亞州";
"total" = "";
"total_confirmed_cases" = "確診病例總數";
"total_deaths" = "";
"total_per_state" = "各州和領地確診病例總數";
"UILaunchStoryboardName" = "LaunchScreen_zh_Hant";
"under_sixteen_content" = "我確認我的父母或監護人同意數字化轉型局作為數據儲存管理者,根據澳大利亞政府衛生部常務副部長的合法決定,收集以下資訊:";
@ -412,10 +425,15 @@
"update_available_dismiss_btn" = "請稍後提醒";
"update_available_message_ios" = "我們一直都在改良 COVIDSafe。通過App Store更新。";
"update_available_title" = "更新可用!";
"update_description" = "";
"update_description_VO" = "";
"update_heading" = "允許定位服務";
"update_modal_button" = "繼續";
"upload_answer_no" = "否";
/* Upload flow */
"upload_answer_yes" = "是";
"upload_consent_button" = "我同意";
"upload_fail_heading" = "你的上傳失敗";
"upload_failed" = "上傳失敗";
"upload_finished_header" = "感謝你協助遏止 COVID-19 的擴散!";
"upload_finished_sub_header" = "你已成功將個人資料上傳到 COVIDSafe 高度保密的儲存系統。\n\n若錄得有其他 COVIDSafe 用户曾密切接觸過你,各州或領地的衛生部官員將會通知他們。你的身份仍舊予以保密。";
@ -501,7 +519,7 @@
"NotificationsDisabled_VOLabel" = "「通知傳送」功能已被闗閉。如果 COVIDSafe 尚未處於啟用狀態,你將不會收到通知。更改「通知傳送」設定";
"NotificationsEnabled_VOLabel" = "「通知傳送」功能已被開啓。如果 COVIDSafe 尚未處於啟用狀態,你將會收到通知。更改「通知傳送」設定";
"OS1b_TopParagraph_VOLabel" = "藍牙®訊號用於確定你在何時接近另一名 COVIDSafe 用戶。 \n你與其他 COVIDSafe 用戶的每次密切接觸都會被記錄下來,以創建密切接觸者資料。該資料會被加密,並僅儲存在你的手機内。 \n如果你是一名 COVIDSafe 用戶,而又被驗出 COVID-19 陽性,則州或領地衛生官員將會與你聯絡。他們將會協助你自願把你自己的密切接觸者資料上傳到高度保密的資料儲存系統。\n如果你曾密切接觸過另一名化驗結果呈陽性的 COVIDSafe 用戶,則州或領地衛生官員也可能會聯絡你。 \n欲知詳情請參閱「求助主題」頁。";
"permission_content_iOS_2_VOLabel" = "1. 藍牙®\n2. 通知\n\nCOVIDSafe 不會傳送配對請求。";
"permission_content_iOS_VOLabel" = "需開啓藍牙®才能執行 COVIDSafe 。透過開啟「通知傳送」功能,你會在 COVIDSafe 尚未處於啟用狀態時收到提醒。\n\n請選擇「繼續」來開啟";
"permission_content_iOS_2_VOLabel" = "1. 藍牙\n2. 通知\n3. 位置服務";
"permission_content_iOS_VOLabel" = "COVIDSafe需要啟用藍牙才能運作。COVIDSafe不會發出配對請求。\n\n通過允許通知當COVIDSafe未在啟用狀態時你會收到提示。\n\n你的iPhone要求COVIDSafe獲得位置許可權才能運作。*COVIDSafe不會跟蹤或者存儲你的位置資料。*\n\n\n選擇 '繼續’來啟用";
"ShareCovidSafe_VOLabel" = "分享使用 COVIDSafe邀請他人參與防疫。團結便是力量";
"UploadData_VOLabel" = "曾有衛官員聯絡過你嗎?只有在你被驗出病毒陽性時,才能上傳個人資料。";