diff --git a/src/xcode/ENA/ENA.xcodeproj/project.pbxproj b/src/xcode/ENA/ENA.xcodeproj/project.pbxproj index cd3994ddcf9..573bd479510 100644 --- a/src/xcode/ENA/ENA.xcodeproj/project.pbxproj +++ b/src/xcode/ENA/ENA.xcodeproj/project.pbxproj @@ -579,6 +579,8 @@ 347FBB6A2610DDBD00672770 /* TraceLocationDetailsQRCodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347FBB692610DDBD00672770 /* TraceLocationDetailsQRCodeCell.swift */; }; 347FBB752611C9F100672770 /* TraceLocationDetailsDateTimeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347FBB742611C9EF00672770 /* TraceLocationDetailsDateTimeCell.swift */; }; 3484AC7C26A060E900CB0500 /* LocalStatisticsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3484AC7B26A060E900CB0500 /* LocalStatisticsState.swift */; }; + 3492C187292D172000C48133 /* SRSDataProcessingInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3492C186292D172000C48133 /* SRSDataProcessingInfoViewController.swift */; }; + 3492C189292D174400C48133 /* SRSDataProcessingInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3492C188292D174400C48133 /* SRSDataProcessingInfoViewModel.swift */; }; 3494634C27EDF90800403F7A /* ExposureSubmissionTestResultModeling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3494634B27EDF90800403F7A /* ExposureSubmissionTestResultModeling.swift */; }; 3494634E27F498D900403F7A /* ExposureSubmissionTestResultFamilyMemberViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3494634D27F498D900403F7A /* ExposureSubmissionTestResultFamilyMemberViewModelTests.swift */; }; 3494635227FC937900403F7A /* AntigenTestProfileOverviewEmptyStateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3494635127FC937900403F7A /* AntigenTestProfileOverviewEmptyStateViewModel.swift */; }; @@ -594,8 +596,10 @@ 34C40E3227A2FCC00098C6ED /* ccl-text-descriptor-test-cases.json in Resources */ = {isa = PBXBuildFile; fileRef = 34C40E3127A2FCB30098C6ED /* ccl-text-descriptor-test-cases.json */; }; 34C40E3627A2FE3A0098C6ED /* TestCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C40E3427A2FE150098C6ED /* TestCases.swift */; }; 34CF3B1F2615C1D1004A9435 /* QRCodeErrorCorrectionLevelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34CF3B1E2615C1D1004A9435 /* QRCodeErrorCorrectionLevelType.swift */; }; + 34D9977F29264190000EA8BF /* SubmissionTestType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D9977E29264190000EA8BF /* SubmissionTestType.swift */; }; 34E03F822823BEF8001A352B /* ccl-configuration.bin in Resources */ = {isa = PBXBuildFile; fileRef = 34E03F812823BEF8001A352B /* ccl-configuration.bin */; }; 34E03F842823ED29001A352B /* HealthCertificateWebTokenHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E03F832823ED29001A352B /* HealthCertificateWebTokenHeader.swift */; }; + 34F687B4291553970062A6D2 /* ppdd_srs_parameters.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F687B3291553970062A6D2 /* ppdd_srs_parameters.pb.swift */; }; 34FE156A26010A840002CB69 /* TraceLocation+GenerateQRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34FE156926010A830002CB69 /* TraceLocation+GenerateQRCode.swift */; }; 34FE15A02604B1880002CB69 /* QRCodePosterTemplateProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34FE159B2604B1800002CB69 /* QRCodePosterTemplateProviderTests.swift */; }; 34FE15A82604F99E0002CB69 /* UIColor+HEX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34FE15A72604F99D0002CB69 /* UIColor+HEX.swift */; }; @@ -950,12 +954,17 @@ 85D7594E24570491008175F0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85D7594C24570491008175F0 /* LaunchScreen.storyboard */; }; 85D7596424570491008175F0 /* ENAUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D7596324570491008175F0 /* ENAUITests.swift */; }; 85E33444247EB357006E74EC /* CircularProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E33443247EB357006E74EC /* CircularProgressView.swift */; }; + 8F011DF82927D4AB00DAD275 /* authorizeOtpSrs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F011DF72927D4AB00DAD275 /* authorizeOtpSrs.swift */; }; 8F070CE925E58B9400D29344 /* HtmlInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F070CE825E58B9400D29344 /* HtmlInfoModel.swift */; }; 8F07EB2428F98716001FCFDE /* SAP_Internal_Stats_LinkCard+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3B278C28F55B6D00A55769 /* SAP_Internal_Stats_LinkCard+Mock.swift */; }; 8F0B0A9D26EA180500ED6F71 /* BoosterNotificationServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0B0A9C26EA180500ED6F71 /* BoosterNotificationServiceError.swift */; }; 8F1E6EEB26DD93BD00483660 /* RulesDownloadService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1E6EEA26DD93BD00483660 /* RulesDownloadService.swift */; }; 8F270194259B5BA700E48CFE /* ContactDiaryMigration1To2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F270193259B5BA700E48CFE /* ContactDiaryMigration1To2.swift */; }; 8F27B8CF28C80D7200C35A5D /* OTPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F27B8CE28C80D7200C35A5D /* OTPResponse.swift */; }; + 8F282D632919CA79009F2184 /* SRSFlowType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F282D622919CA79009F2184 /* SRSFlowType.swift */; }; + 8F2FA2E5292B2D8400C6286A /* OTPAuthorizationForSRSResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2FA2E4292B2D8400C6286A /* OTPAuthorizationForSRSResource.swift */; }; + 8F2FA2EB292B447100C6286A /* srs_otp.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2FA2E9292B447000C6286A /* srs_otp.pb.swift */; }; + 8F2FA2EC292B447100C6286A /* srs_otp_request_ios.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2FA2EA292B447000C6286A /* srs_otp_request_ios.pb.swift */; }; 8F3167E626A5D906008F35AA /* RegionStatisticsData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3167E526A5D906008F35AA /* RegionStatisticsData.swift */; }; 8F3167E826A5D929008F35AA /* RegionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3167E726A5D929008F35AA /* RegionType.swift */; }; 8F3202442600C7BB00D6A224 /* TorchMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3202432600C7BB00D6A224 /* TorchMode.swift */; }; @@ -995,10 +1004,12 @@ 8F5B5C172648CD3800456381 /* HealthCertifiedPersonCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5B5C162648CD3800456381 /* HealthCertifiedPersonCellModel.swift */; }; 8F5B774D282D2DD600EAF41F /* AccompanyingCertificatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5B774C282D2DD600EAF41F /* AccompanyingCertificatesViewController.swift */; }; 8F5B774F282D2DE100EAF41F /* AccompanyingCertificatesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5B774E282D2DE000EAF41F /* AccompanyingCertificatesViewModel.swift */; }; + 8F5C4F942919C26700135070 /* SRSConsentViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5C4F932919C26700135070 /* SRSConsentViewModelTests.swift */; }; 8F613538287B257700FD2DD8 /* PPASubmitResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F613537287B257700FD2DD8 /* PPASubmitResource.swift */; }; 8F63407C262D8F4400077240 /* AntigenTestInformation+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F634077262D8EAA00077240 /* AntigenTestInformation+Mock.swift */; }; 8F64BD91276796A400E48299 /* RoundedLabeledView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F64BD90276796A400E48299 /* RoundedLabeledView.swift */; }; 8F6635F02713A7B500384B63 /* stats.bin in Resources */ = {isa = PBXBuildFile; fileRef = 8F6635EF2713A7B400384B63 /* stats.bin */; }; + 8F75219229266B1200F1B3DE /* SRSError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F75219129266B1200F1B3DE /* SRSError.swift */; }; 8F7AB89F25F106A50043A3C8 /* SendErrorLogsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F7AB89E25F106A50043A3C8 /* SendErrorLogsViewModel.swift */; }; 8F7AB8CE25F257220043A3C8 /* TopErrorReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F7AB8CD25F257220043A3C8 /* TopErrorReportViewModel.swift */; }; 8F7C3A1A261BB53700AEC979 /* CheckinQRScannerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F7C3A19261BB53700AEC979 /* CheckinQRScannerError.swift */; }; @@ -1010,6 +1021,8 @@ 8F87214E274DBCAE00A263D5 /* Locator+ServiceIdentityDocumentValidationDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87214D274DBCAE00A263D5 /* Locator+ServiceIdentityDocumentValidationDecorator.swift */; }; 8F872150274DBD7B00A263D5 /* ServiceIdentityDocumentValidationDecoratorResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87214F274DBD7B00A263D5 /* ServiceIdentityDocumentValidationDecoratorResource.swift */; }; 8F95A1B225EFC5BE00506CF2 /* SendErrorLogsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F95A1B125EFC5BE00506CF2 /* SendErrorLogsViewController.swift */; }; + 8FA3E9C62913F334000512FC /* SRSConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FA3E9C52913F334000512FC /* SRSConsentViewController.swift */; }; + 8FA3E9C82913F34A000512FC /* SRSConsentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FA3E9C72913F34A000512FC /* SRSConsentViewModel.swift */; }; 8FA88F672613325700FD36DA /* QRCodePayload+extention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FA88F662613325700FD36DA /* QRCodePayload+extention.swift */; }; 8FA88F9F2613A7E300FD36DA /* EncodingType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FA88F9E2613A7E300FD36DA /* EncodingType.swift */; }; 8FA88FE02616692000FD36DA /* TraceLocationCheckinViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FA88FDF2616692000FD36DA /* TraceLocationCheckinViewModelTests.swift */; }; @@ -1029,9 +1042,12 @@ 8FB7C92D2750EF6E0071C98C /* TicketValidationDecoratorIdentityDocumentProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FB7C9272750EF6E0071C98C /* TicketValidationDecoratorIdentityDocumentProcessor.swift */; }; 8FBF34AA28BD1C4F000A86D7 /* OTPAuthorizationForELSResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FBF34A928BD1C4F000A86D7 /* OTPAuthorizationForELSResource.swift */; }; 8FBF34AE28BD1CD5000A86D7 /* OTPResponsePropertiesReceiveModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FBF34AD28BD1CD5000A86D7 /* OTPResponsePropertiesReceiveModel.swift */; }; + 8FD5BE21292BB95400B0315B /* SRSService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FD5BE20292BB95400B0315B /* SRSService.swift */; }; 8FD7E2C927E9EABD00E08ABB /* CCLServiceInvalidationRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FD7E2C827E9EABD00E08ABB /* CCLServiceInvalidationRulesTests.swift */; }; 8FDB9403261FBA3800BA8CB1 /* RapidTestQRCodeInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDB9402261FBA3800BA8CB1 /* RapidTestQRCodeInformation.swift */; }; 8FDB940B261FBAAA00BA8CB1 /* CoronaTestRegistrationInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDB940A261FBAAA00BA8CB1 /* CoronaTestRegistrationInformation.swift */; }; + 8FDFEF63292C1E5400707FE2 /* SRSKeySubmissionResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDFEF62292C1E5400707FE2 /* SRSKeySubmissionResource.swift */; }; + 8FDFEF65292CD91400707FE2 /* MockSrsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FDFEF64292CD91400707FE2 /* MockSrsService.swift */; }; 8FE68F9328A146CC003E86E1 /* ELSSubmitResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FE68F9228A146CC003E86E1 /* ELSSubmitResource.swift */; }; 8FE68F9728A15BAC003E86E1 /* ELSSubmitReceiveModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FE68F9628A15BAC003E86E1 /* ELSSubmitReceiveModel.swift */; }; 8FEC12242754B6C000C5DADC /* Locator+AllowList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FEC12232754B6C000C5DADC /* Locator+AllowList.swift */; }; @@ -1734,8 +1750,9 @@ DC03798C28F4483C00E1EE20 /* HomeLinkCardViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC03798B28F4483B00E1EE20 /* HomeLinkCardViewModelTests.swift */; }; DC03798E28F449D000E1EE20 /* HomeLinkCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC03798D28F449D000E1EE20 /* HomeLinkCard.swift */; }; DC28F7C828FEC94100EFFA34 /* UIImage+SFSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC28F7C728FEC94100EFFA34 /* UIImage+SFSymbols.swift */; }; - DC3B278D28F55B6D00A55769 /* SAP_Internal_Stats_LinkCard+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3B278C28F55B6D00A55769 /* SAP_Internal_Stats_LinkCard+Mock.swift */; }; DC3B278F28F5936700A55769 /* NSAttributedString+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3B278E28F5936700A55769 /* NSAttributedString+Image.swift */; }; + DC4618C429155B2F0035CAA7 /* SRSTestTypeSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4618C329155B2F0035CAA7 /* SRSTestTypeSelectionViewController.swift */; }; + DC4618C629155E560035CAA7 /* SRSTestTypeSelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4618C529155E560035CAA7 /* SRSTestTypeSelectionViewModel.swift */; }; DC7002D9284F6B7900B164AC /* HealthCertificateExportCertificatesInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC7002D8284F6B7900B164AC /* HealthCertificateExportCertificatesInfoViewModel.swift */; }; DC7002DB284F8BA100B164AC /* HealthCertificateExportCertificatesInfoViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC7002DA284F8BA100B164AC /* HealthCertificateExportCertificatesInfoViewModelTests.swift */; }; DC7002DD2850EA7800B164AC /* TooltipViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC7002DC2850EA7800B164AC /* TooltipViewController.swift */; }; @@ -1747,6 +1764,8 @@ DC95881628BE3D5500F4D03E /* MaskStateCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC95881528BE3D5500F4D03E /* MaskStateCellModel.swift */; }; DC95881828BE5E2800F4D03E /* MaskStateCellModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC95881728BE5E2800F4D03E /* MaskStateCellModelTests.swift */; }; DC9AB2392908174000A2F9D1 /* String+Attributed.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9AB2382908174000A2F9D1 /* String+Attributed.swift */; }; + DCD345B8291BF7B700E95A2D /* UIView+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD345B7291BF7B700E95A2D /* UIView+Utils.swift */; }; + DCE0A446291E4B79008721D8 /* SRSTestTypeSelectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE0A445291E4B79008721D8 /* SRSTestTypeSelectionViewModelTests.swift */; }; DCEDBD5628479ECB00D34E7A /* Alias.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEDBD5528479ECB00D34E7A /* Alias.swift */; }; DCEDBD582847A0AF00D34E7A /* HealthCertificateExportCertificatesInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEDBD572847A0AF00D34E7A /* HealthCertificateExportCertificatesInfoViewController.swift */; }; EB08F1782541CE3300D11FA9 /* risk_score_classification.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB08F1702541CE3200D11FA9 /* risk_score_classification.pb.swift */; }; @@ -2441,6 +2460,8 @@ 347FBB692610DDBD00672770 /* TraceLocationDetailsQRCodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceLocationDetailsQRCodeCell.swift; sourceTree = ""; }; 347FBB742611C9EF00672770 /* TraceLocationDetailsDateTimeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceLocationDetailsDateTimeCell.swift; sourceTree = ""; }; 3484AC7B26A060E900CB0500 /* LocalStatisticsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStatisticsState.swift; sourceTree = ""; }; + 3492C186292D172000C48133 /* SRSDataProcessingInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRSDataProcessingInfoViewController.swift; sourceTree = ""; }; + 3492C188292D174400C48133 /* SRSDataProcessingInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRSDataProcessingInfoViewModel.swift; sourceTree = ""; }; 3494634B27EDF90800403F7A /* ExposureSubmissionTestResultModeling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExposureSubmissionTestResultModeling.swift; sourceTree = ""; }; 3494634D27F498D900403F7A /* ExposureSubmissionTestResultFamilyMemberViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExposureSubmissionTestResultFamilyMemberViewModelTests.swift; sourceTree = ""; }; 3494635127FC937900403F7A /* AntigenTestProfileOverviewEmptyStateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AntigenTestProfileOverviewEmptyStateViewModel.swift; sourceTree = ""; }; @@ -2456,8 +2477,10 @@ 34C40E3127A2FCB30098C6ED /* ccl-text-descriptor-test-cases.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "ccl-text-descriptor-test-cases.json"; sourceTree = ""; }; 34C40E3427A2FE150098C6ED /* TestCases.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCases.swift; sourceTree = ""; }; 34CF3B1E2615C1D1004A9435 /* QRCodeErrorCorrectionLevelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeErrorCorrectionLevelType.swift; sourceTree = ""; }; + 34D9977E29264190000EA8BF /* SubmissionTestType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubmissionTestType.swift; sourceTree = ""; }; 34E03F812823BEF8001A352B /* ccl-configuration.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = "ccl-configuration.bin"; sourceTree = ""; }; 34E03F832823ED29001A352B /* HealthCertificateWebTokenHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthCertificateWebTokenHeader.swift; sourceTree = ""; }; + 34F687B3291553970062A6D2 /* ppdd_srs_parameters.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ppdd_srs_parameters.pb.swift; sourceTree = ""; }; 34FE156926010A830002CB69 /* TraceLocation+GenerateQRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TraceLocation+GenerateQRCode.swift"; sourceTree = ""; }; 34FE156E26010FBD0002CB69 /* TraceLocation+GenerateQRCodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TraceLocation+GenerateQRCodeTests.swift"; sourceTree = ""; }; 34FE1580260126080002CB69 /* QRCodePosterTemplateProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodePosterTemplateProviding.swift; sourceTree = ""; }; @@ -2828,6 +2851,7 @@ 85D7596324570491008175F0 /* ENAUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ENAUITests.swift; sourceTree = ""; }; 85D7596524570491008175F0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 85E33443247EB357006E74EC /* CircularProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressView.swift; sourceTree = ""; }; + 8F011DF72927D4AB00DAD275 /* authorizeOtpSrs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = authorizeOtpSrs.swift; sourceTree = ""; }; 8F070CE825E58B9400D29344 /* HtmlInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HtmlInfoModel.swift; sourceTree = ""; }; 8F09523227508642002B7049 /* ServiceIdentityDocumentValidationDecoratorResourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceIdentityDocumentValidationDecoratorResourceTests.swift; sourceTree = ""; }; 8F0B0A9C26EA180500ED6F71 /* BoosterNotificationServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoosterNotificationServiceError.swift; sourceTree = ""; }; @@ -2835,6 +2859,10 @@ 8F27018E259B593700E48CFE /* ContactDiaryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDiaryStore.swift; sourceTree = ""; }; 8F270193259B5BA700E48CFE /* ContactDiaryMigration1To2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDiaryMigration1To2.swift; sourceTree = ""; }; 8F27B8CE28C80D7200C35A5D /* OTPResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTPResponse.swift; sourceTree = ""; }; + 8F282D622919CA79009F2184 /* SRSFlowType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRSFlowType.swift; sourceTree = ""; }; + 8F2FA2E4292B2D8400C6286A /* OTPAuthorizationForSRSResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPAuthorizationForSRSResource.swift; sourceTree = ""; }; + 8F2FA2E9292B447000C6286A /* srs_otp.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = srs_otp.pb.swift; sourceTree = ""; }; + 8F2FA2EA292B447000C6286A /* srs_otp_request_ios.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = srs_otp_request_ios.pb.swift; sourceTree = ""; }; 8F3167E526A5D906008F35AA /* RegionStatisticsData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionStatisticsData.swift; sourceTree = ""; }; 8F3167E726A5D929008F35AA /* RegionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionType.swift; sourceTree = ""; }; 8F3202432600C7BB00D6A224 /* TorchMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorchMode.swift; sourceTree = ""; }; @@ -2873,10 +2901,12 @@ 8F5B5C162648CD3800456381 /* HealthCertifiedPersonCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthCertifiedPersonCellModel.swift; sourceTree = ""; }; 8F5B774C282D2DD600EAF41F /* AccompanyingCertificatesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccompanyingCertificatesViewController.swift; sourceTree = ""; }; 8F5B774E282D2DE000EAF41F /* AccompanyingCertificatesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccompanyingCertificatesViewModel.swift; sourceTree = ""; }; + 8F5C4F932919C26700135070 /* SRSConsentViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRSConsentViewModelTests.swift; sourceTree = ""; }; 8F613537287B257700FD2DD8 /* PPASubmitResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PPASubmitResource.swift; sourceTree = ""; }; 8F634077262D8EAA00077240 /* AntigenTestInformation+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AntigenTestInformation+Mock.swift"; sourceTree = ""; }; 8F64BD90276796A400E48299 /* RoundedLabeledView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedLabeledView.swift; sourceTree = ""; }; 8F6635EF2713A7B400384B63 /* stats.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = stats.bin; sourceTree = ""; }; + 8F75219129266B1200F1B3DE /* SRSError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRSError.swift; sourceTree = ""; }; 8F7AB89E25F106A50043A3C8 /* SendErrorLogsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendErrorLogsViewModel.swift; sourceTree = ""; }; 8F7AB8CD25F257220043A3C8 /* TopErrorReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopErrorReportViewModel.swift; sourceTree = ""; }; 8F7C39CB261B430700AEC979 /* CheckinQRCodeParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckinQRCodeParserTests.swift; sourceTree = ""; }; @@ -2889,6 +2919,8 @@ 8F87214D274DBCAE00A263D5 /* Locator+ServiceIdentityDocumentValidationDecorator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locator+ServiceIdentityDocumentValidationDecorator.swift"; sourceTree = ""; }; 8F87214F274DBD7B00A263D5 /* ServiceIdentityDocumentValidationDecoratorResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceIdentityDocumentValidationDecoratorResource.swift; sourceTree = ""; }; 8F95A1B125EFC5BE00506CF2 /* SendErrorLogsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendErrorLogsViewController.swift; sourceTree = ""; }; + 8FA3E9C52913F334000512FC /* SRSConsentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRSConsentViewController.swift; sourceTree = ""; }; + 8FA3E9C72913F34A000512FC /* SRSConsentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRSConsentViewModel.swift; sourceTree = ""; }; 8FA88F662613325700FD36DA /* QRCodePayload+extention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QRCodePayload+extention.swift"; sourceTree = ""; }; 8FA88F9E2613A7E300FD36DA /* EncodingType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodingType.swift; sourceTree = ""; }; 8FA88FDF2616692000FD36DA /* TraceLocationCheckinViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceLocationCheckinViewModelTests.swift; sourceTree = ""; }; @@ -2908,9 +2940,12 @@ 8FB7C9272750EF6E0071C98C /* TicketValidationDecoratorIdentityDocumentProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketValidationDecoratorIdentityDocumentProcessor.swift; sourceTree = ""; }; 8FBF34A928BD1C4F000A86D7 /* OTPAuthorizationForELSResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPAuthorizationForELSResource.swift; sourceTree = ""; }; 8FBF34AD28BD1CD5000A86D7 /* OTPResponsePropertiesReceiveModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPResponsePropertiesReceiveModel.swift; sourceTree = ""; }; + 8FD5BE20292BB95400B0315B /* SRSService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRSService.swift; sourceTree = ""; }; 8FD7E2C827E9EABD00E08ABB /* CCLServiceInvalidationRulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CCLServiceInvalidationRulesTests.swift; sourceTree = ""; }; 8FDB9402261FBA3800BA8CB1 /* RapidTestQRCodeInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RapidTestQRCodeInformation.swift; sourceTree = ""; }; 8FDB940A261FBAAA00BA8CB1 /* CoronaTestRegistrationInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoronaTestRegistrationInformation.swift; sourceTree = ""; }; + 8FDFEF62292C1E5400707FE2 /* SRSKeySubmissionResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRSKeySubmissionResource.swift; sourceTree = ""; }; + 8FDFEF64292CD91400707FE2 /* MockSrsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSrsService.swift; sourceTree = ""; }; 8FE68F9228A146CC003E86E1 /* ELSSubmitResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ELSSubmitResource.swift; sourceTree = ""; }; 8FE68F9628A15BAC003E86E1 /* ELSSubmitReceiveModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ELSSubmitReceiveModel.swift; sourceTree = ""; }; 8FEC12232754B6C000C5DADC /* Locator+AllowList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locator+AllowList.swift"; sourceTree = ""; }; @@ -3614,6 +3649,8 @@ DC28F7C728FEC94100EFFA34 /* UIImage+SFSymbols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+SFSymbols.swift"; sourceTree = ""; }; DC3B278C28F55B6D00A55769 /* SAP_Internal_Stats_LinkCard+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SAP_Internal_Stats_LinkCard+Mock.swift"; sourceTree = ""; }; DC3B278E28F5936700A55769 /* NSAttributedString+Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Image.swift"; sourceTree = ""; }; + DC4618C329155B2F0035CAA7 /* SRSTestTypeSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRSTestTypeSelectionViewController.swift; sourceTree = ""; }; + DC4618C529155E560035CAA7 /* SRSTestTypeSelectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRSTestTypeSelectionViewModel.swift; sourceTree = ""; }; DC7002D8284F6B7900B164AC /* HealthCertificateExportCertificatesInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthCertificateExportCertificatesInfoViewModel.swift; sourceTree = ""; }; DC7002DA284F8BA100B164AC /* HealthCertificateExportCertificatesInfoViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthCertificateExportCertificatesInfoViewModelTests.swift; sourceTree = ""; }; DC7002DC2850EA7800B164AC /* TooltipViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipViewController.swift; sourceTree = ""; }; @@ -3625,6 +3662,8 @@ DC95881528BE3D5500F4D03E /* MaskStateCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaskStateCellModel.swift; sourceTree = ""; }; DC95881728BE5E2800F4D03E /* MaskStateCellModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaskStateCellModelTests.swift; sourceTree = ""; }; DC9AB2382908174000A2F9D1 /* String+Attributed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Attributed.swift"; sourceTree = ""; }; + DCD345B7291BF7B700E95A2D /* UIView+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Utils.swift"; sourceTree = ""; }; + DCE0A445291E4B79008721D8 /* SRSTestTypeSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRSTestTypeSelectionViewModelTests.swift; sourceTree = ""; }; DCEDBD5528479ECB00D34E7A /* Alias.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alias.swift; sourceTree = ""; }; DCEDBD572847A0AF00D34E7A /* HealthCertificateExportCertificatesInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthCertificateExportCertificatesInfoViewController.swift; sourceTree = ""; }; EB08F1702541CE3200D11FA9 /* risk_score_classification.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = risk_score_classification.pb.swift; path = ../../../gen/output/internal/risk_score_classification.pb.swift; sourceTree = ""; }; @@ -3933,6 +3972,7 @@ 0196FE7B27E4BA2E00B2729E /* UserCoronaTest.swift */, 0196FE7C27E4EC1B00B2729E /* FamilyMemberCoronaTest.swift */, 01992EC127CE4B8000BC16DA /* CoronaTestType.swift */, + 34D9977E29264190000EA8BF /* SubmissionTestType.swift */, 0196FE7E27E4ED1B00B2729E /* UserPCRTest.swift */, 0196FE7F27E4EE6B00B2729E /* FamilyMemberPCRTest.swift */, 0196FE7D27E4ED1B00B2729E /* UserAntigenTest.swift */, @@ -4047,6 +4087,7 @@ 0138F53B25ECD44300A9B075 /* v2 */ = { isa = PBXGroup; children = ( + 34F687B3291553970062A6D2 /* ppdd_srs_parameters.pb.swift */, 0138F54125ECD44300A9B075 /* app_config_ios.pb.swift */, 0138F53C25ECD44300A9B075 /* app_features.pb.swift */, EB43DB9A2624439D0063687D /* corona_test_parameters.pb.swift */, @@ -5538,6 +5579,15 @@ path = Model; sourceTree = ""; }; + 3492C185292D16E900C48133 /* SRSDataProcessingInfo */ = { + isa = PBXGroup; + children = ( + 3492C186292D172000C48133 /* SRSDataProcessingInfoViewController.swift */, + 3492C188292D174400C48133 /* SRSDataProcessingInfoViewModel.swift */, + ); + path = SRSDataProcessingInfo; + sourceTree = ""; + }; 3494635027FC91D100403F7A /* AntigenTestProfileOverview */ = { isa = PBXGroup; children = ( @@ -5982,9 +6032,11 @@ children = ( 8F39D083263F63420096F4E2 /* els_otp.pb.swift */, 8F39D07C263F62A80096F4E2 /* els_otp_request_ios.pb.swift */, + 8F2FA2EA292B447000C6286A /* srs_otp_request_ios.pb.swift */, 505F506925C833A9004920EB /* edus_otp_request_ios.pb.swift */, 505F506A25C833A9004920EB /* edus_otp.pb.swift */, 505F506D25C833A9004920EB /* ppac_ios.pb.swift */, + 8F2FA2E9292B447000C6286A /* srs_otp.pb.swift */, 8F43A3CA25D55A3E008FD630 /* ppa_data_request_ios.pb.swift */, 8F43A3C925D55A3D008FD630 /* ppa_data.pb.swift */, 50F065392654FFC200774173 /* tri_state_boolean.pb.swift */, @@ -6442,6 +6494,7 @@ 50BC9046279EEE530000C930 /* Resources */ = { isa = PBXGroup; children = ( + 8F2FA2E3292B2D5C00C6286A /* OTPAuthorizationForSRS */, 8FBF34A828BD1C17000A86D7 /* OTPAuthorizationForELS */, 8FE68F9128A1469E003E86E1 /* ELS */, 8F613534287B1D3E00FD2DD8 /* PPA */, @@ -6857,6 +6910,7 @@ 3494D92B261EF8AC00EC532C /* UITextField+CGFloat.swift */, 2F3218CF248063E300A7AC0A /* UIView+Convenience.swift */, EB858D1F24E700D10048A0AA /* UIView+Screenshot.swift */, + DCD345B7291BF7B700E95A2D /* UIView+Utils.swift */, 85142500245DA0B3009D2791 /* UIViewController+Alert.swift */, 2F26CE2D248B9C4F00BE30EE /* UIViewController+BackButton.swift */, EB66267C25C3219900C4D7C2 /* UIViewController+Child.swift */, @@ -7099,6 +7153,7 @@ 5107E3D72459B2D60042FC9B /* Frameworks */, 0DFCC2692484D7A700E2811D /* ENA-Bridging-Header.h */, 0DFCC26F2484DC8200E2811D /* ENATests-Bridging-Header.h */, + 8F2FA2E7292B311B00C6286A /* Recovered References */, ); sourceTree = ""; usesTabs = 1; @@ -7249,6 +7304,7 @@ 85D759802459A82D008175F0 /* Services */ = { isa = PBXGroup; children = ( + 8FD5BE1F292BB68F00B0315B /* SRSService */, B15382DC248270220010F007 /* __tests__ */, ABB8DF5E2799BE9700DD99E4 /* CCLService */, 011216F22617360800320745 /* CoronaTestService */, @@ -7296,6 +7352,21 @@ path = StoreV1; sourceTree = ""; }; + 8F2FA2E3292B2D5C00C6286A /* OTPAuthorizationForSRS */ = { + isa = PBXGroup; + children = ( + 8F2FA2E4292B2D8400C6286A /* OTPAuthorizationForSRSResource.swift */, + ); + path = OTPAuthorizationForSRS; + sourceTree = ""; + }; + 8F2FA2E7292B311B00C6286A /* Recovered References */ = { + isa = PBXGroup; + children = ( + ); + name = "Recovered References"; + sourceTree = ""; + }; 8F3905872715B06400A90E79 /* More */ = { isa = PBXGroup; children = ( @@ -7397,6 +7468,14 @@ path = AccompanyingCertificates; sourceTree = ""; }; + 8F5C4F922919C24800135070 /* __tests__ */ = { + isa = PBXGroup; + children = ( + 8F5C4F932919C26700135070 /* SRSConsentViewModelTests.swift */, + ); + path = __tests__; + sourceTree = ""; + }; 8F613534287B1D3E00FD2DD8 /* PPA */ = { isa = PBXGroup; children = ( @@ -7434,6 +7513,17 @@ path = HTMLViewController; sourceTree = ""; }; + 8FA3E9C42913F2E8000512FC /* SRSConsent */ = { + isa = PBXGroup; + children = ( + 8F5C4F922919C24800135070 /* __tests__ */, + 8FA3E9C52913F334000512FC /* SRSConsentViewController.swift */, + 8FA3E9C72913F34A000512FC /* SRSConsentViewModel.swift */, + 8F282D622919CA79009F2184 /* SRSFlowType.swift */, + ); + path = SRSConsent; + sourceTree = ""; + }; 8FA88FDE261668EE00FD36DA /* __tests__ */ = { isa = PBXGroup; children = ( @@ -7473,6 +7563,15 @@ path = OTPAuthorizationForELS; sourceTree = ""; }; + 8FD5BE1F292BB68F00B0315B /* SRSService */ = { + isa = PBXGroup; + children = ( + 8FD5BE20292BB95400B0315B /* SRSService.swift */, + 8FDFEF64292CD91400707FE2 /* MockSrsService.swift */, + ); + path = SRSService; + sourceTree = ""; + }; 8FDB9401261FBA0F00BA8CB1 /* Submisstion */ = { isa = PBXGroup; children = ( @@ -7606,6 +7705,7 @@ 01A23684251A22E90043D9F8 /* ExposureSubmissionQRInfoModelTests.swift */, 524C4291256587B900EBC3B0 /* ExposureSubmissionTestResultConsentViewModelTests.swift */, AB1EEC0F26382BC20001D1DB /* ExposureSubmissionIntroViewModelTests.swift */, + DCE0A445291E4B79008721D8 /* SRSTestTypeSelectionViewModelTests.swift */, ); path = __tests__; sourceTree = ""; @@ -7643,6 +7743,8 @@ isa = PBXGroup; children = ( EB67B5E5262AD65900218EC7 /* ExposureSubmissionCheckins */, + 8FA3E9C42913F2E8000512FC /* SRSConsent */, + 3492C185292D16E900C48133 /* SRSDataProcessingInfo */, 2F80CFDA247EDDB3000F06AF /* ExposureSubmissionHotlineViewController.swift */, 503DB1A6255D822E00576E57 /* ExposureSubmissionIntroViewController.swift */, 503DB1AB255D826900576E57 /* ExposureSubmissionIntroViewModel.swift */, @@ -7666,6 +7768,7 @@ BA6F006F265FD2590020721B /* TestCertificate */, 347A36AC27E8CA6700DA82D2 /* TestOwnerSelection */, BAD962F925668E5D00FAB615 /* TestResultAvailable */, + DC4618C229155AEF0035CAA7 /* TestTypeSelection */, ); path = Controller; sourceTree = ""; @@ -8528,6 +8631,7 @@ children = ( ABFDBEFB281AD7A500A11FA8 /* __tests__ */, ABFDBEF9281933A000A11FA8 /* KeySubmissionResource.swift */, + 8FDFEF62292C1E5400707FE2 /* SRSKeySubmissionResource.swift */, ); path = KeySubmission; sourceTree = ""; @@ -9948,6 +10052,7 @@ isa = PBXGroup; children = ( BAB6C7FD25C4630800E042FB /* PPACError.swift */, + 8F75219129266B1200F1B3DE /* SRSError.swift */, BAB6C7F825C462E600E042FB /* PPACToken.swift */, BAB6C7EE25C4626100E042FB /* TimestampedToken.swift */, ); @@ -10432,6 +10537,7 @@ isa = PBXGroup; children = ( 5001DE8926E20AC6004242FA /* AuthorizeOtpEdus.swift */, + 8F011DF72927D4AB00DAD275 /* authorizeOtpSrs.swift */, 5001DE8526E20791004242FA /* KeySubmission.swift */, 5001DE7926E2019A004242FA /* LocalStatistics.swift */, 5001DE7226E200DC004242FA /* QRPosterTemplate.swift */, @@ -10497,6 +10603,15 @@ path = HomeLinkCardView; sourceTree = ""; }; + DC4618C229155AEF0035CAA7 /* TestTypeSelection */ = { + isa = PBXGroup; + children = ( + DC4618C329155B2F0035CAA7 /* SRSTestTypeSelectionViewController.swift */, + DC4618C529155E560035CAA7 /* SRSTestTypeSelectionViewModel.swift */, + ); + path = TestTypeSelection; + sourceTree = ""; + }; DC7003052858A7D200B164AC /* Mocks */ = { isa = PBXGroup; children = ( @@ -11124,6 +11239,7 @@ ABE9612D25EFEB4E00D0F699 /* DMInstallationDateViewController.swift in Sources */, 50BD2E6224FE1E8700932566 /* AppInformationModel.swift in Sources */, ABB8DF602799BEC300DD99E4 /* CCLService.swift in Sources */, + DC4618C629155E560035CAA7 /* SRSTestTypeSelectionViewModel.swift in Sources */, 50AF181F27A2E85B00BAA9DB /* Locator+RegistrationToken.swift in Sources */, BA41016426F8D8F60073F28D /* CovPassCheckInformationViewModel.swift in Sources */, B1A89F3B24819CE800DA1CEC /* SettingsLabelCell.swift in Sources */, @@ -11234,6 +11350,7 @@ BA6C8B05254D6B1F008344F5 /* RiskCalculation.swift in Sources */, 01F3323527146EF5005CE21E /* RecycleBinViewModel.swift in Sources */, BA6D1640255ADD0500ED3492 /* DMSwitchCellViewModel.swift in Sources */, + 3492C189292D174400C48133 /* SRSDataProcessingInfoViewModel.swift in Sources */, AB182015275E1FE50054B873 /* HygieneRulesInfoViewController.swift in Sources */, ABE5E0BA269363B900D50C2B /* CountrySelectionCell.swift in Sources */, BA291AC3275A48CA003C42C2 /* DMTicketValidationViewModel.swift in Sources */, @@ -11260,6 +11377,7 @@ 018FD47A2695EEDB000088F8 /* HealthCertificateValidationPassedViewModel.swift in Sources */, 01C6AC3A252B2A500052814D /* UIImage+Color.swift in Sources */, BA01BA64264423CF00D906B6 /* HealthCertificateAttributedTextCell.swift in Sources */, + 8FD5BE21292BB95400B0315B /* SRSService.swift in Sources */, BA6C8AA6254D63A0008344F5 /* MinutesAtAttenuationFilter.swift in Sources */, 8FB776EF26EA3255005255BB /* DMBoosterRulesViewModel.swift in Sources */, 35B2FABA25B9D3F3009ABC8E /* ENATaskExecutionDelegate.swift in Sources */, @@ -11285,6 +11403,7 @@ 35EA684225553AE300335F73 /* DownloadedPackagesSQLLiteStoreV2.swift in Sources */, 2FC0357124B5B70700E234AC /* Error+FAQUrl.swift in Sources */, 8FE68F9728A15BAC003E86E1 /* ELSSubmitReceiveModel.swift in Sources */, + 8F282D632919CA79009F2184 /* SRSFlowType.swift in Sources */, 2EB7D6892680770000633D30 /* AsyncOperation.swift in Sources */, 010A47682754C7E7000AA80E /* TicketValidationResultTokenSendModel.swift in Sources */, 017EE86326779E1A00CD18F6 /* RecoveryEntry+Extension.swift in Sources */, @@ -11390,6 +11509,7 @@ BAB8F2EF2695C4A500AF68A3 /* ValidationInformationViewController.swift in Sources */, BADD36EB260B4FBA00DD282A /* EditCheckinDetailViewController.swift in Sources */, 019789342746920600AAC31C /* TicketValidationResultViewModel.swift in Sources */, + 8FDFEF63292C1E5400707FE2 /* SRSKeySubmissionResource.swift in Sources */, 50BC903F279EECCA0000C930 /* Locator+TicketValidationResultToken.swift in Sources */, 01734B6E255D73E400E60A8B /* ENExposureConfiguration+Convenience.swift in Sources */, 018105D828115D1900C40268 /* DMRevocationListViewModel.swift in Sources */, @@ -11400,6 +11520,7 @@ BAC7419F26414D4A00955311 /* SimpleTextCellViewModel.swift in Sources */, 01E10278266BC4B1009ED803 /* TestCertificateRequestCellModel.swift in Sources */, 01909874257E64BD0065D050 /* DiaryDayViewController.swift in Sources */, + 8FA3E9C82913F34A000512FC /* SRSConsentViewModel.swift in Sources */, BADD36ED260B4FBA00DD282A /* GradientBackgroundView.swift in Sources */, B1FC2D2024D9C8DF00083C81 /* SAP_TemporaryExposureKey+DeveloperMenu.swift in Sources */, 717D21E9248C022E00D9717E /* DynamicTableViewHtmlCell.swift in Sources */, @@ -11483,6 +11604,7 @@ 50FA072B26023784003C1E46 /* TraceWarningPackageDownload.swift in Sources */, BA16BBF327B2BFD700C21B87 /* SerialSecureCacheMigrator.swift in Sources */, BA258CB0281AB7A3002C56D4 /* Locator+AvailableHours.swift in Sources */, + 8F2FA2EB292B447100C6286A /* srs_otp.pb.swift in Sources */, 50690CB32696FA0300127CB2 /* HealthCertificateValidationOnboardedCountriesProvider.swift in Sources */, 50C0298825E4067400460FA4 /* AgeGroup.swift in Sources */, 01992ECB27D2735600BC16DA /* LoadingScreenViewController.swift in Sources */, @@ -11506,6 +11628,8 @@ 019C9F2D258951B700B26392 /* ContactPersonEncounter.swift in Sources */, 35E121DC25B273280098D754 /* key_figure_card.pb.swift in Sources */, 711EFCC72492EE31005FEF21 /* ENAFooterView.swift in Sources */, + 8F011DF82927D4AB00DAD275 /* authorizeOtpSrs.swift in Sources */, + DC4618C429155B2F0035CAA7 /* SRSTestTypeSelectionViewController.swift in Sources */, DC03798428EDA39800E1EE20 /* HomeLinkCardView.swift in Sources */, BAE11F5728217F0100CA2790 /* FetchDaysServiceHelper.swift in Sources */, AB7DE73925C8528700C52B3F /* ExposureDetectionSurveyCellModel.swift in Sources */, @@ -11519,6 +11643,7 @@ EB2394A024E5492900E71225 /* BackgroundAppRefreshViewModel.swift in Sources */, EE22DB8C247FB43A001B0A71 /* DescriptionTableViewCell.swift in Sources */, 2F3218D0248063E300A7AC0A /* UIView+Convenience.swift in Sources */, + 8FA3E9C62913F334000512FC /* SRSConsentViewController.swift in Sources */, 0104CBCF273EAC5B009A543D /* TicketValidationResultToken.swift in Sources */, 0138F54825ECD44300A9B075 /* app_features.pb.swift in Sources */, BA0A1E3F26A5CC090045E712 /* dsc_list.pb.swift in Sources */, @@ -11601,6 +11726,7 @@ ABC506D8274F9DF400698A95 /* TicketValidationAccessToken.swift in Sources */, EB5FE15C2599F5E400797E4E /* ENActivityHandling.swift in Sources */, 01D6816D27C908C900FF6D18 /* HealthCertificateReissuanceCoordinator.swift in Sources */, + 8F2FA2E5292B2D8400C6286A /* OTPAuthorizationForSRSResource.swift in Sources */, BA6C8AB9254D64D3008344F5 /* MinutesAtAttenuationWeight.swift in Sources */, 7154EB4C247E862100A467FF /* ExposureDetectionLoadingCell.swift in Sources */, 01164CE626C57BB5007F9C3E /* OnBehalfTraceLocationSelectionEmptyStateViewModel.swift in Sources */, @@ -11621,6 +11747,7 @@ 94EAF87525B6CAA000BE1F40 /* DeltaOnboardingNewVersionFeaturesViewController.swift in Sources */, 8FB776ED26EA323C005255BB /* DMBoosterRulesViewController.swift in Sources */, AB0246D92716D704002668A1 /* RecycleBinItem.swift in Sources */, + 8F75219229266B1200F1B3DE /* SRSError.swift in Sources */, 01EA17FE259217B100E98E02 /* HomeState.swift in Sources */, 018105D728115D1600C40268 /* DMRevocationListViewController.swift in Sources */, 2FA9E39B24D2F4A10030561C /* ExposureSubmissionService+Protocol.swift in Sources */, @@ -11719,6 +11846,7 @@ 01F52F8A2550679600997A26 /* RiskCalculationExposureWindow.swift in Sources */, 01F2A5C32581183800DA96A6 /* DiaryDayEntryTableViewCell.swift in Sources */, AB7420CB251B7D93006666AC /* DeltaOnboardingCrossCountrySupportViewController.swift in Sources */, + DCD345B8291BF7B700E95A2D /* UIView+Utils.swift in Sources */, B19FD7132491A08500A9D56A /* SAP_SemanticVersion+Compare.swift in Sources */, 5001DE9A26E2146E004242FA /* Locator+ValidationOnboardedCountries.swift in Sources */, B1B381432472EF8B0056BEEE /* HTTPClient+Configuration.swift in Sources */, @@ -11833,6 +11961,7 @@ 01A6C2F62626DE1300B6E95A /* HomeTestRegistrationCellModel.swift in Sources */, B10FD5ED246EAADC00E9D7F2 /* AppInformationDetailViewController.swift in Sources */, AB6887EA25D6757700907465 /* ContactDiaryMigration2To3.swift in Sources */, + 34D9977F29264190000EA8BF /* SubmissionTestType.swift in Sources */, ABD430F627FC6A7E0063666C /* Locator+KIDTypeChunk.swift in Sources */, 351E630C256C5B9C00D89B29 /* LabeledCountriesCell.swift in Sources */, 017AD114259DCD3400FA2B3F /* HomeShownPositiveTestResultCellModel.swift in Sources */, @@ -12026,6 +12155,7 @@ DC03798E28F449D000E1EE20 /* HomeLinkCard.swift in Sources */, BA8A528327EB4F6D007A93DE /* FamilyNameTextFieldCell.swift in Sources */, 5071A88226EB80D900B09B40 /* ServiceError.swift in Sources */, + 34F687B4291553970062A6D2 /* ppdd_srs_parameters.pb.swift in Sources */, 013069522705D4A900D945E3 /* QRScannerTooltipViewController.swift in Sources */, 8FEC1231275656AC00C5DADC /* TicketValidationAllowList.swift in Sources */, 017D8DAC263BEF920009E91C /* HealthCertificateServiceError.swift in Sources */, @@ -12044,11 +12174,13 @@ 01C6AC32252B29C00052814D /* QRScannerError.swift in Sources */, 345D1E6627BA90CE00F79EFD /* OverviewLabelTableViewCell.swift in Sources */, 8FB776FB26F28336005255BB /* HealthCertificateQRCodeParser.swift in Sources */, + 3492C187292D172000C48133 /* SRSDataProcessingInfoViewController.swift in Sources */, ABC506BB2747DC9600698A95 /* EncryptAndSignResult.swift in Sources */, 0196FE8527E4EFCC00B2729E /* UserCoronaTest.swift in Sources */, AB6993AF2608F39E00232AD7 /* TraceWarningMatching.swift in Sources */, 01909878257E64BD0065D050 /* DiaryEditEntriesViewModel.swift in Sources */, BA06D5B325DC11C60039D1DB /* DiaryAddAndEditEntryModel.swift in Sources */, + 8FDFEF65292CD91400707FE2 /* MockSrsService.swift in Sources */, 0104CBCD273EAC46009A543D /* TicketValidationError.swift in Sources */, 5013B18726EBA3FE00A2DD54 /* QRScannerViewController.swift in Sources */, 01653FA526CD373000E21FB7 /* OnBehalfCheckinSubmissionService.swift in Sources */, @@ -12246,6 +12378,7 @@ B14D0CD9246E946E00D5BEBC /* ExposureDetection.swift in Sources */, BA69A7D825CD787D00023265 /* SelectValueTableViewController.swift in Sources */, EBA403D12589260D00D1F039 /* ColorCompatibility.swift in Sources */, + 8F2FA2EC292B447100C6286A /* srs_otp_request_ios.pb.swift in Sources */, B161782524804AC3006E435A /* DownloadedPackagesSQLLiteStoreV1.swift in Sources */, 50AA521D25FA590D000C77BB /* HTTPMethod.swift in Sources */, BADBC81B25D6A7FF00488DB7 /* BaseDataDonationViewModel.swift in Sources */, @@ -12361,7 +12494,6 @@ 3421AFD428102C800059802B /* AntigenTestProfileOverviewViewModelTests.swift in Sources */, 2EF6906E26A07FBB006DE99F /* FooterViewControllerTests.swift in Sources */, 019DDEDF26F1E48C00390509 /* VaccinationStateCellModelTests.swift in Sources */, - DC3B278D28F55B6D00A55769 /* SAP_Internal_Stats_LinkCard+Mock.swift in Sources */, 0156F05C25D2DA2300543640 /* DeadmanNotificationManagerTests.swift in Sources */, 2E4C3FE4263A91FD00E9FAFF /* CreateAntigenTestProfileViewModelTests.swift in Sources */, ABFDBEFD281AD7C000A11FA8 /* KeySubmissionResourceTests.swift in Sources */, @@ -12569,6 +12701,7 @@ 01D16C6024ED6D9A007DB387 /* MockBackgroundRefreshStatusProvider.swift in Sources */, 019155762684AE9600D49C88 /* XCTestCase+HealthCertificate.swift in Sources */, 0120ECFE2587631200F78944 /* DiaryDayAddCellModelTest.swift in Sources */, + 8F5C4F942919C26700135070 /* SRSConsentViewModelTests.swift in Sources */, BA0E47DD266FC6850037F552 /* HealthCertificateQRCodeCellViewModelTests.swift in Sources */, 01DE607B274517B300E12FA4 /* TicketValidationQRCodeParserTests.swift in Sources */, BAAAF13426739FBA00F9666B /* HealthCertificateOverviewViewModelTests.swift in Sources */, @@ -12661,6 +12794,7 @@ ABC506CB274BAA5F00698A95 /* DCCEncryptionTests.swift in Sources */, 015178C22507D2E50074F095 /* ExposureSubmissionSymptomsOnsetViewControllerTests.swift in Sources */, 0154EAD827FDD87C00F1D8D5 /* FamilyMemberCoronaTestsViewModelTest.swift in Sources */, + DCE0A446291E4B79008721D8 /* SRSTestTypeSelectionViewModelTests.swift in Sources */, A32842612490E2AC006B1F09 /* ExposureSubmissionWarnOthersViewControllerTests.swift in Sources */, BAF6A46E27A15DDC008DFAF5 /* CachePolicyTests.swift in Sources */, AB5F84B424F8FA26000400D4 /* SerialMigratorTests.swift in Sources */, @@ -13605,6 +13739,7 @@ CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = 523TP53AQF; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 523TP53AQF; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -13628,6 +13763,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "de.rki.coronawarnapp-dev"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = $IPHONE_APP_DIST_PROF_SPECIFIER; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Corona-Warn-App-Dev1"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) USE_DEV_PK_FOR_SIG_VERIFICATION DISABLE_CERTIFICATE_PINNING"; SWIFT_OBJC_BRIDGING_HEADER = "ENA-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/Icons - ContactJournal.imageset/Contents.json b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/Icons - ContactJournal.imageset/Contents.json new file mode 100644 index 00000000000..f621a528cc8 --- /dev/null +++ b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/Icons - ContactJournal.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "icons_contactjournal_light.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "icons_contactjournal_dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/Icons - ContactJournal.imageset/icons_contactjournal_dark.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/Icons - ContactJournal.imageset/icons_contactjournal_dark.pdf new file mode 100644 index 00000000000..13c12feda95 Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/Icons - ContactJournal.imageset/icons_contactjournal_dark.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/Icons - ContactJournal.imageset/icons_contactjournal_light.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/Icons - ContactJournal.imageset/icons_contactjournal_light.pdf new file mode 100644 index 00000000000..c6c4852bd85 Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/Icons - ContactJournal.imageset/icons_contactjournal_light.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/Icons_Risikoermittlung/Contents.json b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/Icons_Risikoermittlung/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/Icons_Risikoermittlung/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-No-Certificate-icon.imageset/Contents.json b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-No-Certificate-icon.imageset/Contents.json new file mode 100644 index 00000000000..7b74e190192 --- /dev/null +++ b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-No-Certificate-icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "SRS-No-Certificate-icon.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "SRS-No-Certificate-icon-dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-No-Certificate-icon.imageset/SRS-No-Certificate-icon-dark.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-No-Certificate-icon.imageset/SRS-No-Certificate-icon-dark.pdf new file mode 100644 index 00000000000..950b8009d03 Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-No-Certificate-icon.imageset/SRS-No-Certificate-icon-dark.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-No-Certificate-icon.imageset/SRS-No-Certificate-icon.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-No-Certificate-icon.imageset/SRS-No-Certificate-icon.pdf new file mode 100644 index 00000000000..4f27d8b295b Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-No-Certificate-icon.imageset/SRS-No-Certificate-icon.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Positive-icon.imageset/Contents.json b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Positive-icon.imageset/Contents.json new file mode 100644 index 00000000000..7ef2afb917f --- /dev/null +++ b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Positive-icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "SRS-Positive-icon.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "SRS-Positive-icon-dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Positive-icon.imageset/SRS-Positive-icon-dark.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Positive-icon.imageset/SRS-Positive-icon-dark.pdf new file mode 100644 index 00000000000..148f1372aca Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Positive-icon.imageset/SRS-Positive-icon-dark.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Positive-icon.imageset/SRS-Positive-icon.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Positive-icon.imageset/SRS-Positive-icon.pdf new file mode 100644 index 00000000000..0da9346256e Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Positive-icon.imageset/SRS-Positive-icon.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Warn-Others-icon.imageset/Contents.json b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Warn-Others-icon.imageset/Contents.json new file mode 100644 index 00000000000..691e8305416 --- /dev/null +++ b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Warn-Others-icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "SRS-Warn-Others-icon.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "SRS-Warn-Others-icon-dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Warn-Others-icon.imageset/SRS-Warn-Others-icon-dark.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Warn-Others-icon.imageset/SRS-Warn-Others-icon-dark.pdf new file mode 100644 index 00000000000..214d9c479bc Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Warn-Others-icon.imageset/SRS-Warn-Others-icon-dark.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Warn-Others-icon.imageset/SRS-Warn-Others-icon.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Warn-Others-icon.imageset/SRS-Warn-Others-icon.pdf new file mode 100644 index 00000000000..b47a2d51c74 Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Common/SRS-Warn-Others-icon.imageset/SRS-Warn-Others-icon.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_Anruf.imageset/Illu_Submission_Anruf-Dark.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_Anruf.imageset/Illu_Submission_Anruf-Dark.pdf deleted file mode 100644 index 42b1c20e1de..00000000000 Binary files a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_Anruf.imageset/Illu_Submission_Anruf-Dark.pdf and /dev/null differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_Anruf.imageset/Illu_Submission_Anruf.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_Anruf.imageset/Illu_Submission_Anruf.pdf deleted file mode 100644 index 134356d0d74..00000000000 Binary files a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_Anruf.imageset/Illu_Submission_Anruf.pdf and /dev/null differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_TAN.imageset/Contents.json b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_PositiveSelfTest.imageset/Contents.json similarity index 75% rename from src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_TAN.imageset/Contents.json rename to src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_PositiveSelfTest.imageset/Contents.json index 5174165085e..c97c9682920 100644 --- a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_TAN.imageset/Contents.json +++ b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_PositiveSelfTest.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Illu_Submission_TAN.pdf", + "filename" : "Illu_Submission_PositiveSelfTest_light.pdf", "idiom" : "universal" }, { @@ -11,7 +11,7 @@ "value" : "dark" } ], - "filename" : "Illu_Submission_TAN-Dark.pdf", + "filename" : "Illu_Submission_PositiveSelfTest_dark.pdf", "idiom" : "universal" } ], diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_PositiveSelfTest.imageset/Illu_Submission_PositiveSelfTest_dark.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_PositiveSelfTest.imageset/Illu_Submission_PositiveSelfTest_dark.pdf new file mode 100644 index 00000000000..d224fc34557 Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_PositiveSelfTest.imageset/Illu_Submission_PositiveSelfTest_dark.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_PositiveSelfTest.imageset/Illu_Submission_PositiveSelfTest_light.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_PositiveSelfTest.imageset/Illu_Submission_PositiveSelfTest_light.pdf new file mode 100644 index 00000000000..18894a78860 Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_PositiveSelfTest.imageset/Illu_Submission_PositiveSelfTest_light.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_Anruf.imageset/Contents.json b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_SRS.imageset/Contents.json similarity index 79% rename from src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_Anruf.imageset/Contents.json rename to src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_SRS.imageset/Contents.json index 7d79cae7d69..bf444254f23 100644 --- a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_Anruf.imageset/Contents.json +++ b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_SRS.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Illu_Submission_Anruf.pdf", + "filename" : "Illu_Submission_SRS_light.pdf", "idiom" : "universal" }, { @@ -11,7 +11,7 @@ "value" : "dark" } ], - "filename" : "Illu_Submission_Anruf-Dark.pdf", + "filename" : "Illu_Submission_SRS_dark.pdf", "idiom" : "universal" } ], diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_SRS.imageset/Illu_Submission_SRS_dark.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_SRS.imageset/Illu_Submission_SRS_dark.pdf new file mode 100644 index 00000000000..7ec8a8b6191 Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_SRS.imageset/Illu_Submission_SRS_dark.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_SRS.imageset/Illu_Submission_SRS_light.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_SRS.imageset/Illu_Submission_SRS_light.pdf new file mode 100644 index 00000000000..97956f2fe8d Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_SRS.imageset/Illu_Submission_SRS_light.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_TAN.imageset/Illu_Submission_TAN-Dark.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_TAN.imageset/Illu_Submission_TAN-Dark.pdf deleted file mode 100644 index 3ede424d6af..00000000000 Binary files a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_TAN.imageset/Illu_Submission_TAN-Dark.pdf and /dev/null differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_TAN.imageset/Illu_Submission_TAN.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_TAN.imageset/Illu_Submission_TAN.pdf deleted file mode 100644 index 667c7eece80..00000000000 Binary files a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_Submission_TAN.imageset/Illu_Submission_TAN.pdf and /dev/null differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_WarningAfterSelfTest.imageset/Contents.json b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_WarningAfterSelfTest.imageset/Contents.json new file mode 100644 index 00000000000..c91c6e3c63e --- /dev/null +++ b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_WarningAfterSelfTest.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "Illu_WarningAfterSelfTest_light.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Illu_WarningAfterSelfTest_dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_WarningAfterSelfTest.imageset/Illu_WarningAfterSelfTest_dark.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_WarningAfterSelfTest.imageset/Illu_WarningAfterSelfTest_dark.pdf new file mode 100644 index 00000000000..213a32fee17 Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_WarningAfterSelfTest.imageset/Illu_WarningAfterSelfTest_dark.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_WarningAfterSelfTest.imageset/Illu_WarningAfterSelfTest_light.pdf b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_WarningAfterSelfTest.imageset/Illu_WarningAfterSelfTest_light.pdf new file mode 100644 index 00000000000..59b99695a3f Binary files /dev/null and b/src/xcode/ENA/ENA/Resources/Assets/Assets.xcassets/Illustrations/Illu_WarningAfterSelfTest.imageset/Illu_WarningAfterSelfTest_light.pdf differ diff --git a/src/xcode/ENA/ENA/Resources/Localization/de.lproj/Localizable.legal.strings b/src/xcode/ENA/ENA/Resources/Localization/de.lproj/Localizable.legal.strings index 94e3a834b42..1e356e4cdba 100644 --- a/src/xcode/ENA/ENA/Resources/Localization/de.lproj/Localizable.legal.strings +++ b/src/xcode/ENA/ENA/Resources/Localization/de.lproj/Localizable.legal.strings @@ -154,6 +154,18 @@ "Checkin_Information_Legal_SubHeadline_2" = "Unter „Meine Check-ins“ können Sie einen Check-in jederzeit löschen. In diesem Fall werden Sie nur gewarnt, wenn Ihr Smartphone die Zufalls-IDs des positiv getesteten Nutzers empfangen und eine Risiko-Begegnung ermittelt hat."; +/* SRS Consent Screen */ + +"SRS_Consent_Legal_Headline" = "Ihr Einverständnis"; + +"SRS_Consent_Legal_Description" = "Durch Antippen von Einverstanden willigen Sie in die folgenden Schritte ein:"; + +"SRS_Consent_Legal_Text_1" = "Die App teilt Ihr positives Testergebnis, um andere Nutzer der Corona-Warn-App zu warnen, denen Sie begegnet sind oder die zeitgleich am selben Event oder Ort wie Sie eingecheckt waren."; + +"SRS_Consent_Legal_Text_2" = "Bevor das Testergebnis geteilt werden kann, wird die Echtheit Ihrer App einmalig geprüft. Dazu wird durch Ihr Smartphone eine eindeutige Kennung erzeugt und an Apple in die USA oder andere Drittländer übermittelt, damit Apple die Echtheit Ihrer App gegenüber dem RKI bestätigen kann. Die Kennung enthält Informationen über die Version Ihres Smartphones und der App. Apple kann damit möglicherweise auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones stattgefunden hat. Weitere Angaben aus der App erhält Apple hierbei nicht."; + +"SRS_Consent_Legal_Text_3" = "Wenn Sie zusätzlich Angaben zum Beginn Ihrer Symptome machen, werden auch diese geteilt."; + /* TraceLocation */ "TraceLocation_Information_Legal_Headline_1" = "Datenschutz und Datensicherheit"; diff --git a/src/xcode/ENA/ENA/Resources/Localization/de.lproj/Localizable.strings b/src/xcode/ENA/ENA/Resources/Localization/de.lproj/Localizable.strings index 32c00955fca..6a93a3230c5 100644 --- a/src/xcode/ENA/ENA/Resources/Localization/de.lproj/Localizable.strings +++ b/src/xcode/ENA/ENA/Resources/Localization/de.lproj/Localizable.strings @@ -499,8 +499,7 @@ Bei ausgeschalteter Hintergrundaktualisierung müssen Sie die App täglich aufru "OnboardingInfo_alwaysStayInformedPage_title" = "Warnungen erhalten, Risiken erkennen"; -"OnboardingInfo_alwaysStayInformedPage_boldText" = "Die App kann Ihnen automatisch Mitteilungen senden und Sie unter anderem über Ihren Risikostatus oder die Gültigkeit Ihrer Zertifikate informieren. Passen Sie dafür die Mitteilungseinstellungen an."; - +"OnboardingInfo_alwaysStayInformedPage_boldText" = "Die App kann Ihnen automatisch Mitteilungen senden und Sie unter anderem über Ihren Risikostatus oder die Gültigkeit Ihrer Zertifikate informieren. Passen Sie dafür die Mitteilungseinstellungen an."; "OnboardingInfo_alwaysStayInformedPage_normalText" = "Auf diese Weise können Sie sich und Ihre Mitmenschen schützen und die Gültigkeit Ihrer Zertifikate im Blick behalten."; /* Onboarding EU Keys */ @@ -564,24 +563,26 @@ Bei ausgeschalteter Hintergrundaktualisierung müssen Sie die App täglich aufru "ExposureSubmission_DispatchSectionHeadline" = "Sie haben sich bereits testen lassen?"; -"ExposureSubmission_DispatchSectionHeadline2" = "Ihr PCR-Test ist positiv?"; +"ExposureSubmission_DispatchSectionHeadline2" = "Test registrieren"; "ExposureSubmissionDispatch_QRCodeButtonTitle" = "QR-Code scannen"; "ExposureSubmissionDispatch_QRCodeButtonDescription" = "Erhalten Sie Ihr Testergebnis und die Ihrer Familienmitglieder und fordern Sie Ihr COVID-Testzertifikat an."; -"ExposureSubmissionDispatch_TANButtonTitle" = "TAN für PCR-Test eingeben"; +"ExposureSubmissionDispatch_PositiveSelfTestButtonTitle" = "Selbsttest positiv?"; -"ExposureSubmissionDispatch_TANButtonDescription" = "Ihnen liegt eine TAN für Ihren PCR-Test vor? Weiter zur TAN-Eingabe, um andere zu warnen."; +"ExposureSubmissionDispatch_PositiveSelfTestButtonDescription" = "Warnen Sie andere, wenn Ihr eigener Selbsttest positiv ist."; -"ExposureSubmissionDispatch_HotlineButtonTitle" = "TAN für PCR-Test anfragen"; +"ExposureSubmissionDispatch_SRSButtonTitle" = "Test positiv und kein Ergebnis in der App?"; -"ExposureSubmissionDispatch_HotlineButtonDescription" = "Rufen Sie uns an und erhalten Sie eine TAN für Ihren PCR-Test."; +"ExposureSubmissionDispatch_SRSButtonDescription" = "Warnen Sie andere mit Ihrem eigenen positiven Schnell- oder PCR-Test, auch wenn Ihr Test nicht registriert war oder Ihr Testergebnis nicht in der App zugestellt wurde."; "ExposureSubmissionDispatch_FindTestCentersTitle" = "Testmöglichkeit finden"; "ExposureSubmissionDispatch_FindTestCentersDescription" = "Finden Sie Teststellen in Ihrer Nähe und falls Probleme auftreten, können Sie diese melden. \nSie werden auf eine externe Website des RKI weitergeleitet."; +"SRS_submission_Invalid_OTP" = "Ein Fehler ist aufgetreten. Bitte versuchen Sie die Warnung erneut zu senden."; + /* Exposure Submission Hotline */ "ExposureSubmissionHotline_Title" = "TAN für PCR-Test anfragen"; @@ -673,15 +674,27 @@ Bei ausgeschalteter Hintergrundaktualisierung müssen Sie die App täglich aufru "ExposureSubmissionSuccess_PCR_listItem0" = "Sie sind sehr wahrscheinlich ansteckend. Isolieren Sie sich von anderen Personen."; -"ExposureSubmissionSuccess_PCR_listItem1" = "Das Gesundheitsamt wird sich möglicherweise in den nächsten Tagen bei Ihnen melden."; +"ExposureSubmissionSuccess_PCR_listItem1" = "Ihre Warnung wird dem Kontakt-Tagebuch hinzugefügt."; + +"ExposureSubmissionSuccess_PCR_listItem2" = "Das Gesundheitsamt wird sich möglicherweise in den nächsten Tagen bei Ihnen melden."; -"ExposureSubmissionSuccess_PCR_listItem2" = "Falls Sie Risikofaktoren für eine schwere COVID-19-Erkrankung haben, lassen Sie sich ärztlich beraten. Bekannte Risikofaktoren sind z.B. Alter über 60 Jahre, Immunschwäche, Diabetes und hohes Übergewicht."; +"ExposureSubmissionSuccess_PCR_listItem3" = "Falls Sie Risikofaktoren für eine schwere COVID-19-Erkrankung haben, lassen Sie sich ärztlich beraten. Bekannte Risikofaktoren sind z.B. Alter über 60 Jahre, Immunschwäche, Diabetes und hohes Übergewicht."; "ExposureSubmissionSuccess_RAT_listItem0" = "Sie sind sehr wahrscheinlich ansteckend. Isolieren Sie sich von anderen Personen."; -"ExposureSubmissionSuccess_RAT_listItem1" = "Machen Sie einen PCR-Test, um dieses Test-Ergebnis zu verifizieren."; +"ExposureSubmissionSuccess_RAT_listItem1" = "Ihre Warnung wird dem Kontakt-Tagebuch hinzugefügt."; + +"ExposureSubmissionSuccess_RAT_listItem2" = "Machen Sie einen PCR-Test, um dieses Test-Ergebnis zu verifizieren."; + +"ExposureSubmissionSuccess_RAT_listItem3" = "Das Gesundheitsamt wird sich möglicherweise in den nächsten Tagen bei Ihnen melden."; + +"ExposureSubmissionSuccess_SRS_listItem0" = "Sie sind sehr wahrscheinlich ansteckend. Isolieren Sie sich von anderen Personen."; -"ExposureSubmissionSuccess_RAT_listItem2" = "Das Gesundheitsamt wird sich möglicherweise in den nächsten Tagen bei Ihnen melden."; +"ExposureSubmissionSuccess_SRS_listItem1" = "Ihre Warnung wird dem Kontakt-Tagebuch hinzugefügt."; + +"ExposureSubmissionSuccess_SRS_listItem2" = "Falls noch nicht geschehen, machen Sie einen PCR-Test, um dieses Testergebnis zu verifizieren."; + +"ExposureSubmissionSuccess_SRS_listItem3" = "Das Gesundheitsamt wird sich möglicherweise in den nächsten Tagen bei Ihnen melden."; "ExposureSubmissionSuccess_listItem2_1" = "Bitte beobachten Sie genau, wie sich Ihre Symptome entwickeln."; @@ -716,6 +729,39 @@ Bei ausgeschalteter Hintergrundaktualisierung müssen Sie die App täglich aufru "ExposureSubmissionTestresultAvailable_CloseAlertButtonContinue" = "Ergebnis anzeigen"; + +/* SRS Consent Screen */ + +"SRS_ConsentScreen_title" = "Ihr Einverständnis"; + +"SRS_ConsentScreen_header_section1" = "Andere warnen"; + +"SRS_ConsentScreen_title_description1" = "Bevor Sie andere warnen können, ist Ihr Einverständnis erforderlich."; + +"SRS_ConsentScreen_instruction1" = "Wenn Sie positiv getestet wurden, können Sie Ihre Mitmenschen über die App warnen.\nDie Warnung nach einem positiven Selbsttest oder mit einem Testergebnis, das nicht in der App vorliegt, erfordert die Prüfung der Echtheit der App."; + +"SRS_ConsentScreen_instruction2" = "So kann zugleich sichergestellt werden, dass eine solche Warnung nur nach Ablauf von %@ Tagen erneut möglich ist."; + +"SRS_ConsentScreen_Acknowledgement_1" = "Die Warnung anderer ist freiwillig. Wenn Sie Ihr Testergebnis teilen, helfen Sie jedoch mit, Ihre Mitmenschen vor Ansteckungen zu schützen."; + +"SRS_ConsentScreen_Acknowledgement_2" = "Ihre Identität bleibt geheim. Andere Nutzer erfahren nicht, wer sein Testergebnis geteilt hat."; + +"SRS_ConsentScreen_Acknowledgement_3" = "Unter „Meine Check-ins“ können Sie Ihre Events und Orte einsehen, deren eingecheckte Gäste gewarnt werden. Sie können einzelne Check-Ins auch entfernen und so von der Warnung ausschließen."; + +"SRS_ConsentScreen_Acknowledgement_4" = "Sie können Ihr Einverständnis abgeben, wenn Sie mindestens 16 Jahre alt sind."; + +"SRS_ConsentScreen_Acknowledgement_5" = "Sie können andere auch ohne Echtheitsprüfung warnen. Hierfür müssen Sie einen PCR- oder Schnelltest in der App registrieren und das Ergebnis in der App abrufen."; + +"SRS_ConsentScreen_Acknowledgement_6" = "Erteilen Sie im nächsten Schritt Ihre Erlaubnis zum Zugriff auf die Zufalls-IDs."; + +"SRS_ConsentScreen_DataProcessingDetailInfo" = "Ausführliche Informationen zur Echtheitsprüfung und den Datenschutzrisiken in den USA und anderen Drittländern"; + +/* SRSDataProcessingInfo */ + +"SRS_DataProcessingInfo_title" = "Prüfung der Echtheit und Drittlandsübermittlung"; + +"SRS_DataProcessingInfodescription" = "Um die Echtheit Ihrer App zu bestätigen, erzeugt Ihr Smartphone eine eindeutige Kennung, die Informationen über die Version Ihres Smartphones und der App enthält. Das ist erforderlich, um sicherzustellen, dass nur Nutzer eine Warnung nach einem positiven Selbsttest oder mit einem Testergebnis, das nicht in der App vorliegt, auslösen, die tatsächlich die Corona-Warn-App nutzen und nicht manipulierte Warnungen bereitstellen. Das Verfahren dient außerdem dazu, sicherzustellen, dass Warnungen nach einem positiven Selbsttest oder mit einem Testergebnis, das nicht in der App vorliegt, nur alle drei Monate möglich sind.\n\nDie bei der Echtheitsprüfung erzeugte eindeutige Kennung wird beim Auslösen der Warnung einmalig an Apple übermittelt. Dabei kann es auch zu einer Datenübermittlung in die USA oder andere Drittländer kommen. Dort besteht möglicherweise kein dem europäischen Recht entsprechendes Datenschutzniveau und Ihre europäischen Datenschutzrechte können eventuell nicht durchgesetzt werden. Insbesondere besteht die Möglichkeit, dass Sicherheitsbehörden im Drittland, auch ohne einen konkreten Verdacht, auf die übermittelten Daten bei Apple zugreifen und diese auswerten, beispielsweise indem sie Daten mit anderen Informationen verknüpfen. Dies betrifft nur die an Apple übermittelte Kennung. Möglicherweise kann Apple jedoch anhand der Kennung auf Ihre Identität schließen und nachvollziehen, dass die Echtheitsprüfung Ihres Smartphones im Rahmen einer Warnung anderer stattgefunden hat.\n\nWenn Sie mit der Drittlandsübermittlung nicht einverstanden sind, tippen Sie bitte nicht „Einverstanden“ an. Sie können die App weiterhin nutzen, eine Warnung anderer nach einem positiven Selbsttest oder mit einem Testergebnis, das nicht in der App vorliegt, ist dann jedoch nicht möglich."; + /* Exposure Submission Result */ "ExposureSubmissionResult_Title" = "Ihr Testergebnis"; @@ -785,7 +831,7 @@ Bei ausgeschalteter Hintergrundaktualisierung müssen Sie die App täglich aufru "ExposureSubmissionResult_familyMember_testPending" = "Das Testergebnis liegt noch nicht vor"; -"ExposureSubmissionResult_testPendingDesc" = "Sobald Ihr Testergebnis vorliegt, wird es Ihnen in der App angezeigt.\n\nSie bekommen Ihr Testergebnis auch außerhalb der App mitgeteilt. Falls Ihr Test positiv ist, bekommen Sie vom Gesundheitsamt eine Mitteilung.\n\nWenn Ihnen außerhalb der App ein positives Testergebnis mitgeteilt wurde, löschen Sie den aktuell in der App registrierten Test. Rufen Sie die unter „TAN anfragen” angegebene Nummer an, um eine TAN zu erhalten. Registrieren Sie dann Ihr Testergebnis mithilfe der TAN in der App."; +"ExposureSubmissionResult_testPendingDesc" = "Sobald Ihr Testergebnis vorliegt, wird es Ihnen in der App angezeigt.\n\nSie bekommen Ihr Testergebnis auch außerhalb der App mitgeteilt. Falls Ihr Test positiv ist, bekommen Sie vom Gesundheitsamt eine Mitteilung.\n\nWenn Ihnen außerhalb der App ein positives Testergebnis mitgeteilt wurde, löschen Sie den aktuell in der App registrierten Test."; "ExposureSubmissionResult_familyMember_testPendingDesc" = "Sobald das Testergebnis vorliegt, wird es Ihnen in der App angezeigt.\nSie bekommen das Testergebnis auch außerhalb der App mitgeteilt. Falls der Test positiv ist, bekommen Sie vom Gesundheitsamt eine Mitteilung."; @@ -925,6 +971,36 @@ Bei ausgeschalteter Hintergrundaktualisierung müssen Sie die App täglich aufru "ExposureSubmissionWarnOthers_continueButton" = "Einverstanden"; +/* Exposure Submission Warn Others Test Type Selection */ + +"ExposureSubmission_SRSTestTypeSelection_title" = "Art des Tests"; + +"ExposureSubmission_SRSTestTypeSelection_body" = "Bitte wählen Sie die Art des Tests aus, auf dessen Grundlage Sie warnen."; + +"ExposureSubmission_SRSTestTypeSelection_description" = "Die Angabe der Art des Tests dient nur der statistischen Auswertung. Die Angabe ist freiwillig. Sie wird nicht mit der Warnung oder Ihrer Identität verbunden. Wenn Sie keine Angabe machen wollen, wählen Sie „Keine Angabe“."; + +"ExposureSubmission_SRSTestTypeSelection_optionSRSSelfTest_title" = "Antigen-Schnelltest nicht in der App registriert"; + +"ExposureSubmission_SRSTestTypeSelection_optionSRSRegisteredRat_title" = "Antigen-Schnelltest in der App registriert, aber kein Ergebnis erhalten"; + +"ExposureSubmission_SRSTestTypeSelection_optionSRSRegisteredPcr_title" = "PCR-Labortest in der App registriert, aber kein Ergebnis erhalten"; + +"ExposureSubmission_SRSTestTypeSelection_optionSRSUnregisteredPcr_title" = "PCR-Labortest nicht in der App registriert"; + +"ExposureSubmission_SRSTestTypeSelection_optionSRSRapidPcr_title" = "PCR-Schnelltest (PoC-NAT-Test)"; + +"ExposureSubmission_SRSTestTypeSelection_optionSRSOther_title" = "Keine Angabe"; + +"ExposureSubmission_SRSTestTypeSelection_warnProcessCancelAlert_Title" = "Warn-Vorgang abbrechen?"; + +"ExposureSubmission_SRSTestTypeSelection_warnProcessCancelAlert_Message" = "Sind Sie sich sicher, dass Sie den Warn-Vorgang abbrechen wollen?\n\nWenn Ihr Test positiv war, können Sie mit einer Warnung helfen, Infektionsketten zu unterbrechen."; + +"ExposureSubmission_SRSTestTypeSelection_warnProcessCancelAlert_ActionContinue" = "Warnen fortsetzen"; + +"ExposureSubmission_SRSTestTypeSelection_warnProcessCancelAlert_ActionCancel" = "Nicht warnen"; + +"ExposureSubmission_SRSTestTypeSelection_primaryButtonTitle" = "Weiter"; + /* Exposure Submission Result */ "ExposureSubmissionResult_RemoveAlert_Title" = "Wollen Sie Ihr Testergebnis löschen?"; @@ -1395,9 +1471,11 @@ Bei ausgeschalteter Hintergrundaktualisierung müssen Sie die App täglich aufru /* Home Test Registration Card */ -"Home_TestRegistration_Title" = "Sie lassen sich testen?"; +"Home_TestRegistration_Title" = "Tests erfassen und andere warnen"; + +"Home_TestRegistration_Subtitle" = "Selbsttest, PCR- und Schnelltest"; -"Home_TestRegistration_Body" = "Finden Sie Teststellen in Ihrer Umgebung und registrieren Sie einen Test, um andere schneller warnen zu können, oder Ihr digitales COVID-Testzertifikat anzufordern."; +"Home_TestRegistration_Body" = "Warnen Sie andere, wenn Sie positiv getestet sind oder fordern Sie Ihr digitales COVID-Testzertifikat an. Finden Sie Teststellen in Ihrer Umgebung."; "Home_TestRegistration_Button" = "Weiter"; diff --git a/src/xcode/ENA/ENA/Source/AppDelegate & Globals/AppDelegate.swift b/src/xcode/ENA/ENA/Source/AppDelegate & Globals/AppDelegate.swift index e8e758c6138..e9854d585e9 100644 --- a/src/xcode/ENA/ENA/Source/AppDelegate & Globals/AppDelegate.swift +++ b/src/xcode/ENA/ENA/Source/AppDelegate & Globals/AppDelegate.swift @@ -527,6 +527,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, CoronaWarnAppDelegate, Re otpService: otpService ) + private lazy var srsService: SRSServiceProviding = SRSService( + restServicerProvider: restServiceProvider, + store: store, + ppacService: ppacService, + otpService: otpService, + configurationProvider: appConfigurationProvider + ) + private let recycleBin: RecycleBin private let restServiceProvider: RestServiceProviding @@ -866,6 +874,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, CoronaWarnAppDelegate, Re eventCheckoutService: eventCheckoutService, otpService: otpService, ppacService: ppacService, + srsService: srsService, cclService: cclService, healthCertificateService: healthCertificateService, healthCertificateRequestService: healthCertificateRequestService, diff --git a/src/xcode/ENA/ENA/Source/Client/Client.swift b/src/xcode/ENA/ENA/Source/Client/Client.swift index e552770eece..5258bb9d616 100644 --- a/src/xcode/ENA/ENA/Source/Client/Client.swift +++ b/src/xcode/ENA/ENA/Source/Client/Client.swift @@ -83,7 +83,7 @@ struct SubmissionPayload { let checkinProtectedReports: [SAP_Internal_Pt_CheckInProtectedReport] /// a transaction number - let tan: String + let tan: String? let submissionType: SAP_Internal_SubmissionPayload.SubmissionType } diff --git a/src/xcode/ENA/ENA/Source/Extensions/UIView+Utils.swift b/src/xcode/ENA/ENA/Source/Extensions/UIView+Utils.swift new file mode 100644 index 00000000000..1cbdbf41e77 --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Extensions/UIView+Utils.swift @@ -0,0 +1,18 @@ +// +// 🦠 Corona-Warn-App +// + +import UIKit + +extension UIView { + func roundCorners(corners: UIRectCorner, radius: CGFloat) { + let path = UIBezierPath( + roundedRect: bounds, + byRoundingCorners: corners, + cornerRadii: CGSize(width: radius, height: radius) + ) + let mask = CAShapeLayer() + mask.path = path.cgPath + layer.mask = mask + } +} diff --git a/src/xcode/ENA/ENA/Source/HTTPClient/LocatorsNotYetUsed/KeySubmission.swift b/src/xcode/ENA/ENA/Source/HTTPClient/LocatorsNotYetUsed/KeySubmission.swift index b7f6430bdae..6f72fb68389 100644 --- a/src/xcode/ENA/ENA/Source/HTTPClient/LocatorsNotYetUsed/KeySubmission.swift +++ b/src/xcode/ENA/ENA/Source/HTTPClient/LocatorsNotYetUsed/KeySubmission.swift @@ -11,22 +11,43 @@ extension Locator { // type: // comment: Custom error handling required static func keySubmission( - payload: SubmissionPayload, + payload: SubmissionPayload? = nil, + srsOtp: String? = nil, isFake: Bool ) -> Locator { let fake = String(isFake ? 1 : 0) let fakePadding = isFake ? String.getRandomString(of: 36) : "" - return Locator( - endpoint: .submission, - paths: ["version", "v1", "diagnosis-keys"], - method: .post, - defaultHeaders: [ - "Content-Type": "application/x-protobuf", - "cwa-authorization": payload.tan, - "cwa-fake": fake, - "cwa-header-padding": fakePadding - ] - ) + + // we have an extra special key in the header to differentiate SRS from normal submission + // for that we have the optional srsOtp argument in the function and based on it we + // either add the srs header or the normal submission header + if let srsOtp = srsOtp { + // SRS submission + return Locator( + endpoint: .submission, + paths: ["version", "v1", "diagnosis-keys"], + method: .post, + defaultHeaders: [ + "Content-Type": "application/x-protobuf", + "cwa-otp": srsOtp, + "cwa-fake": fake, + "cwa-header-padding": fakePadding + ] + ) + } else { + // Normal submission + return Locator( + endpoint: .submission, + paths: ["version", "v1", "diagnosis-keys"], + method: .post, + defaultHeaders: [ + "Content-Type": "application/x-protobuf", + "cwa-authorization": payload?.tan ?? "", + "cwa-fake": fake, + "cwa-header-padding": fakePadding + ] + ) + } } } diff --git a/src/xcode/ENA/ENA/Source/HTTPClient/LocatorsNotYetUsed/SubmitOnBehalf.swift b/src/xcode/ENA/ENA/Source/HTTPClient/LocatorsNotYetUsed/SubmitOnBehalf.swift index 405e6a387f4..2e6403c2166 100644 --- a/src/xcode/ENA/ENA/Source/HTTPClient/LocatorsNotYetUsed/SubmitOnBehalf.swift +++ b/src/xcode/ENA/ENA/Source/HTTPClient/LocatorsNotYetUsed/SubmitOnBehalf.swift @@ -22,7 +22,7 @@ extension Locator { method: .post, defaultHeaders: [ "Content-Type": "application/x-protobuf", - "cwa-authorization": payload.tan, + "cwa-authorization": payload.tan ?? "", "cwa-fake": fake, "cwa-header-padding": fakePadding ] diff --git a/src/xcode/ENA/ENA/Source/HTTPClient/LocatorsNotYetUsed/authorizeOtpSrs.swift b/src/xcode/ENA/ENA/Source/HTTPClient/LocatorsNotYetUsed/authorizeOtpSrs.swift new file mode 100644 index 00000000000..62d1ece7cb8 --- /dev/null +++ b/src/xcode/ENA/ENA/Source/HTTPClient/LocatorsNotYetUsed/authorizeOtpSrs.swift @@ -0,0 +1,27 @@ +// +// 🦠 Corona-Warn-App +// + +import Foundation + +extension Locator { + + // send: ProtoBuf SAP_Internal_Ppdd_SRSOneTimePasswordRequestIOS + // receive: JSON + // type: default + // comment: the endpoint for otp authorization for SRS + static func authorizeOtpSrs( + isFake: Bool + ) -> Locator { + let fake = String(isFake ? 1 : 0) + return Locator( + endpoint: .dataDonation, + paths: ["version", "v1", "ios", "srs"], + method: .post, + defaultHeaders: [ + "Content-Type": "application/x-protobuf", + "cwa-fake": fake + ] + ) + } +} diff --git a/src/xcode/ENA/ENA/Source/HTTPClient/Resources/KeySubmission/SRSKeySubmissionResource.swift b/src/xcode/ENA/ENA/Source/HTTPClient/Resources/KeySubmission/SRSKeySubmissionResource.swift new file mode 100644 index 00000000000..310734829e6 --- /dev/null +++ b/src/xcode/ENA/ENA/Source/HTTPClient/Resources/KeySubmission/SRSKeySubmissionResource.swift @@ -0,0 +1,85 @@ +// +// 🦠 Corona-Warn-App +// + +import Foundation + +enum SRSKeySubmissionResourceError: LocalizedError, Equatable { + case invalidPayloadOrHeader + case invalidOtp + case requestCouldNotBeBuilt + case serverError(Int) + + var errorDescription: String? { + switch self { + case let .serverError(code): + return "\(AppStrings.ExposureSubmissionError.other)\(code) \(AppStrings.ExposureSubmissionError.otherend)" + case .invalidPayloadOrHeader: + return "\(AppStrings.ExposureSubmissionError.errorPrefix) - Received an invalid payload or headers." + case .invalidOtp: + return "\(AppStrings.ExposureSubmissionDispatch.SRSSubmissionError.srsSubmissionInvalidOTP) - invalid OTP." + case .requestCouldNotBeBuilt: + return "\(AppStrings.ExposureSubmissionError.errorPrefix) - The submission request could not be built correctly." + } + } +} + +struct SRSKeySubmissionResource: Resource { + + // MARK: - Init + + init( + payload: SubmissionPayload, + srsOtp: String, + isFake: Bool = false, + trustEvaluation: TrustEvaluating = DefaultTrustEvaluation( + publicKeyHash: Environments().currentEnvironment().pinningKeyHashData + ) + ) { + self.locator = .keySubmission(srsOtp: srsOtp, isFake: isFake) + self.type = .default + self.receiveResource = EmptyReceiveResource() + self.trustEvaluation = trustEvaluation + + self.sendResource = ProtobufSendResource( + SAP_Internal_SubmissionPayload.with { + $0.requestPadding = payload.exposureKeys.submissionPadding + $0.keys = payload.exposureKeys + $0.checkIns = payload.checkins + // Consent needs always set to be false with SRS + $0.consentToFederation = false + $0.visitedCountries = payload.visitedCountries.map { $0.id } + $0.submissionType = payload.submissionType + $0.checkInProtectedReports = payload.checkinProtectedReports + } + ) + } + + // MARK: - Protocol Resource + + let trustEvaluation: TrustEvaluating + + var locator: Locator + var type: ServiceType + var sendResource: ProtobufSendResource + var receiveResource: EmptyReceiveResource + + func customError(for error: ServiceError, responseBody: Data?) -> KeySubmissionResourceError? { + switch error { + case .invalidRequestError: + return .requestCouldNotBeBuilt + case .unexpectedServerError(let statusCode): + switch statusCode { + case 400: + return .invalidPayloadOrHeaders + case 403: + return .invalidTan + default: + return .serverError(statusCode) + } + default: + return nil + } + } + +} diff --git a/src/xcode/ENA/ENA/Source/HTTPClient/Resources/OTPAuthorizationForELS/OTPAuthorizationForELSResource.swift b/src/xcode/ENA/ENA/Source/HTTPClient/Resources/OTPAuthorizationForELS/OTPAuthorizationForELSResource.swift index 35d5eaf2a37..902ccc7b2ae 100644 --- a/src/xcode/ENA/ENA/Source/HTTPClient/Resources/OTPAuthorizationForELS/OTPAuthorizationForELSResource.swift +++ b/src/xcode/ENA/ENA/Source/HTTPClient/Resources/OTPAuthorizationForELS/OTPAuthorizationForELSResource.swift @@ -4,7 +4,7 @@ import Foundation -enum OTPAuthorizationForELSError: LocalizedError, Equatable { +enum OTPAuthorizationError: LocalizedError, Equatable { case generalError(underlyingError: Error? = nil) case invalidResponseError @@ -52,7 +52,7 @@ enum OTPAuthorizationForELSError: LocalizedError, Equatable { } } - static func == (lhs: OTPAuthorizationForELSError, rhs: OTPAuthorizationForELSError) -> Bool { + static func == (lhs: OTPAuthorizationError, rhs: OTPAuthorizationError) -> Bool { return lhs.description == rhs.description } @@ -92,6 +92,10 @@ struct OTPAuthorizationForELSResource: Resource { // MARK: - Protocol Resource + typealias Send = ProtobufSendResource + typealias Receive = JSONReceiveResource + typealias CustomError = OTPAuthorizationError + let trustEvaluation: TrustEvaluating var locator: Locator @@ -100,9 +104,9 @@ struct OTPAuthorizationForELSResource: Resource { var receiveResource: JSONReceiveResource func customError( - for error: ServiceError, + for error: ServiceError, responseBody: Data? = nil - ) -> OTPAuthorizationForELSError? { + ) -> OTPAuthorizationError? { switch error { case .transportationError: return .noNetworkConnection @@ -125,7 +129,7 @@ struct OTPAuthorizationForELSResource: Resource { // MARK: - Private - private func otpAuthorizationFailureHandler(for response: Data?, statusCode: Int) -> OTPAuthorizationForELSError? { + private func otpAuthorizationFailureHandler(for response: Data?, statusCode: Int) -> OTPAuthorizationError? { guard let responseBody = response else { Log.error("Failed to get authorized OTP - no 200 status code", log: .api) Log.error(String(statusCode), log: .api) diff --git a/src/xcode/ENA/ENA/Source/HTTPClient/Resources/OTPAuthorizationForSRS/OTPAuthorizationForSRSResource.swift b/src/xcode/ENA/ENA/Source/HTTPClient/Resources/OTPAuthorizationForSRS/OTPAuthorizationForSRSResource.swift new file mode 100644 index 00000000000..f371692036a --- /dev/null +++ b/src/xcode/ENA/ENA/Source/HTTPClient/Resources/OTPAuthorizationForSRS/OTPAuthorizationForSRSResource.swift @@ -0,0 +1,115 @@ +// +// 🦠 Corona-Warn-App +// + +import Foundation + +struct OTPAuthorizationForSRSResource: Resource { + + // MARK: - Init + + init( + otpSRS: String, + ppacToken: PPACToken, + trustEvaluation: TrustEvaluating = DefaultTrustEvaluation( + publicKeyHash: Environments().currentEnvironment().pinningKeyHashData + ) + ) { + let ppacIos = SAP_Internal_Ppdd_PPACIOS.with { + $0.apiToken = ppacToken.apiToken + $0.deviceToken = ppacToken.deviceToken + } + let payload = SAP_Internal_Ppdd_SRSOneTimePassword.with { + $0.otp = otpSRS + } + self.sendResource = ProtobufSendResource( + SAP_Internal_Ppdd_SRSOneTimePasswordRequestIOS.with { + $0.payload = payload + $0.authentication = ppacIos + } + ) + + self.locator = .authorizeOtpSrs(isFake: false) + self.type = .default + self.receiveResource = JSONReceiveResource() + self.trustEvaluation = trustEvaluation + } + + // MARK: - Protocol Resource + + typealias Send = ProtobufSendResource + typealias Receive = JSONReceiveResource + typealias CustomError = OTPAuthorizationError + + let trustEvaluation: TrustEvaluating + + var locator: Locator + var type: ServiceType + var sendResource: ProtobufSendResource + var receiveResource: JSONReceiveResource + + func customError( + for error: ServiceError, + responseBody: Data? = nil + ) -> OTPAuthorizationError? { + switch error { + case .transportationError: + return .noNetworkConnection + case .unexpectedServerError(let statusCode): + switch statusCode { + case 400, 401, 403: + return otpAuthorizationFailureHandler(for: responseBody, statusCode: statusCode) + case 500: + Log.error("Failed to get authorized OTP - 500 status code", log: .api) + return .otherServerError + default: + Log.error("Failed to authorize OTP - response error: \(statusCode)", log: .api) + return .otherServerError + } + default: + return .otherServerError + } + } + + // MARK: - Private + + private func otpAuthorizationFailureHandler(for response: Data?, statusCode: Int) -> OTPAuthorizationError? { + guard let responseBody = response else { + Log.error("Failed to get authorized OTP - no 200 status code: \(statusCode)", log: .api) + return .otherServerError + } + + do { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + let decodedResponse = try decoder.decode( + OTPResponseProperties.self, + from: responseBody + ) + guard let errorCode = decodedResponse.errorCode else { + Log.error("Failed to get errorCode because it is nil", log: .api) + return .otherServerError + } + + switch errorCode { + case .API_TOKEN_ALREADY_ISSUED: + return .apiTokenAlreadyIssued + case .API_TOKEN_EXPIRED: + return .apiTokenExpired + case .API_TOKEN_QUOTA_EXCEEDED: + return .apiTokenQuotaExceeded + case .DEVICE_TOKEN_INVALID: + return .deviceTokenInvalid + case .DEVICE_TOKEN_REDEEMED: + return .deviceTokenRedeemed + case .DEVICE_TOKEN_SYNTAX_ERROR: + return .deviceTokenSyntaxError + default: + return .otherServerError + } + } catch { + Log.error("Failed to get errorCode because json could not be decoded", log: .api, error: error) + return .otherServerError + } + } +} diff --git a/src/xcode/ENA/ENA/Source/RootCoordinator.swift b/src/xcode/ENA/ENA/Source/RootCoordinator.swift index bbe8f8e3f14..ffb5c272679 100644 --- a/src/xcode/ENA/ENA/Source/RootCoordinator.swift +++ b/src/xcode/ENA/ENA/Source/RootCoordinator.swift @@ -34,6 +34,7 @@ class RootCoordinator: NSObject, RequiresAppDependencies, UITabBarControllerDele eventCheckoutService: EventCheckoutService, otpService: OTPServiceProviding, ppacService: PrivacyPreservingAccessControl, + srsService: SRSServiceProviding, cclService: CCLServable, healthCertificateService: HealthCertificateService, healthCertificateRequestService: HealthCertificateRequestService, @@ -54,6 +55,7 @@ class RootCoordinator: NSObject, RequiresAppDependencies, UITabBarControllerDele self.eventCheckoutService = eventCheckoutService self.otpService = otpService self.ppacService = ppacService + self.srsService = srsService self.cclService = cclService self.healthCertificateService = healthCertificateService self.healthCertificateRequestService = healthCertificateRequestService @@ -152,6 +154,7 @@ class RootCoordinator: NSObject, RequiresAppDependencies, UITabBarControllerDele eventStore: eventStore, appConfiguration: appConfigurationProvider, eventCheckoutService: eventCheckoutService, + srsService: srsService, healthCertificateService: healthCertificateService, healthCertificateValidationService: healthCertificateValidationService, healthCertificateValidationOnboardedCountriesProvider: healthCertificateValidationOnboardedCountriesProvider, @@ -182,6 +185,7 @@ class RootCoordinator: NSObject, RequiresAppDependencies, UITabBarControllerDele healthCertificateService: healthCertificateService, healthCertificateValidationService: healthCertificateValidationService, elsService: elsService, + srsService: srsService, exposureSubmissionService: exposureSubmissionService, qrScannerCoordinator: qrScannerCoordinator, recycleBin: recycleBin, @@ -400,6 +404,7 @@ class RootCoordinator: NSObject, RequiresAppDependencies, UITabBarControllerDele private let otpService: OTPServiceProviding private let ppacService: PrivacyPreservingAccessControl private let elsService: ErrorLogSubmissionProviding + private let srsService: SRSServiceProviding private let cclService: CCLServable private let healthCertificateService: HealthCertificateService private let healthCertificateRequestService: HealthCertificateRequestService diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureDetection/Survey/SurveyURLProvider.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureDetection/Survey/SurveyURLProvider.swift index e4748ac244e..2eed612e03e 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/ExposureDetection/Survey/SurveyURLProvider.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureDetection/Survey/SurveyURLProvider.swift @@ -12,6 +12,8 @@ enum SurveyError: Error { case deviceNotSupported(String) case changeDeviceTime(String) case alreadyParticipated(String) + case minimumTimeSinceOnboarding(String) + case submissionTooEarly(String) // MARK: - Init @@ -23,6 +25,10 @@ enum SurveyError: Error { self = .deviceNotSupported(ppacError.description) case .timeIncorrect: self = .changeDeviceTime(ppacError.description) + case .submissionTooEarly: + self = .submissionTooEarly(ppacError.description) + case .minimumTimeSinceOnboarding: + self = .minimumTimeSinceOnboarding(ppacError.description) } } @@ -51,6 +57,9 @@ enum SurveyError: Error { return String(format: AppStrings.SurveyConsent.errorChangeDeviceTime, errorCode) case .alreadyParticipated(let errorCode): return String(format: AppStrings.SurveyConsent.errorAlreadyParticipated, errorCode) + case .submissionTooEarly, .minimumTimeSinceOnboarding: + // these cases are unrelated to the survey + return "" } } } diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/ExposureSubmissionCoordinator.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/ExposureSubmissionCoordinator.swift index f1228370da3..07f33755b80 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/ExposureSubmissionCoordinator.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/ExposureSubmissionCoordinator.swift @@ -18,6 +18,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { parentViewController: UIViewController, exposureSubmissionService: ExposureSubmissionService, coronaTestService: CoronaTestServiceProviding, + srsService: SRSServiceProviding, familyMemberCoronaTestService: FamilyMemberCoronaTestServiceProviding, healthCertificateService: HealthCertificateService, healthCertificateValidationService: HealthCertificateValidationProviding, @@ -41,6 +42,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: exposureSubmissionService, coronaTestService: coronaTestService, + srsService: srsService, familyMemberCoronaTestService: familyMemberCoronaTestService, eventProvider: eventProvider, recycleBin: recycleBin @@ -49,8 +51,8 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { // MARK: - Internal - func start(with coronaTestType: CoronaTestType? = nil) { - model.coronaTestType = coronaTestType + func start(with submissionTestType: SubmissionTestType? = nil) { + model.submissionTestType = submissionTestType start(with: self.getInitialViewController()) } @@ -94,13 +96,13 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { } func showTestResultScreen(triggeredFromTeletan: Bool = false) { - let vc = createTestResultViewController(triggeredFromTeletan: triggeredFromTeletan) - push(vc) + let testResultViewController = createTestResultViewController(triggeredFromTeletan: triggeredFromTeletan) + push(testResultViewController) // If a TAN was entered, we skip `showTestResultAvailableScreen(with:)`, so we notify (again) about the new state QuickAction.exposureSubmissionFlowTestResult = model.coronaTest?.testResult } - + func showTanScreen() { let tanInputViewModel = TanInputViewModel( title: AppStrings.ExposureSubmissionTanEntry.title, @@ -114,7 +116,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { } ) - let vc = TanInputViewController( + let tanInputViewController = TanInputViewController( viewModel: tanInputViewModel, dismiss: { [weak self] in self?.dismiss() } ) @@ -127,7 +129,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { ) let footerViewController = FooterViewController(footerViewModel) - let topBottomViewController = TopBottomContainerViewController(topController: vc, bottomController: footerViewController) + let topBottomViewController = TopBottomContainerViewController(topController: tanInputViewController, bottomController: footerViewController) push(topBottomViewController) } @@ -154,14 +156,18 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { // By default, we show the intro view. let viewModel = ExposureSubmissionIntroViewModel( + onPositiveSelfTestButtonTap: { [weak self] in + self?.showSRSConsentScreen(srsFlowType: .srsPositive) + }, + onSelfReportSubmissionButtonTap: { [weak self] in + self?.showSRSConsentScreen(srsFlowType: .positiveWithoutResultInTheApp) + }, onQRCodeButtonTap: { [weak self] isLoading in self?.showQRScreen(testRegistrationInformation: nil, isLoading: isLoading) }, onFindTestCentersTap: { LinkHelper.open(urlString: AppStrings.Links.findTestCentersFAQ) }, - onTANButtonTap: { [weak self] in self?.showTanScreen() }, - onHotlineButtonTap: { [weak self] in self?.showHotlineScreen() }, onRapidTestProfileTap: { [weak self] in guard let antigenTestProfileInfoScreenShown = self?.store.antigenTestProfileInfoScreenShown, antigenTestProfileInfoScreenShown else { self?.showAntigenTestProfileInformation() @@ -198,10 +204,16 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { private var certificateCoordinator: HealthCertificateCoordinator? private var antigenTestProfileOverviewViewController: AntigenTestProfileOverviewViewController? - - private func push(_ vc: UIViewController) { + + private func push(_ viewController: UIViewController) { + navigationController?.topViewController?.view.endEditing(true) + navigationController?.pushViewController(viewController, animated: true) + } + + private func present(_ viewController: UIViewController, withNavigation: Bool = true) { + let navigationControllerWithLargeTitle = NavigationControllerWithLargeTitle(rootViewController: viewController) navigationController?.topViewController?.view.endEditing(true) - navigationController?.pushViewController(vc, animated: true) + navigationController?.present(navigationControllerWithLargeTitle, animated: true) } private func popViewController() { @@ -308,6 +320,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { if coronaTest.testResult == .positive && !coronaTest.keysSubmitted { QuickAction.exposureSubmissionFlowTestResult = coronaTest.testResult } + Analytics.collect(.keySubmissionMetadata(.lastSubmissionFlowScreen(.submissionFlowScreenTestResult, coronaTestType))) let testResultAvailability: TestResultAvailability = model.coronaTest?.testResult == .positive ? .availableAndPositive : .notAvailable @@ -350,7 +363,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { } ) - let vc = ExposureSubmissionTestResultViewController( + let exposureSubmissionTestResultViewController = ExposureSubmissionTestResultViewController( viewModel: viewModel, onDismiss: { [weak self] testResult, isLoading in if testResult == TestResult.positive && !coronaTest.keysSubmitted { @@ -366,7 +379,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { ) let topBottomContainerViewController = TopBottomContainerViewController( - topController: vc, + topController: exposureSubmissionTestResultViewController, bottomController: footerViewController ) @@ -374,11 +387,11 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { } private func createWarnOthersViewController(supportedCountries: [Country]) -> UIViewController { - if let testType = model.coronaTestType { - Analytics.collect(.keySubmissionMetadata(.lastSubmissionFlowScreen(.submissionFlowScreenWarnOthers, testType))) + if let coronaTestType = model.coronaTestType { + Analytics.collect(.keySubmissionMetadata(.lastSubmissionFlowScreen(.submissionFlowScreenWarnOthers, coronaTestType))) } - let vc = ExposureSubmissionWarnOthersViewController( + let exposureSubmissionWarnOthersViewController = ExposureSubmissionWarnOthersViewController( viewModel: ExposureSubmissionWarnOthersViewModel( supportedCountries: supportedCountries ) { [weak self] in @@ -414,7 +427,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { ) let topBottomContainerViewController = TopBottomContainerViewController( - topController: vc, + topController: exposureSubmissionWarnOthersViewController, bottomController: footerViewController ) @@ -452,18 +465,64 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { // MARK: Screen Flow private func showHotlineScreen() { - let vc = ExposureSubmissionHotlineViewController( + let exposureSubmissionHotlineViewController = ExposureSubmissionHotlineViewController( onPrimaryButtonTap: { [weak self] in self?.showTanScreen() }, dismiss: { [weak self] in self?.dismiss() } ) - push(vc) + push(exposureSubmissionHotlineViewController) } - + + private func makeSRSConsentScreen(srsFlowType: SRSFlowType) -> UIViewController { + let srsConsentViewController = SRSConsentViewController( + onPrimaryButtonTap: { [weak self] isLoading in + guard let self = self else { return } + + isLoading(true) + self.model.exposureSubmissionService.getTemporaryExposureKeys { error in + isLoading(false) + + guard let error = error else { + switch srsFlowType { + case .srsPositive: + self.model.storeSelectedSRSSubmissionType(.srsSelfTest) + self.showSRSFlowNextScreen() + case .positiveWithoutResultInTheApp: + self.showSRSTestTypeSelectionScreen() + } + return + } + + // User selected "Don't Share" / "Nicht teilen" + if error == .notAuthorized { + Log.info("OS submission authorization was declined.") + } else { + self.showErrorAlert(for: error) + } + } + }, + dismiss: { [weak self] in self?.dismiss() } + ) + + let footerViewModel = FooterViewModel( + primaryButtonName: AppStrings.ExposureSubmissionTestResultAvailable.primaryButtonTitle, + primaryIdentifier: AccessibilityIdentifiers.ExposureSubmissionTestResultAvailable.primaryButton, + isSecondaryButtonEnabled: false, + isSecondaryButtonHidden: true + ) + + let topBottomContainerViewController = TopBottomContainerViewController( + topController: srsConsentViewController, + bottomController: FooterViewController(footerViewModel) + ) + + return topBottomContainerViewController + } + private func makeQRInfoScreen(supportedCountries: [Country], testRegistrationInformation: CoronaTestRegistrationInformation) -> UIViewController { - let vc = ExposureSubmissionQRInfoViewController( + let exposureSubmissionQRInfoViewController = ExposureSubmissionQRInfoViewController( supportedCountries: supportedCountries, onPrimaryButtonTap: { [weak self] isLoading in if #available(iOS 14.4, *) { @@ -526,7 +585,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { ) let topBottomContainerViewController = TopBottomContainerViewController( - topController: vc, + topController: exposureSubmissionQRInfoViewController, bottomController: footerViewController ) @@ -537,6 +596,10 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { push(makeQRInfoScreen(supportedCountries: supportedCountries, testRegistrationInformation: testRegistrationInformation)) } + private func showSRSConsentScreen(srsFlowType: SRSFlowType) { + push(makeSRSConsentScreen(srsFlowType: srsFlowType)) + } + private func showFamilyMemberTestConsentScreen( testRegistrationInformation: CoronaTestRegistrationInformation, temporaryAntigenTestProfileName: String? = nil @@ -597,7 +660,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { } ) - let vc = ExposureSubmissionTestResultViewController( + let exposureSubmissionTestResultViewController = ExposureSubmissionTestResultViewController( viewModel: viewModel, onDismiss: { [weak self] _, _ in self?.dismiss() @@ -609,7 +672,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { ) return TopBottomContainerViewController( - topController: vc, + topController: exposureSubmissionTestResultViewController, bottomController: footerViewController ) } @@ -730,8 +793,8 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { } private func showTestResultAvailableScreen() { - let vc = createTestResultAvailableViewController() - push(vc) + let testResultAvailableViewController = createTestResultAvailableViewController() + push(testResultAvailableViewController) // used for updating (hiding) app shortcuts QuickAction.exposureSubmissionFlowTestResult = model.coronaTest?.testResult @@ -746,7 +809,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { let dismissCompletion: (() -> Void)? = testResultAvailability == .notAvailable ? nil : { [weak self] in self?.showTestResultAvailableCloseAlert() } - let vc = ExposureSubmissionTestResultConsentViewController( + let exposureSubmissionTestResultConsentViewController = ExposureSubmissionTestResultConsentViewController( viewModel: ExposureSubmissionTestResultConsentViewModel( supportedCountries: supportedCountries, coronaTestType: coronaTestType, @@ -756,10 +819,10 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { ) ) - push(vc) + push(exposureSubmissionTestResultConsentViewController ) } - private func showCheckinsScreen() { + private func showCheckinsScreen(isSRSFlow: Bool = false) { let showNextScreen = { [weak self] in if self?.model.coronaTest?.positiveTestResultWasShown == true { self?.showThankYouScreen() @@ -768,7 +831,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { } } - guard model.eventProvider.checkinsPublisher.value.contains(where: { $0.checkinCompleted }) else { + guard model.eventProvider.checkinsPublisher.value.contains(where: { $0.checkinCompleted }) || isSRSFlow else { showNextScreen() return } @@ -787,7 +850,12 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { checkins: model.eventProvider.checkinsPublisher.value, onCompletion: { [weak self] selectedCheckins in self?.model.exposureSubmissionService.checkins = selectedCheckins - showNextScreen() + + if isSRSFlow { + self?.showSymptomsScreen() + } else { + showNextScreen() + } }, onSkip: { [weak self] in self?.showSkipCheckinsAlert(dontShareHandler: { @@ -797,12 +865,12 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { onDismiss: { [weak self] in if self?.model.coronaTest?.positiveTestResultWasShown == true { self?.showSkipCheckinsAlert(dontShareHandler: { - if let testType = self?.model.coronaTestType { - Analytics.collect(.keySubmissionMetadata(.submittedAfterCancel(true, testType))) - } - self?.submitExposure(showSubmissionSuccess: false) { isLoading in - footerViewModel.setLoadingIndicator(isLoading, disable: isLoading, button: .secondary) - footerViewModel.setLoadingIndicator(false, disable: isLoading, button: .primary) + if let coronaTestType = self?.model.coronaTestType { + Analytics.collect(.keySubmissionMetadata(.submittedAfterCancel(true, coronaTestType))) + self?.submitExposure(showSubmissionSuccess: false) { isLoading in + footerViewModel.setLoadingIndicator(isLoading, disable: isLoading, button: .secondary) + footerViewModel.setLoadingIndicator(false, disable: isLoading, button: .primary) + } } }) } else { @@ -845,8 +913,8 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { // MARK: Late consent private func showWarnOthersScreen(supportedCountries: [Country]) { - let vc = createWarnOthersViewController(supportedCountries: supportedCountries) - push(vc) + let warnOthersViewController = createWarnOthersViewController(supportedCountries: supportedCountries) + push(warnOthersViewController) } private func showThankYouScreen() { @@ -878,23 +946,73 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { push(topBottomContainerViewController) } + // MARK: - Test Type Selection + + /// Shows the Type of Tests screen + /// - Parameter isSelfTestTypePreselected: If the process was started via self-report with self-test on the Manage Your Tests View, the Self-test entry shall be pre-selected. Otherwise, no entry shall be selected. + private func showSRSTestTypeSelectionScreen(isSelfTestTypePreselected: Bool = false) { + let srsTestTypeSelectionViewController = SRSTestTypeSelectionViewController( + viewModel: SRSTestTypeSelectionViewModel(isSelfTestTypePreselected: isSelfTestTypePreselected), + onPrimaryButtonTap: { [weak self] submissionType in + self?.model.storeSelectedSRSSubmissionType(submissionType) + self?.showSRSFlowNextScreen() + }, onDismiss: { [weak self] in + self?.parentViewController?.dismiss(animated: true) + } + ) + + let footerViewController = FooterViewController( + FooterViewModel( + primaryButtonName: AppStrings.ExposureSubmission.SRSTestTypeSelection.primaryButtonTitle, + isSecondaryButtonEnabled: false, + isSecondaryButtonHidden: true + ) + ) + + let topBottomContainerViewController = TopBottomContainerViewController( + topController: srsTestTypeSelectionViewController, + bottomController: footerViewController + ) + + push(topBottomContainerViewController) + } + + private func showSRSFlowNextScreen() { + if model.eventProvider.checkinsPublisher.value.isEmpty { + showSymptomsScreen() + } else { + showCheckinsScreen(isSRSFlow: true) + } + } + // MARK: Symptoms private func showSymptomsScreen() { - if let testType = model.coronaTestType { - Analytics.collect(.keySubmissionMetadata(.lastSubmissionFlowScreen(.submissionFlowScreenSymptoms, testType))) + if let coronaTestType = model.coronaTestType { + Analytics.collect(.keySubmissionMetadata(.lastSubmissionFlowScreen(.submissionFlowScreenSymptoms, coronaTestType))) } - let vc = ExposureSubmissionSymptomsViewController( + let exposureSubmissionSymptomsViewController = ExposureSubmissionSymptomsViewController( onPrimaryButtonTap: { [weak self] selectedSymptomsOption, isLoading in guard let self = self else { return } self.model.symptomsOptionSelected(selectedSymptomsOption) - // we don't need to set it true if yes is selected - if selectedSymptomsOption != .yes, let testType = self.model.coronaTestType { - Analytics.collect(.keySubmissionMetadata(.submittedAfterSymptomFlow(true, testType))) + + switch self.model.submissionTestType { + case .registeredTest(let coronaTestType): + // we don't need to set it true if yes is selected + if selectedSymptomsOption != .yes, let coronaTestType = coronaTestType { + Analytics.collect(.keySubmissionMetadata(.submittedAfterSymptomFlow(true, coronaTestType))) + } + + self.model.shouldShowSymptomsOnsetScreen ? self.showSymptomsOnsetScreen() : self.submitExposure(showSubmissionSuccess: true, isLoading: isLoading) + case .srs: + if self.model.shouldShowSymptomsOnsetScreen { + self.showSymptomsOnsetScreen() + } + case .none: + break } - self.model.shouldShowSymptomsOnsetScreen ? self.showSymptomsOnsetScreen() : self.submitExposure(showSubmissionSuccess: true, isLoading: isLoading) }, onDismiss: { [weak self] isLoading in self?.showSubmissionSymptomsCancelAlert(isLoading: isLoading) @@ -911,7 +1029,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { ) let topBottomContainerViewController = TopBottomContainerViewController( - topController: vc, + topController: exposureSubmissionSymptomsViewController, bottomController: footerViewController ) @@ -919,19 +1037,27 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { } private func showSymptomsOnsetScreen() { - if let testType = self.model.coronaTestType { - Analytics.collect(.keySubmissionMetadata(.lastSubmissionFlowScreen(.submissionFlowScreenSymptomOnset, testType))) + if let coronaTestType = model.coronaTestType { + Analytics.collect(.keySubmissionMetadata(.lastSubmissionFlowScreen(.submissionFlowScreenSymptomOnset, coronaTestType))) } - let vc = ExposureSubmissionSymptomsOnsetViewController( + let exposureSubmissionSymptomsOnsetViewController = ExposureSubmissionSymptomsOnsetViewController( onPrimaryButtonTap: { [weak self] selectedSymptomsOnsetOption, isLoading in self?.model.symptomsOnsetOptionSelected(selectedSymptomsOnsetOption) - if let testType = self?.model.coronaTestType { - // setting it to true regardless of the options selected - Analytics.collect(.keySubmissionMetadata(.submittedAfterSymptomFlow(true, testType))) + switch self?.model.submissionTestType { + case .registeredTest(let coronaTestType): + if let coronaTestType = coronaTestType { + // setting it to true regardless of the options selected + Analytics.collect(.keySubmissionMetadata(.submittedAfterSymptomFlow(true, coronaTestType))) + } + + self?.submitExposure(showSubmissionSuccess: true, isLoading: isLoading) + case .srs: + self?.submitSRSExposure(showSubmissionSuccess: true, isLoading: isLoading) + case .none: + break } - self?.submitExposure(showSubmissionSuccess: true, isLoading: isLoading) }, onDismiss: { [weak self] isLoading in self?.showSubmissionSymptomsCancelAlert(isLoading: isLoading) @@ -950,7 +1076,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { ) let topBottomContainerViewController = TopBottomContainerViewController( - topController: vc, + topController: exposureSubmissionSymptomsOnsetViewController, bottomController: footerViewController ) @@ -1065,7 +1191,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { store: store ), didTapContinue: { [weak self] isLoading, antigenTestProfile in - self?.model.coronaTestType = .antigen + self?.model.submissionTestType = .registeredTest(.antigen) self?.showQRScreen( testRegistrationInformation: nil, temporaryAntigenTestProfileName: antigenTestProfile.fullName, @@ -1347,10 +1473,10 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { title: cancelAlertButtonTitle, style: .cancel, handler: { [weak self] _ in - if let testType = self?.model.coronaTestType { - Analytics.collect(.keySubmissionMetadata(.submittedAfterCancel(true, testType))) + if let coronaTestType = self?.model.coronaTestType { + Analytics.collect(.keySubmissionMetadata(.submittedAfterCancel(true, coronaTestType))) + self?.submitExposure(showSubmissionSuccess: false, isLoading: isLoading) } - self?.submitExposure(showSubmissionSuccess: false, isLoading: isLoading) } ) ) @@ -1377,10 +1503,10 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { title: AppStrings.ExposureSubmissionSymptomsCancelAlert.cancelButton, style: .cancel, handler: { [weak self] _ in - if let testType = self?.model.coronaTestType { - Analytics.collect(.keySubmissionMetadata(.submittedAfterCancel(true, testType))) + if let coronaTestType = self?.model.coronaTestType { + Analytics.collect(.keySubmissionMetadata(.submittedAfterCancel(true, coronaTestType))) + self?.submitExposure(showSubmissionSuccess: false, isLoading: isLoading) } - self?.submitExposure(showSubmissionSuccess: false, isLoading: isLoading) } ) ) @@ -1505,7 +1631,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { certificateConsent: certificateConsent, isLoading: isLoading, onSuccess: { [weak self] testResult in - self?.model.coronaTestType = testQRCodeInformation.testType + self?.model.submissionTestType = .registeredTest(testQRCodeInformation.testType) switch testQRCodeInformation { case .teleTAN: @@ -1561,6 +1687,8 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { self.model.submitExposure( isLoading: isLoading, onSuccess: { [weak self] in + self?.store.mostRecentKeySubmissionDate = Date() + if showSubmissionSuccess { self?.showExposureSubmissionSuccessViewController() } else { @@ -1568,12 +1696,30 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { } }, onError: { [weak self] error in - if let testType = self?.model.coronaTestType { + if let coronaTestType = self?.model.coronaTestType { // reset all the values taken during the submission flow because submission failed - Analytics.collect(.keySubmissionMetadata(.submittedAfterSymptomFlow(false, testType))) - Analytics.collect(.keySubmissionMetadata(.submittedAfterCancel(false, testType))) - Analytics.collect(.keySubmissionMetadata(.lastSubmissionFlowScreen(.submissionFlowScreenUnknown, testType))) + Analytics.collect(.keySubmissionMetadata(.submittedAfterSymptomFlow(false, coronaTestType))) + Analytics.collect(.keySubmissionMetadata(.submittedAfterCancel(false, coronaTestType))) + Analytics.collect(.keySubmissionMetadata(.lastSubmissionFlowScreen(.submissionFlowScreenUnknown, coronaTestType))) + } + self?.showServiceErrorAlert(for: error) { + self?.dismiss() } + } + ) + } + + private func submitSRSExposure(showSubmissionSuccess: Bool = false, isLoading: @escaping (Bool) -> Void) { + self.model.submitSRSExposure( + isLoading: isLoading, + onSuccess: { [weak self] in + if showSubmissionSuccess { + self?.showExposureSubmissionSuccessViewController() + } else { + self?.dismiss() + } + }, + onError: { [weak self] error in self?.showServiceErrorAlert(for: error) { self?.dismiss() } @@ -1582,14 +1728,14 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { } private func showExposureSubmissionSuccessViewController() { - guard let coronaTestType = model.coronaTestType else { - Log.error("No corona test type set to show the success view controller for, dismissing to be safe", log: .ui) + guard let submissionTestType = model.submissionTestType else { + Log.error("No submission test type set to show the success view controller for, dismissing to be safe", log: .ui) dismiss() return } let exposureSubmissionSuccessViewController = ExposureSubmissionSuccessViewController( - coronaTestType: coronaTestType, + submissionTestType: submissionTestType, dismiss: { [weak self] in self?.dismiss() } @@ -1606,7 +1752,7 @@ class ExposureSubmissionCoordinator: NSObject, RequiresAppDependencies { Log.info("restoreAndShowTest only restores tests") return case .userCoronaTest(let coronaTest): - start(with: coronaTest.type) + start(with: .registeredTest(coronaTest.type)) case .familyMemberCoronaTest(let coronaTest): let familyMemberTestResultViewController = createTestResultScreen(for: coronaTest) start(with: familyMemberTestResultViewController) diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/ExposureSubmissionCoordinatorModel.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/ExposureSubmissionCoordinatorModel.swift index 2a925706402..283f0b1b587 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/ExposureSubmissionCoordinatorModel.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/ExposureSubmissionCoordinatorModel.swift @@ -13,6 +13,7 @@ class ExposureSubmissionCoordinatorModel { init( exposureSubmissionService: ExposureSubmissionService, coronaTestService: CoronaTestServiceProviding, + srsService: SRSServiceProviding, familyMemberCoronaTestService: FamilyMemberCoronaTestServiceProviding, eventProvider: EventProviding, recycleBin: RecycleBin @@ -22,7 +23,7 @@ class ExposureSubmissionCoordinatorModel { self.coronaTestService = coronaTestService self.eventProvider = eventProvider self.recycleBin = recycleBin - + self.srsService = srsService // Try to load current country list initially to make it virtually impossible the user has to wait for it later. exposureSubmissionService.loadSupportedCountries { _ in // no op @@ -38,18 +39,28 @@ class ExposureSubmissionCoordinatorModel { let familyMemberCoronaTestService: FamilyMemberCoronaTestServiceProviding let eventProvider: EventProviding let recycleBin: RecycleBin + let srsService: SRSServiceProviding - var coronaTestType: CoronaTestType? + var submissionTestType: SubmissionTestType? var markNewlyAddedCoronaTestAsUnseen: Bool = false + var shouldShowSymptomsOnsetScreen = false var coronaTest: UserCoronaTest? { - guard let coronaTestType = coronaTestType else { + guard case let .registeredTest(coronaTestType) = submissionTestType, let coronaTestType = coronaTestType else { return nil } return coronaTestService.coronaTest(ofType: coronaTestType) } + var coronaTestType: CoronaTestType? { + guard case let .registeredTest(coronaTestType) = self.submissionTestType else { + return nil + } + + return coronaTestType + } + func shouldShowOverrideTestNotice(for coronaTestType: CoronaTestType) -> Bool { if let oldTest = coronaTestService.coronaTest(ofType: coronaTestType), oldTest.testResult != .expired, @@ -73,7 +84,11 @@ class ExposureSubmissionCoordinatorModel { } } - var shouldShowSymptomsOnsetScreen = false + /// Handle the storing of the selected submission type (only SRS types!). + /// - Parameter _ type: The selected submission type (SRS) + func storeSelectedSRSSubmissionType(_ type: SRSSubmissionType) { + submissionTestType = .srs(type) + } func symptomsOptionSelected( _ selectedSymptomsOption: ExposureSubmissionSymptomsViewController.SymptomsOption @@ -138,7 +153,7 @@ class ExposureSubmissionCoordinatorModel { onSuccess: @escaping () -> Void, onError: @escaping (ExposureSubmissionServiceError) -> Void ) { - guard let coronaTestType = coronaTestType else { + guard case let .registeredTest(coronaTestType) = submissionTestType, let coronaTestType = coronaTestType else { onError(.preconditionError(.noCoronaTestTypeGiven)) return } @@ -166,6 +181,49 @@ class ExposureSubmissionCoordinatorModel { } } + func submitSRSExposure( + isLoading: @escaping (Bool) -> Void, + onSuccess: @escaping () -> Void, + onError: @escaping (ExposureSubmissionServiceError) -> Void + ) { + guard case let .srs(srsSubmissionType) = submissionTestType else { + return + } + isLoading(true) + srsService.authenticate(completion: { [weak self] result in + guard let self = self else { return } + isLoading(false) + switch result { + case .success(let srsOTP): + self.exposureSubmissionService.submitSRSExposure( + submissionType: srsSubmissionType, + srsOTP: srsOTP + ) { error in + + switch error { + + // We continue the regular flow even if there are no keys collected. + case .none, .preconditionError(.noKeysCollected): + onSuccess() + + // We don't show an error if the submission consent was not given, because we assume that the submission already happened in the background. + case .preconditionError(.noSubmissionConsent): + Log.info("Consent Not Given", log: .ui) + onSuccess() + + case .some(let error): + Log.error("error: \(error.localizedDescription)", log: .api) + onError(error) + } + } + case .failure(let srsError): + onError(.srsError(srsError)) + Log.debug(srsError.description, log: .ppac) + } + + }) + } + // swiftlint:disable cyclomatic_complexity func registerTestAndGetResult( for registrationInformation: CoronaTestRegistrationInformation, @@ -335,11 +393,19 @@ class ExposureSubmissionCoordinatorModel { } func setSubmissionConsentGiven(_ isSubmissionConsentGiven: Bool) { - switch coronaTestType { - case .pcr: - coronaTestService.pcrTest.value?.isSubmissionConsentGiven = isSubmissionConsentGiven - case .antigen: - coronaTestService.antigenTest.value?.isSubmissionConsentGiven = isSubmissionConsentGiven + switch submissionTestType { + case .registeredTest(let coronaTestType): + switch coronaTestType { + case .pcr: + coronaTestService.pcrTest.value?.isSubmissionConsentGiven = isSubmissionConsentGiven + case .antigen: + coronaTestService.antigenTest.value?.isSubmissionConsentGiven = isSubmissionConsentGiven + case .none: + fatalError("Cannot set submission consent, no corona test type is set") + } + case .srs: + // we don't store the consent in case of SRS + break case .none: fatalError("Cannot set submission consent, no corona test type is set") } diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionIntroViewModel.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionIntroViewModel.swift index 10c1960b256..76329437904 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionIntroViewModel.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionIntroViewModel.swift @@ -12,17 +12,17 @@ class ExposureSubmissionIntroViewModel { // MARK: - Init init( + onPositiveSelfTestButtonTap: @escaping () -> Void, + onSelfReportSubmissionButtonTap: @escaping () -> Void, onQRCodeButtonTap: @escaping (@escaping (Bool) -> Void) -> Void, onFindTestCentersTap: @escaping () -> Void, - onTANButtonTap: @escaping () -> Void, - onHotlineButtonTap: @escaping () -> Void, onRapidTestProfileTap: @escaping () -> Void, antigenTestProfileStore: AntigenTestProfileStoring ) { + self.onPositiveSelfTestButtonTap = onPositiveSelfTestButtonTap + self.onSelfReportSubmissionButtonTap = onSelfReportSubmissionButtonTap self.onQRCodeButtonTap = onQRCodeButtonTap self.onFindTestCentersTap = onFindTestCentersTap - self.onTANButtonTap = onTANButtonTap - self.onHotlineButtonTap = onHotlineButtonTap self.onRapidTestProfileTap = onRapidTestProfileTap self.antigenTestProfileStore = antigenTestProfileStore } @@ -31,8 +31,8 @@ class ExposureSubmissionIntroViewModel { let onQRCodeButtonTap: (@escaping (Bool) -> Void) -> Void let onFindTestCentersTap: () -> Void - let onTANButtonTap: () -> Void - let onHotlineButtonTap: () -> Void + let onPositiveSelfTestButtonTap: () -> Void + let onSelfReportSubmissionButtonTap: () -> Void let onRapidTestProfileTap: () -> Void let antigenTestProfileStore: AntigenTestProfileStoring @@ -69,6 +69,24 @@ class ExposureSubmissionIntroViewModel { cells: [] )) $0.add(.section(cells: [ + .imageCard( + title: AppStrings.ExposureSubmissionDispatch.positiveSelfTestButtonTitle, + description: AppStrings.ExposureSubmissionDispatch.postiveSelfTestButtonDescription, + image: UIImage(named: "Illu_Submission_PositiveSelfTest"), + action: .execute { [weak self] _, _ in self?.onPositiveSelfTestButtonTap() }, + accessibilityIdentifier: AccessibilityIdentifiers.ExposureSubmissionDispatch.positiveSelfTestButtonDescription + ), + .imageCard( + title: AppStrings.ExposureSubmissionDispatch.SRSButtonTitle, + description: AppStrings.ExposureSubmissionDispatch.SRSButtonDescription, + image: UIImage(named: "Illu_Submission_SRS"), + action: .execute { [weak self] _, _ in self?.onSelfReportSubmissionButtonTap() }, + accessibilityIdentifier: AccessibilityIdentifiers.ExposureSubmissionDispatch.SRSButtonDescription + ), + .title2( + text: AppStrings.ExposureSubmissionDispatch.sectionHeadline2, + accessibilityIdentifier: AccessibilityIdentifiers.ExposureSubmissionDispatch.sectionHeadline2 + ), .imageCard( title: AppStrings.ExposureSubmissionDispatch.qrCodeButtonTitle, description: AppStrings.ExposureSubmissionDispatch.qrCodeButtonDescription, @@ -88,25 +106,7 @@ class ExposureSubmissionIntroViewModel { action: .execute { [weak self] _, _ in self?.onFindTestCentersTap() }, accessibilityIdentifier: AccessibilityIdentifiers.ExposureSubmissionDispatch.findTestCentersButtonDescription ), - profileCell, - .title2( - text: AppStrings.ExposureSubmissionDispatch.sectionHeadline2, - accessibilityIdentifier: AccessibilityIdentifiers.ExposureSubmissionDispatch.sectionHeadline2 - ), - .imageCard( - title: AppStrings.ExposureSubmissionDispatch.tanButtonTitle, - description: AppStrings.ExposureSubmissionDispatch.tanButtonDescription, - image: UIImage(named: "Illu_Submission_TAN"), - action: .execute { [weak self] _, _ in self?.onTANButtonTap() }, - accessibilityIdentifier: AccessibilityIdentifiers.ExposureSubmissionDispatch.tanButtonDescription - ), - .imageCard( - title: AppStrings.ExposureSubmissionDispatch.hotlineButtonTitle, - description: AppStrings.ExposureSubmissionDispatch.hotlineButtonDescription, - image: UIImage(named: "Illu_Submission_Anruf"), - action: .execute { [weak self] _, _ in self?.onHotlineButtonTap() }, - accessibilityIdentifier: AccessibilityIdentifiers.ExposureSubmissionDispatch.hotlineButtonDescription - ) + profileCell ])) } } diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionSuccessViewController.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionSuccessViewController.swift index a1cd3aa96a3..ab9831a8ab0 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionSuccessViewController.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionSuccessViewController.swift @@ -11,10 +11,10 @@ class ExposureSubmissionSuccessViewController: DynamicTableViewController, ENANa // MARK: - Init init( - coronaTestType: CoronaTestType, + submissionTestType: SubmissionTestType, dismiss: @escaping () -> Void ) { - self.coronaTestType = coronaTestType + self.submissionTestType = submissionTestType self.dismiss = dismiss super.init(nibName: nil, bundle: nil) @@ -60,13 +60,9 @@ class ExposureSubmissionSuccessViewController: DynamicTableViewController, ENANa } - // MARK: - Public - - // MARK: - Internal - // MARK: - Private - private let coronaTestType: CoronaTestType + private let submissionTestType: SubmissionTestType private let dismiss: () -> Void private lazy var navigationFooterItem: ENANavigationFooterItem = { @@ -80,11 +76,12 @@ class ExposureSubmissionSuccessViewController: DynamicTableViewController, ENANa return item }() + // swiftlint:disable:next function_body_length private func setupTableView() { tableView.separatorStyle = .none - + tableView.register(ExposureSubmissionStepCell.self, forCellReuseIdentifier: CustomCellReuseIdentifiers.stepCell.rawValue) - + var cells: [DynamicCell] = [ .body( text: AppStrings.ExposureSubmissionSuccess.description, @@ -95,13 +92,95 @@ class ExposureSubmissionSuccessViewController: DynamicTableViewController, ENANa accessibilityIdentifier: AccessibilityIdentifiers.ExposureSubmissionSuccess.listTitle ) ] - - if coronaTestType == .pcr { + + switch submissionTestType { + case .registeredTest(let coronaTestType): + switch coronaTestType { + case .pcr: + cells.append(contentsOf: [ + ExposureSubmissionDynamicCell.stepCell( + style: .body, + title: AppStrings.ExposureSubmissionSuccess.listItemPCR0, + accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.PCR.firstBulletPoint, + icon: UIImage(named: "Icons - Home"), + iconTint: .enaColor(for: .riskHigh), + hairline: .none, + bottomSpacing: .medium + ), + ExposureSubmissionDynamicCell.stepCell( + style: .body, + title: AppStrings.ExposureSubmissionSuccess.listItemPCR1, + accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.PCR.secondBulletPoint, + icon: UIImage(named: "Icons - ContactJournal"), + hairline: .none, + bottomSpacing: .medium + ), + ExposureSubmissionDynamicCell.stepCell( + style: .body, + title: AppStrings.ExposureSubmissionSuccess.listItemPCR2, + accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.PCR.thirdBulletPoint, + icon: UIImage(named: "Icons - Hotline"), + iconTint: .enaColor(for: .riskHigh), + hairline: .none, + bottomSpacing: .medium + ), + ExposureSubmissionDynamicCell.stepCell( + style: .body, + title: AppStrings.ExposureSubmissionSuccess.listItemPCR3, + accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.PCR.fourthBulletPoint, + icon: UIImage(named: "Icons - Red Plus"), + hairline: .none, + bottomSpacing: .medium + ) + ]) + case .antigen: + cells.append(contentsOf: [ + ExposureSubmissionDynamicCell.stepCell( + style: .body, + title: AppStrings.ExposureSubmissionSuccess.listItemRAT0, + accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.RAT.firstBulletPoint, + icon: UIImage(named: "Icons - Home"), + iconTint: .enaColor(for: .riskHigh), + hairline: .none, + bottomSpacing: .medium + ), + ExposureSubmissionDynamicCell.stepCell( + style: .body, + title: AppStrings.ExposureSubmissionSuccess.listItemRAT1, + accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.RAT.secondBulletPoint, + icon: UIImage(named: "Icons - ContactJournal"), + iconTint: .enaColor(for: .riskHigh), + hairline: .none, + bottomSpacing: .medium + ), + ExposureSubmissionDynamicCell.stepCell( + style: .body, + title: AppStrings.ExposureSubmissionSuccess.listItemRAT2, + accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.RAT.thirdBulletPoint, + icon: UIImage(named: "Icons - Test Tube"), + iconTint: .enaColor(for: .riskHigh), + hairline: .none, + bottomSpacing: .medium + ), + ExposureSubmissionDynamicCell.stepCell( + style: .body, + title: AppStrings.ExposureSubmissionSuccess.listItemRAT3, + accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.RAT.fourthBulletPoint, + icon: UIImage(named: "Icons - Hotline"), + iconTint: .enaColor(for: .riskHigh), + hairline: .none, + bottomSpacing: .medium + ) + ]) + case .none: + break + } + case .srs: cells.append(contentsOf: [ ExposureSubmissionDynamicCell.stepCell( style: .body, - title: AppStrings.ExposureSubmissionSuccess.listItemPCR0, - accessibilityIdentifier: nil, + title: AppStrings.ExposureSubmissionSuccess.listItemSRS0, + accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.SRS.firstBulletPoint, icon: UIImage(named: "Icons - Home"), iconTint: .enaColor(for: .riskHigh), hairline: .none, @@ -109,37 +188,17 @@ class ExposureSubmissionSuccessViewController: DynamicTableViewController, ENANa ), ExposureSubmissionDynamicCell.stepCell( style: .body, - title: AppStrings.ExposureSubmissionSuccess.listItemPCR1, - accessibilityIdentifier: nil, - icon: UIImage(named: "Icons - Hotline"), + title: AppStrings.ExposureSubmissionSuccess.listItemSRS1, + accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.SRS.secondBulletPoint, + icon: UIImage(named: "Icons - ContactJournal"), iconTint: .enaColor(for: .riskHigh), hairline: .none, bottomSpacing: .medium ), ExposureSubmissionDynamicCell.stepCell( style: .body, - title: AppStrings.ExposureSubmissionSuccess.listItemPCR2, - accessibilityIdentifier: nil, - icon: UIImage(named: "Icons - Red Plus"), - hairline: .none, - bottomSpacing: .medium - ) - ]) - } else if coronaTestType == .antigen { - cells.append(contentsOf: [ - ExposureSubmissionDynamicCell.stepCell( - style: .body, - title: AppStrings.ExposureSubmissionSuccess.listItemRAT0, - accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.RAT.firstBulletPoint, - icon: UIImage(named: "Icons - Home"), - iconTint: .enaColor(for: .riskHigh), - hairline: .none, - bottomSpacing: .medium - ), - ExposureSubmissionDynamicCell.stepCell( - style: .body, - title: AppStrings.ExposureSubmissionSuccess.listItemRAT1, - accessibilityIdentifier: nil, + title: AppStrings.ExposureSubmissionSuccess.listItemSRS2, + accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.SRS.thirdBulletPoint, icon: UIImage(named: "Icons - Test Tube"), iconTint: .enaColor(for: .riskHigh), hairline: .none, @@ -147,8 +206,8 @@ class ExposureSubmissionSuccessViewController: DynamicTableViewController, ENANa ), ExposureSubmissionDynamicCell.stepCell( style: .body, - title: AppStrings.ExposureSubmissionSuccess.listItemRAT2, - accessibilityIdentifier: nil, + title: AppStrings.ExposureSubmissionSuccess.listItemSRS3, + accessibilityIdentifier: AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.SRS.fourthBulletPoint, icon: UIImage(named: "Icons - Hotline"), iconTint: .enaColor(for: .riskHigh), hairline: .none, @@ -156,7 +215,7 @@ class ExposureSubmissionSuccessViewController: DynamicTableViewController, ENANa ) ]) } - + cells.append(contentsOf: [ .title2( text: AppStrings.ExposureSubmissionSuccess.subTitle, diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionSymptomsOnsetViewController.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionSymptomsOnsetViewController.swift index 3c24ff6851e..1976cffa6e5 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionSymptomsOnsetViewController.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionSymptomsOnsetViewController.swift @@ -104,7 +104,8 @@ class ExposureSubmissionSymptomsOnsetViewController: DynamicTableViewController, navigationItem.title = AppStrings.ExposureSubmissionSymptomsOnset.title navigationItem.rightBarButtonItem = dismissHandlingCloseBarButton - + navigationItem.setHidesBackButton(true, animated: true) + setupTableView() symptomsOnsetButtonStateSubscription = $selectedSymptomsOnsetOption.receive(on: RunLoop.main.ocombine).sink { [weak self] in diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionSymptomsViewController.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionSymptomsViewController.swift index 096d66a49fe..0d11abd496e 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionSymptomsViewController.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/ExposureSubmissionSymptomsViewController.swift @@ -112,6 +112,7 @@ final class ExposureSubmissionSymptomsViewController: DynamicTableViewController navigationItem.title = AppStrings.ExposureSubmissionSymptoms.title navigationItem.rightBarButtonItem = dismissHandlingCloseBarButton + navigationItem.setHidesBackButton(true, animated: true) view.backgroundColor = .enaColor(for: .background) diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSConsent/SRSConsentViewController.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSConsent/SRSConsentViewController.swift new file mode 100644 index 00000000000..78e36b69a68 --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSConsent/SRSConsentViewController.swift @@ -0,0 +1,70 @@ +// +// 🦠 Corona-Warn-App +// + +import UIKit + +class SRSConsentViewController: DynamicTableViewController, FooterViewHandling { + + // MARK: - Init + + init( + viewModel: SRSConsentViewModel = .init(), + onPrimaryButtonTap: @escaping (@escaping (Bool) -> Void) -> Void, + dismiss: @escaping () -> Void + ) { + self.viewModel = viewModel + self.onPrimaryButtonTap = onPrimaryButtonTap + self.dismiss = dismiss + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Overrides + + override func viewDidLoad() { + super.viewDidLoad() + setupView() + } + + // MARK: - Protocol FooterViewHandling + + func didTapFooterViewButton(_ type: FooterViewModel.ButtonType) { + onPrimaryButtonTap { [weak self] isLoading in + DispatchQueue.main.async { + self?.footerView?.setLoadingIndicator(isLoading, disable: isLoading, button: .primary) + } + } + } + + // MARK: - Private + + private let viewModel: SRSConsentViewModel + private let onPrimaryButtonTap: (@escaping (Bool) -> Void) -> Void + private let dismiss: () -> Void + + private func setupView() { + + navigationItem.title = AppStrings.SRSConsentScreen.title + navigationItem.rightBarButtonItem = CloseBarButtonItem(onTap: dismiss) + + if traitCollection.userInterfaceStyle == .dark { + navigationController?.navigationBar.tintColor = .enaColor(for: .textContrast) + } else { + navigationController?.navigationBar.tintColor = .enaColor(for: .tint) + } + + view.backgroundColor = .enaColor(for: .background) + tableView.register( + UINib(nibName: String(describing: DynamicLegalExtendedCell.self), bundle: nil), + forCellReuseIdentifier: DynamicLegalExtendedCell.reuseIdentifier + ) + + dynamicTableViewModel = viewModel.dynamicTableViewModel + tableView.separatorStyle = .none + } +} diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSConsent/SRSConsentViewModel.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSConsent/SRSConsentViewModel.swift new file mode 100644 index 00000000000..c731908a037 --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSConsent/SRSConsentViewModel.swift @@ -0,0 +1,125 @@ +// +// 🦠 Corona-Warn-App +// + +import UIKit + +struct SRSConsentViewModel { + + // MARK: - Internal + + var dynamicTableViewModel: DynamicTableViewModel { + var model = DynamicTableViewModel([]) + + // Ihr Einverständnis Section + model.add( + .section( + header: .image( + UIImage(imageLiteralResourceName: "Illu_Testresult_available"), + accessibilityLabel: AppStrings.ExposureSubmissionTestResultAvailable.accImageDescription, + accessibilityIdentifier: AccessibilityIdentifiers.SRSConsentScreen.accImageDescription, + height: 250 + ), + cells: [ + .title2( + text: AppStrings.SRSConsentScreen.headerSection1, + accessibilityIdentifier: AccessibilityIdentifiers.SRSConsentScreen.headerSection1 + ) + ]) + ) + + // Andere Warnen + model.add( + .section(cells: [ + .body(text: AppStrings.SRSConsentScreen.titleDescription1), + .icon( + UIImage(imageLiteralResourceName: "SRS-Positive-icon"), + text: .string(AppStrings.SRSConsentScreen.instruction1), + alignment: .top + ), + .icon( + UIImage(imageLiteralResourceName: "SRS-Warn-Others-icon"), + text: .string(String( + format: AppStrings.SRSConsentScreen.instruction2, + "90" // to.do fetch the number from app config + )), + alignment: .top + ) + ]) + ) + + // Legal text + model.add( + .section(cells: [ + .legalExtendedDataDonation( + title: NSAttributedString(string: AppStrings.SRSConsentScreen.legalHeadline), + description: NSAttributedString( + string: AppStrings.SRSConsentScreen.legalDescription, + attributes: [.font: UIFont.preferredFont(forTextStyle: .body)] + ), + bulletPoints: bulletPoints, + accessibilityIdentifier: AccessibilityIdentifiers.SRSConsentScreen.acknowledgementTitle, + configure: { _, cell, _ in + cell.backgroundColor = .enaColor(for: .background) + } + ), + .bulletPoint(text: AppStrings.SRSConsentScreen.acknowledgement1, alignment: .legal), + .space(height: 8.0), + .bulletPoint(text: AppStrings.SRSConsentScreen.acknowledgement2, alignment: .legal), + .space(height: 8.0), + .bulletPoint(text: AppStrings.SRSConsentScreen.acknowledgement3, alignment: .legal), + .space(height: 8.0), + .bulletPoint(text: AppStrings.SRSConsentScreen.acknowledgement4, alignment: .legal), + .space(height: 8.0), + .bulletPoint(text: AppStrings.SRSConsentScreen.acknowledgement5, alignment: .legal), + .space(height: 8.0), + .body(text: AppStrings.SRSConsentScreen.acknowledgement6) + ]) + ) + + // Even more info + model.add( + .section(separators: .all, cells: [ + .body( + text: AppStrings.SRSConsentScreen.dataProcessingDetailInfo, + style: DynamicCell.TextCellStyle.label, + accessibilityIdentifier: AccessibilityIdentifiers.SRSConsentScreen.dataProcessingDetailInfoButton, + accessibilityTraits: UIAccessibilityTraits.link, + action: .push(viewController: SRSDataProcessingInfoViewController()), + configure: { _, cell, _ in + cell.accessoryType = .disclosureIndicator + cell.selectionStyle = .default + }) + ]) + ) + + return model + } + + // MARK: - Private + + private var bulletPoints: [NSAttributedString] { + var points = [NSAttributedString]() + + // highlighted texts + let attributes: [NSAttributedString.Key: Any] = [ + .font: UIFont.preferredFont(forTextStyle: .headline) + ] + + // Don't forget the tab for all paragraphs after the first! + let ack1 = NSMutableAttributedString(string: "\(AppStrings.SRSConsentScreen.legalBulletPoint01)") + ack1.addAttributes(attributes, range: NSRange(location: 0, length: AppStrings.SRSConsentScreen.legalBulletPoint01.count)) + + let ack2 = NSMutableAttributedString(string: "\(AppStrings.SRSConsentScreen.legalBulletPoint02)") + ack2.addAttributes(attributes, range: NSRange(location: 52, length: 36)) + + let ack3 = NSMutableAttributedString(string: "\(AppStrings.SRSConsentScreen.legalBulletPoint03)") + ack3.addAttributes(attributes, range: NSRange(location: 0, length: AppStrings.SRSConsentScreen.legalBulletPoint03.count)) + + points.append(ack1) + points.append(ack2) + points.append(ack3) + + return points + } +} diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSConsent/SRSFlowType.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSConsent/SRSFlowType.swift new file mode 100644 index 00000000000..c6c363e43a1 --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSConsent/SRSFlowType.swift @@ -0,0 +1,8 @@ +// +// 🦠 Corona-Warn-App +// + +enum SRSFlowType { + case srsPositive + case positiveWithoutResultInTheApp +} diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSConsent/__tests__/SRSConsentViewModelTests.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSConsent/__tests__/SRSConsentViewModelTests.swift new file mode 100644 index 00000000000..4805d46f53c --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSConsent/__tests__/SRSConsentViewModelTests.swift @@ -0,0 +1,22 @@ +// +// 🦠 Corona-Warn-App +// + +import XCTest +@testable import ENA + +class SRSConsentViewModelTests: XCTestCase { + + func testDynamicTableViewModel() { + let viewModel = SRSConsentViewModel() + + let dynamicTableViewModel = viewModel.dynamicTableViewModel + + XCTAssertEqual(dynamicTableViewModel.numberOfSection, 4) + XCTAssertEqual(dynamicTableViewModel.section(0).cells.count, 1) + XCTAssertEqual(dynamicTableViewModel.section(1).cells.count, 3) + XCTAssertEqual(dynamicTableViewModel.section(2).cells.count, 12) + XCTAssertEqual(dynamicTableViewModel.section(3).cells.count, 1) + } + +} diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSDataProcessingInfo/SRSDataProcessingInfoViewController.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSDataProcessingInfo/SRSDataProcessingInfoViewController.swift new file mode 100644 index 00000000000..308d09d4da5 --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSDataProcessingInfo/SRSDataProcessingInfoViewController.swift @@ -0,0 +1,54 @@ +// +// 🦠 Corona-Warn-App +// + +import UIKit + +class SRSDataProcessingInfoViewController: DynamicTableViewController { + + // MARK: - Init + + init( + viewModel: SRSDataProcessingInfoViewModel = .init() + ) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Overrides + + override func viewDidLoad() { + super.viewDidLoad() + setupView() + } + + // MARK: - Private + + private let viewModel: SRSDataProcessingInfoViewModel + + private func setupView() { + navigationController?.navigationBar.prefersLargeTitles = true + navigationItem.title = AppStrings.SRSConsentScreen.dataProcessingDetailInfo + + if traitCollection.userInterfaceStyle == .dark { + navigationController?.navigationBar.tintColor = .enaColor(for: .textContrast) + } else { + navigationController?.navigationBar.tintColor = .enaColor(for: .tint) + } + + view.backgroundColor = .enaColor(for: .background) + + tableView.register( + UINib(nibName: String(describing: DynamicLegalExtendedCell.self), bundle: nil), + forCellReuseIdentifier: DynamicLegalExtendedCell.reuseIdentifier + ) + + dynamicTableViewModel = viewModel.dynamicTableViewModel + tableView.separatorStyle = .none + } +} diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSDataProcessingInfo/SRSDataProcessingInfoViewModel.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSDataProcessingInfo/SRSDataProcessingInfoViewModel.swift new file mode 100644 index 00000000000..dcc95cecdaa --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/SRSDataProcessingInfo/SRSDataProcessingInfoViewModel.swift @@ -0,0 +1,32 @@ +// +// 🦠 Corona-Warn-App +// + +import UIKit + +struct SRSDataProcessingInfoViewModel { + + // MARK: - Internal + + var dynamicTableViewModel: DynamicTableViewModel { + var model = DynamicTableViewModel([]) + + model.add( + .section(cells: [ + .legalExtendedDataDonation( + title: NSAttributedString(string: AppStrings.SRSDataProcessingInfo.title), + description: NSAttributedString( + string: AppStrings.SRSDataProcessingInfo.description, + attributes: [.font: UIFont.preferredFont(forTextStyle: .body)] + ), + accessibilityIdentifier: AccessibilityIdentifiers.SRSDataProcessingDetailInfo.content, + configure: { _, cell, _ in + cell.backgroundColor = .enaColor(for: .background) + } + ) + ]) + ) + + return model + } +} diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/TestTypeSelection/SRSTestTypeSelectionViewController.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/TestTypeSelection/SRSTestTypeSelectionViewController.swift new file mode 100644 index 00000000000..70f2ae64440 --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/TestTypeSelection/SRSTestTypeSelectionViewController.swift @@ -0,0 +1,131 @@ +// +// 🦠 Corona-Warn-App +// + +import UIKit +import OpenCombine + +class SRSTestTypeSelectionViewController: DynamicTableViewController { + + // MARK: - Init + + init( + viewModel: SRSTestTypeSelectionViewModel, + onPrimaryButtonTap: @escaping (SRSSubmissionType) -> Void, + onDismiss: @escaping CompletionVoid + ) { + self.viewModel = viewModel + self.onPrimaryButtonTap = onPrimaryButtonTap + self.onDismiss = onDismiss + + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Overrides + + override func viewDidLoad() { + super.viewDidLoad() + + setupNavigation() + setupView() + setupTableView() + setupBindings() + } + + // MARK: - Private + + private let viewModel: SRSTestTypeSelectionViewModel + private let onPrimaryButtonTap: (SRSSubmissionType) -> Void + private let onDismiss: CompletionVoid + private var subscriptions = Set() + + private func setupNavigation() { + navigationItem.title = AppStrings.ExposureSubmission.SRSTestTypeSelection.title + navigationItem.rightBarButtonItem = dismissHandlingCloseBarButton + navigationItem.setHidesBackButton(true, animated: true) + } + + private func setupView() { + view.backgroundColor = .enaColor(for: .background) + } + + private func setupTableView() { + tableView.separatorStyle = .none + + tableView.register( + DynamicTableViewOptionGroupCell.self, + forCellReuseIdentifier: Self.CustomCellReuseIdentifiers.optionGroupCell.rawValue + ) + + dynamicTableViewModel = viewModel.dynamicTableViewModel + } + + private func setupBindings() { + viewModel.$selectedSubmissionType + .sink { [weak self] in + self?.footerView?.setEnabled($0 != nil, button: .primary) + } + .store(in: &subscriptions) + } + + // to.do move alert handling to coordinator + private func showWarnProcessCancelAlert() { + let alert = UIAlertController( + title: AppStrings.ExposureSubmission.SRSTestTypeSelection.warnProcessCancelAlertTitle, + message: AppStrings.ExposureSubmission.SRSTestTypeSelection.warnProcessCancelAlertMessage, + preferredStyle: .alert + ) + + alert.addAction(UIAlertAction( + title: AppStrings.ExposureSubmission.SRSTestTypeSelection.warnProcessCancelAlertActionContinue, + style: .default + )) + + alert.addAction(UIAlertAction( + title: AppStrings.ExposureSubmission.SRSTestTypeSelection.warnProcessCancelAlertActionCancel, + style: .cancel, + handler: { [weak self] _ in + self?.onDismiss() + } + )) + + navigationController?.topViewController?.present(alert, animated: true) + } +} + +// MARK: - FooterViewHandling + +extension SRSTestTypeSelectionViewController: FooterViewHandling { + + func didTapFooterViewButton(_ type: FooterViewModel.ButtonType) { + guard let submissionType = viewModel.selectedSubmissionType else { + Log.error("\(#function): Primary button must not be enabled before the user has selected an option") + return + } + + onPrimaryButtonTap(submissionType) + } +} + +// MARK: - DismissHandling + +extension SRSTestTypeSelectionViewController: DismissHandling { + + func wasAttemptedToBeDismissed() { + showWarnProcessCancelAlert() + } +} + + +// MARK: - Cell reuse identifiers. + +extension SRSTestTypeSelectionViewController { + enum CustomCellReuseIdentifiers: String, TableViewCellReuseIdentifiers { + case optionGroupCell + } +} diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/TestTypeSelection/SRSTestTypeSelectionViewModel.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/TestTypeSelection/SRSTestTypeSelectionViewModel.swift new file mode 100644 index 00000000000..4de90258fd9 --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/View/Controller/TestTypeSelection/SRSTestTypeSelectionViewModel.swift @@ -0,0 +1,135 @@ +// +// 🦠 Corona-Warn-App +// + +import Foundation +import OpenCombine + +class SRSTestTypeSelectionViewModel { + + // MARK: - Init + + init(isSelfTestTypePreselected: Bool) { + isSelfTestTypePreSelected = isSelfTestTypePreselected + } + + // MARK: - Internal + + var dynamicTableViewModel: DynamicTableViewModel { + .init([ + .section( + header: .none, + footer: .none, + separators: .none, + isHidden: nil, + background: .none, + cells: [ + .body( + text: AppStrings.ExposureSubmission.SRSTestTypeSelection.body, + accessibilityIdentifier: AccessibilityIdentifiers.ExposureSubmission.SRSTestTypeSelection.body + ), + .footnote( + text: AppStrings.ExposureSubmission.SRSTestTypeSelection.description, + color: .enaColor(for: .textPrimary2), + accessibilityIdentifier: AccessibilityIdentifiers.ExposureSubmission.SRSTestTypeSelection.description + ), + .custom( + withIdentifier: SRSTestTypeSelectionViewController.CustomCellReuseIdentifiers.optionGroupCell, + configure: { _, cell, _ in + guard let cell = cell as? DynamicTableViewOptionGroupCell else { + return + } + + let options: [OptionGroupViewModel.Option] = self.submissionTypes + .map { + (title: $0.optionTitle, accessibilityIdentifier: $0.optionAccessibilityIdentifier) + } + .filter { + !$0.title.isEmpty + } + .map { + .option( + title: $0.title, + accessibilityIdentifier: $0.accessibilityIdentifier + ) + } + + cell.configure( + options: options, + initialSelection: self.initialSelection + ) + + self.optionGroupSelectionSubscription = cell.$selection.sink { + guard case let .option(index) = $0 else { return } + self.selectedSubmissionType = self.submissionTypes[index] + } + }) + ] + ) + ]) + } + + /// The `SAP_Internal_SubmissionPayload.SubmissionType` that the user has selected in the list. + /// Is `nil`, as long as the user hasn't made a selection. + @OpenCombine.Published var selectedSubmissionType: SRSSubmissionType? + + // MARK: - Private + + /// The order of the list entries shown. + private let submissionTypes: [SRSSubmissionType] = [ + .srsRegisteredRat, + .srsSelfTest, + .srsRegisteredPcr, + .srsUnregisteredPcr, + .srsRapidPcr, + .srsOther + ] + + private var optionGroupSelectionSubscription: AnyCancellable? + + private let isSelfTestTypePreSelected: Bool + + private var initialSelection: OptionGroupViewModel.Selection? { + if isSelfTestTypePreSelected, let index = submissionTypes.firstIndex(of: .srsSelfTest) { + return .option(index: index) + } else { + return nil + } + } +} + +fileprivate extension SRSSubmissionType { + var optionTitle: String { + switch self { + case .srsSelfTest: + return AppStrings.ExposureSubmission.SRSTestTypeSelection.optionSRSSelfTestTitle + case .srsRegisteredRat: + return AppStrings.ExposureSubmission.SRSTestTypeSelection.optionSRSRegisteredRatTitle + case .srsRegisteredPcr: + return AppStrings.ExposureSubmission.SRSTestTypeSelection.optionSRSRegisteredPcrTitle + case .srsUnregisteredPcr: + return AppStrings.ExposureSubmission.SRSTestTypeSelection.optionSRSUnregisteredPcrTitle + case .srsRapidPcr: + return AppStrings.ExposureSubmission.SRSTestTypeSelection.optionSRSRapidPcrTitle + case .srsOther: + return AppStrings.ExposureSubmission.SRSTestTypeSelection.optionSRSOtherTitle + } + } + + var optionAccessibilityIdentifier: String? { + switch self { + case .srsSelfTest: + return AccessibilityIdentifiers.ExposureSubmission.SRSTestTypeSelection.optionSRSSelfTest + case .srsRegisteredRat: + return AccessibilityIdentifiers.ExposureSubmission.SRSTestTypeSelection.optionSRSRegisteredRat + case .srsRegisteredPcr: + return AccessibilityIdentifiers.ExposureSubmission.SRSTestTypeSelection.optionSRSRegisteredPcr + case .srsUnregisteredPcr: + return AccessibilityIdentifiers.ExposureSubmission.SRSTestTypeSelection.optionSRSUnregisteredPcr + case .srsRapidPcr: + return AccessibilityIdentifiers.ExposureSubmission.SRSTestTypeSelection.optionSRSRapidPcr + case .srsOther: + return AccessibilityIdentifiers.ExposureSubmission.SRSTestTypeSelection.optionSRSOther + } + } +} diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionCoordinatorModelTests.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionCoordinatorModelTests.swift index 3cfdeae82b1..1f30c52eb17 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionCoordinatorModelTests.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionCoordinatorModelTests.swift @@ -22,6 +22,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: familyMemberCoronaTestService, eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -41,6 +42,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: familyMemberCoronaTestService, eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -73,6 +75,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: familyMemberCoronaTestService, eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -109,6 +112,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: familyMemberCoronaTestService, eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -124,6 +128,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -136,6 +141,8 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), + familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -159,6 +166,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -182,6 +190,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -205,6 +214,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -219,6 +229,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -240,12 +251,13 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: exposureSubmissionService, coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) ) - model.coronaTestType = .pcr + model.submissionTestType = .registeredTest(.pcr) model.symptomsOptionSelected(.no) // Submit to check that correct symptoms onset is set @@ -268,12 +280,13 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: exposureSubmissionService, coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) ) - model.coronaTestType = .pcr + model.submissionTestType = .registeredTest(.pcr) model.symptomsOptionSelected(.preferNotToSay) // Submit to check that correct symptoms onset is set @@ -298,12 +311,13 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: exposureSubmissionService, coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) ) - model.coronaTestType = .pcr + model.submissionTestType = .registeredTest(.pcr) let yesterday = try XCTUnwrap(Calendar.gregorian().date(byAdding: .day, value: -1, to: Date())) @@ -328,12 +342,13 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: exposureSubmissionService, coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) ) - model.coronaTestType = .pcr + model.submissionTestType = .registeredTest(.pcr) model.symptomsOnsetOptionSelected(.lastSevenDays) // Submit to check that correct symptoms onset is set @@ -355,12 +370,13 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: exposureSubmissionService, coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) ) - model.coronaTestType = .pcr + model.submissionTestType = .registeredTest(.pcr) model.symptomsOnsetOptionSelected(.oneToTwoWeeksAgo) // Submit to check that correct symptoms onset is set @@ -383,12 +399,13 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: exposureSubmissionService, coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) ) - model.coronaTestType = .pcr + model.submissionTestType = .registeredTest(.pcr) model.symptomsOnsetOptionSelected(.moreThanTwoWeeksAgo) // Submit to check that correct symptoms onset is set @@ -410,12 +427,13 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: exposureSubmissionService, coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) ) - model.coronaTestType = .pcr + model.submissionTestType = .registeredTest(.pcr) model.symptomsOnsetOptionSelected(.preferNotToSay) // Submit to check that correct symptoms onset is set @@ -446,6 +464,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: store) @@ -469,6 +488,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: store) @@ -497,6 +517,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: store) @@ -520,6 +541,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: store) @@ -541,12 +563,13 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: exposureSubmissionService, coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) ) - model.coronaTestType = .pcr + model.submissionTestType = .registeredTest(.pcr) let expectedIsLoadingValues = [true, false] var isLoadingValues = [Bool]() @@ -581,12 +604,13 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: exposureSubmissionService, coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) ) - model.coronaTestType = .pcr + model.submissionTestType = .registeredTest(.pcr) let expectedIsLoadingValues = [true, false] var isLoadingValues = [Bool]() @@ -621,12 +645,13 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: exposureSubmissionService, coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) ) - model.coronaTestType = .pcr + model.submissionTestType = .registeredTest(.pcr) let expectedIsLoadingValues = [true, false] var isLoadingValues = [Bool]() @@ -661,12 +686,13 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: exposureSubmissionService, coronaTestService: MockCoronaTestService(), + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) ) - model.coronaTestType = .pcr + model.submissionTestType = .registeredTest(.pcr) let expectedIsLoadingValues = [true, false] var isLoadingValues = [Bool]() @@ -701,6 +727,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -746,6 +773,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: familyMemberCoronaTestService, eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -790,6 +818,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -835,6 +864,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: familyMemberCoronaTestService, eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -879,6 +909,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -923,6 +954,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -967,6 +999,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -1012,6 +1045,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: familyMemberCoronaTestService, eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -1056,6 +1090,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -1101,6 +1136,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: familyMemberCoronaTestService, eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -1145,6 +1181,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -1190,6 +1227,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: familyMemberCoronaTestService, eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -1234,6 +1272,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: MockFamilyMemberCoronaTestService(), eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) @@ -1279,6 +1318,7 @@ class ExposureSubmissionCoordinatorModelTests: CWATestCase { let model = ExposureSubmissionCoordinatorModel( exposureSubmissionService: MockExposureSubmissionService(), coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: familyMemberCoronaTestService, eventProvider: MockEventStore(), recycleBin: RecycleBin(store: MockTestStore()) diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionCoordinatorTests.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionCoordinatorTests.swift index 67d9e24ea70..b60cf9c2fef 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionCoordinatorTests.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionCoordinatorTests.swift @@ -98,6 +98,7 @@ class ExposureSubmissionCoordinatorTests: CWATestCase { eventStore: eventStore, contactDiaryStore: diaryStore ), + srsService: MockSRSService(), healthCertificateService: healthCertificateService, healthCertificateValidationService: healthCertificateValidationService, healthCertificateValidationOnboardedCountriesProvider: healthCertificateValidationOnboardedCountriesProvider, @@ -120,6 +121,7 @@ class ExposureSubmissionCoordinatorTests: CWATestCase { parentViewController: parentNavigationController, exposureSubmissionService: exposureSubmissionService, coronaTestService: coronaTestService, + srsService: MockSRSService(), familyMemberCoronaTestService: familyMemberCoronaTestService, healthCertificateService: healthCertificateService, healthCertificateValidationService: healthCertificateValidationService, @@ -181,7 +183,7 @@ class ExposureSubmissionCoordinatorTests: CWATestCase { coronaTestService.pcrTest.value = .mock(registrationToken: "asdf", testResult: .negative) - coordinator.start(with: .pcr) + coordinator.start(with: .registeredTest(.pcr)) // Get navigation controller and make sure to load view. let navigationController = getNavigationController(from: coordinator) @@ -200,7 +202,7 @@ class ExposureSubmissionCoordinatorTests: CWATestCase { coronaTestService.pcrTest.value = .mock(registrationToken: "asdf", testResult: .positive) - coordinator.start(with: .pcr) + coordinator.start(with: .registeredTest(.pcr)) // Get navigation controller and make sure to load view. let navigationController = getNavigationController(from: coordinator) @@ -238,7 +240,7 @@ class ExposureSubmissionCoordinatorTests: CWATestCase { coronaTestService.pcrTest.value = .mock(testResult: .positive, positiveTestResultWasShown: false) - coordinator.start(with: .pcr) + coordinator.start(with: .registeredTest(.pcr)) let unknown = coordinator.getInitialViewController() XCTAssertTrue(unknown is TopBottomContainerViewController) diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionIntroViewModelTests.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionIntroViewModelTests.swift index f9dece30a61..1b0a8f5ca53 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionIntroViewModelTests.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionIntroViewModelTests.swift @@ -22,17 +22,61 @@ class ExposureSubmissionIntroViewModelTests: CWATestCase { store.antigenTestProfiles = [antigenTestProfile] let viewModel = ExposureSubmissionIntroViewModel( + onPositiveSelfTestButtonTap: { }, + onSelfReportSubmissionButtonTap: { }, onQRCodeButtonTap: { _ in }, onFindTestCentersTap: { }, - onTANButtonTap: { }, - onHotlineButtonTap: { }, onRapidTestProfileTap: { }, antigenTestProfileStore: store ) - let profileCell = viewModel.dynamicTableModel.cell(at: IndexPath(row: 2, section: 1)) + let profileCell = viewModel.dynamicTableModel.cell(at: IndexPath(row: 5, section: 1)) XCTAssertEqual(profileCell.tag, "AntigenTestProfileCard") } + func test_When_DynamicTableViewModel_Then_NumberOfCellsAndTypeIsCorrect() { + let store = MockTestStore() + + let viewModel = ExposureSubmissionIntroViewModel( + onPositiveSelfTestButtonTap: { }, + onSelfReportSubmissionButtonTap: { }, + onQRCodeButtonTap: { _ in }, + onFindTestCentersTap: { }, + onRapidTestProfileTap: { }, + antigenTestProfileStore: store + ) + + let section0 = viewModel.dynamicTableModel.section(0) + var cells = section0.cells + XCTAssertEqual(cells.count, 0) + + let section1 = viewModel.dynamicTableModel.section(1) + cells = section1.cells + XCTAssertEqual(cells.count, 6) + + let firstItem = cells[0] + var id = firstItem.cellReuseIdentifier + XCTAssertEqual(id.rawValue, "imageCardCell") + + let secondItem = cells[1] + id = secondItem.cellReuseIdentifier + XCTAssertEqual(id.rawValue, "imageCardCell") + + let thirdItem = cells[2] + id = thirdItem.cellReuseIdentifier + XCTAssertEqual(id.rawValue, "labelCell") + + let fourthItem = cells[3] + id = fourthItem.cellReuseIdentifier + XCTAssertEqual(id.rawValue, "imageCardCell") + + let fifthItem = cells[4] + id = fifthItem.cellReuseIdentifier + XCTAssertEqual(id.rawValue, "imageCardCell") + + let sixthItem = cells[5] + id = sixthItem.cellReuseIdentifier + XCTAssertEqual(id.rawValue, "imageCardCell") + } } diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionSuccessViewControllerTest.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionSuccessViewControllerTest.swift index 4d3fe6f7c38..b49324f73aa 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionSuccessViewControllerTest.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/ExposureSubmissionSuccessViewControllerTest.swift @@ -8,23 +8,33 @@ import XCTest class ExposureSubmissionSuccessViewControllerTest: CWATestCase { func testPCR() { - let vc = ExposureSubmissionSuccessViewController(coronaTestType: .pcr, dismiss: { }) + let vc = ExposureSubmissionSuccessViewController(submissionTestType: .registeredTest(.pcr), dismiss: { }) _ = vc.view XCTAssertEqual(vc.navigationItem.title, AppStrings.ExposureSubmissionSuccess.title) XCTAssertTrue(vc.navigationItem.hidesBackButton) XCTAssertEqual(vc.tableView.numberOfSections, 1) - XCTAssertEqual(vc.tableView.numberOfRows(inSection: 0), 10) + XCTAssertEqual(vc.tableView.numberOfRows(inSection: 0), 11) } func testAntigen() { - let vc = ExposureSubmissionSuccessViewController(coronaTestType: .antigen, dismiss: { }) + let vc = ExposureSubmissionSuccessViewController(submissionTestType: .registeredTest(.antigen), dismiss: { }) _ = vc.view XCTAssertEqual(vc.navigationItem.title, AppStrings.ExposureSubmissionSuccess.title) XCTAssertTrue(vc.navigationItem.hidesBackButton) XCTAssertEqual(vc.tableView.numberOfSections, 1) - XCTAssertEqual(vc.tableView.numberOfRows(inSection: 0), 10) + XCTAssertEqual(vc.tableView.numberOfRows(inSection: 0), 11) + } + + func testSRS() { + let vc = ExposureSubmissionSuccessViewController(submissionTestType: .srs(.srsSelfTest), dismiss: { }) + + _ = vc.view + XCTAssertEqual(vc.navigationItem.title, AppStrings.ExposureSubmissionSuccess.title) + XCTAssertTrue(vc.navigationItem.hidesBackButton) + XCTAssertEqual(vc.tableView.numberOfSections, 1) + XCTAssertEqual(vc.tableView.numberOfRows(inSection: 0), 11) } } diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/Mock Objects/MockExposureSubmissionService.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/Mock Objects/MockExposureSubmissionService.swift index efbc7ea6e67..f79a4061411 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/Mock Objects/MockExposureSubmissionService.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/Mock Objects/MockExposureSubmissionService.swift @@ -38,5 +38,9 @@ class MockExposureSubmissionService: ExposureSubmissionService { submitExposureCallback?(completion) } + func submitSRSExposure(submissionType: SRSSubmissionType, srsOTP: String, completion: @escaping (ExposureSubmissionServiceError?) -> Void) { + submitExposureCallback?(completion) + } + } #endif diff --git a/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/SRSTestTypeSelectionViewModelTests.swift b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/SRSTestTypeSelectionViewModelTests.swift new file mode 100644 index 00000000000..505d21e6c58 --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Scenes/ExposureSubmission/__tests__/SRSTestTypeSelectionViewModelTests.swift @@ -0,0 +1,40 @@ +// +// 🦠 Corona-Warn-App +// + +import XCTest +@testable import ENA + +final class SRSTestTypeSelectionViewModelTests: XCTestCase { + + func test_When_DynamicTableViewModel_Then_NumberOfCellsAndTypeIsCorrect() throws { + // Given + let sut = SRSTestTypeSelectionViewModel(isSelfTestTypePreselected: false) + let cells = sut.dynamicTableViewModel.section(0).cells + + // Then + XCTAssertEqual(cells.count, 3) + + XCTAssertEqual(cells[0].cellReuseIdentifier.rawValue, "labelCell") + XCTAssertEqual(cells[1].cellReuseIdentifier.rawValue, "labelCell") + XCTAssertEqual(cells[2].cellReuseIdentifier.rawValue, "optionGroupCell") + } + + func test_When_IsSelfTestTypePreselected_False_Then_selectedSubmissionTypeShouldBeNil() throws { + // Given + let sut = SRSTestTypeSelectionViewModel(isSelfTestTypePreselected: false) + + // Then + XCTAssertNil(sut.selectedSubmissionType) + } + + func test_When_IsSelfTestTypePreselected_True_Then_selectedSubmissionTypeShouldBeSRSSelfTest() throws { + DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.1) { + // Given + let sut = SRSTestTypeSelectionViewModel(isSelfTestTypePreselected: true) + + // Then + XCTAssertEqual(sut.selectedSubmissionType, .srsSelfTest) + } + } +} diff --git a/src/xcode/ENA/ENA/Source/Scenes/HealthCertificates/HealthCertifiedPerson/Cells/HealthCertificate/HealthCertificateCellViewModel.swift b/src/xcode/ENA/ENA/Source/Scenes/HealthCertificates/HealthCertifiedPerson/Cells/HealthCertificate/HealthCertificateCellViewModel.swift index 5773604db8c..915a1a44a03 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/HealthCertificates/HealthCertifiedPerson/Cells/HealthCertificate/HealthCertificateCellViewModel.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/HealthCertificates/HealthCertifiedPerson/Cells/HealthCertificate/HealthCertificateCellViewModel.swift @@ -189,6 +189,8 @@ final class HealthCertificateCellViewModel { return UIImage(named: "Icon_CurrentlyUsedCertificate_green") case .blueRedTilted, .blueOnly, .solidGrey, .whiteWithGreyBorder, .whiteToLightBlue: return UIImage(named: "Icon_CurrentlyUsedCertificate_grey") + default: + return nil } }() diff --git a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Risk/HomeRiskTableViewCell.swift b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Risk/HomeRiskTableViewCell.swift index 274e54619b7..34bf6a4ed7d 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Risk/HomeRiskTableViewCell.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Risk/HomeRiskTableViewCell.swift @@ -39,7 +39,9 @@ final class HomeRiskTableViewCell: UITableViewCell { return accessibilityElements } - set { } + set { + _ = newValue + } } // Ignore touches on the button when it's disabled diff --git a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/HomeLinkCardView/HomeLinkCardView.swift b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/HomeLinkCardView/HomeLinkCardView.swift index 9aca3fae9d6..495784e70b4 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/HomeLinkCardView/HomeLinkCardView.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/HomeLinkCardView/HomeLinkCardView.swift @@ -41,7 +41,10 @@ class HomeLinkCardView: UIView { return accessibilityElements as [Any] } - set {} + + set { + _ = newValue + } } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { diff --git a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/HomeStatisticsCardView/HomeStatisticsCardView.swift b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/HomeStatisticsCardView/HomeStatisticsCardView.swift index a75e4e9ffd2..af3034f865c 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/HomeStatisticsCardView/HomeStatisticsCardView.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/HomeStatisticsCardView/HomeStatisticsCardView.swift @@ -100,7 +100,10 @@ class HomeStatisticsCardView: UIView { return accessibilityElements } - set { } + + set { + _ = newValue + } } // MARK: - Internal diff --git a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/Manage Statistics/CustomDashedView.swift b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/Manage Statistics/CustomDashedView.swift index e7734b57a36..a02e7d9ba26 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/Manage Statistics/CustomDashedView.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/Manage Statistics/CustomDashedView.swift @@ -53,7 +53,9 @@ class CustomDashedView: UIControl { override var accessibilityElements: [Any]? { get { [label, icon].compactMap { $0 } } - set { } + set { + _ = newValue + } } override func accessibilityElementDidBecomeFocused() { diff --git a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/__tests__/SAP_Internal_Stats_LinkCard+Mock.swift b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/__tests__/SAP_Internal_Stats_LinkCard+Mock.swift index 646da803957..a3c2c0adafd 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/__tests__/SAP_Internal_Stats_LinkCard+Mock.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/Statistics/__tests__/SAP_Internal_Stats_LinkCard+Mock.swift @@ -2,10 +2,8 @@ // 🦠 Corona-Warn-App // -@testable import ENA - extension SAP_Internal_Stats_LinkCard { - static let rkiPandemicRadarURL: String = "https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Situationsberichte/COVID-19-Trends/COVID-19-Trends.html?__blob=publicationFile#/home" + static let rkiPandemicRadarURLMock: String = "https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Situationsberichte/COVID-19-Trends/COVID-19-Trends.html?__blob=publicationFile#/home" /** Returns a mocked `SAP_Internal_Stats_LinkCard` @@ -17,7 +15,7 @@ extension SAP_Internal_Stats_LinkCard { static func mock( cardID: Int32 = 0, updatedAt: Int64 = 0, - url: String = Self.rkiPandemicRadarURL + url: String = Self.rkiPandemicRadarURLMock ) -> SAP_Internal_Stats_LinkCard { var cardHeader = SAP_Internal_Stats_CardHeader() cardHeader.cardID = cardID diff --git a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/HomeTestRegistrationCellModel.swift b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/HomeTestRegistrationCellModel.swift index ba118294397..52f3d3de4b7 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/HomeTestRegistrationCellModel.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/HomeTestRegistrationCellModel.swift @@ -9,10 +9,12 @@ class HomeTestRegistrationCellModel { // MARK: - Internal var title = AppStrings.Home.TestRegistration.title + var subtitle = AppStrings.Home.TestRegistration.subtitle var description = AppStrings.Home.TestRegistration.description var buttonTitle = AppStrings.Home.TestRegistration.button - var image = UIImage(named: "Illu_Hand_with_phone-initial") + var image = UIImage(named: "Illu_WarningAfterSelfTest") var tintColor: UIColor = .enaColor(for: .textPrimary1) - var accessibilityIdentifier = AccessibilityIdentifiers.Home.submitCardButton + var gradientViewType: GradientView.GradientType = .lightBlueToWhite + var buttonAccessibilityIdentifier = AccessibilityIdentifiers.Home.submitCardButton } diff --git a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/HomeTestRegistrationTableViewCell.swift b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/HomeTestRegistrationTableViewCell.swift index 24daa568e1d..9481d4c651e 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/HomeTestRegistrationTableViewCell.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/HomeTestRegistrationTableViewCell.swift @@ -20,6 +20,12 @@ final class HomeTestRegistrationTableViewCell: UITableViewCell { setup() } + + override func layoutSubviews() { + super.layoutSubviews() + + gradientView.roundCorners(corners: [.topLeft, .topRight], radius: 16) + } override func setHighlighted(_ highlighted: Bool, animated: Bool) { super.setHighlighted(highlighted, animated: animated) @@ -36,12 +42,14 @@ final class HomeTestRegistrationTableViewCell: UITableViewCell { // MARK: - Internal func configure(with cellModel: HomeTestRegistrationCellModel, onPrimaryAction: @escaping () -> Void) { + gradientView.type = cellModel.gradientViewType titleLabel.text = cellModel.title + subtitleLabel.text = cellModel.subtitle descriptionLabel.text = cellModel.description illustrationView.image = cellModel.image button.setTitle(cellModel.buttonTitle, for: .normal) - button.accessibilityIdentifier = cellModel.accessibilityIdentifier + button.accessibilityIdentifier = cellModel.buttonAccessibilityIdentifier self.tintColor = tintColor @@ -50,7 +58,9 @@ final class HomeTestRegistrationTableViewCell: UITableViewCell { // MARK: - Private + @IBOutlet private var gradientView: GradientView! @IBOutlet private var titleLabel: ENALabel! + @IBOutlet private var subtitleLabel: ENALabel! @IBOutlet private var descriptionLabel: ENALabel! @IBOutlet private var illustrationView: UIImageView! @IBOutlet private var button: ENAButton! @@ -67,9 +77,9 @@ final class HomeTestRegistrationTableViewCell: UITableViewCell { private func updateIllustration(for traitCollection: UITraitCollection) { if traitCollection.preferredContentSizeCategory >= .accessibilityLarge { - illustrationView.superview?.isHidden = true + illustrationView.isHidden = true } else { - illustrationView.superview?.isHidden = false + illustrationView.isHidden = false } } diff --git a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/HomeTestRegistrationTableViewCell.xib b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/HomeTestRegistrationTableViewCell.xib index 39a5b389584..d70b93f96eb 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/HomeTestRegistrationTableViewCell.xib +++ b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/HomeTestRegistrationTableViewCell.xib @@ -1,10 +1,12 @@ - + + - + + @@ -17,93 +19,96 @@ - + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + + - + + + + - - - - - - - - - + + + + + + - - - - + - + + + + + + + - + @@ -111,9 +116,9 @@ - - - + + + @@ -130,26 +135,31 @@ + + + + + - + - + - + - + @@ -162,5 +172,8 @@ + + + diff --git a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/__tests__/HomeTestRegistrationCellModelTests.swift b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/__tests__/HomeTestRegistrationCellModelTests.swift index 641278a6c29..aee20d03d03 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/__tests__/HomeTestRegistrationCellModelTests.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/Home/Cells/TestRegistration/__tests__/HomeTestRegistrationCellModelTests.swift @@ -13,10 +13,12 @@ class HomeTestRegistrationCellModelTests: CWATestCase { // THEN XCTAssertEqual(cellModel.title, AppStrings.Home.TestRegistration.title) + XCTAssertEqual(cellModel.subtitle, AppStrings.Home.TestRegistration.subtitle) XCTAssertEqual(cellModel.description, AppStrings.Home.TestRegistration.description) XCTAssertEqual(cellModel.buttonTitle, AppStrings.Home.TestRegistration.button) - XCTAssertEqual(cellModel.image, UIImage(named: "Illu_Hand_with_phone-initial")) - XCTAssertEqual(cellModel.accessibilityIdentifier, AccessibilityIdentifiers.Home.submitCardButton) + XCTAssertEqual(cellModel.image, UIImage(named: "Illu_WarningAfterSelfTest")) + XCTAssertEqual(cellModel.gradientViewType, .lightBlueToWhite) + XCTAssertEqual(cellModel.buttonAccessibilityIdentifier, AccessibilityIdentifiers.Home.submitCardButton) } } diff --git a/src/xcode/ENA/ENA/Source/Scenes/Home/HomeCoordinator.swift b/src/xcode/ENA/ENA/Source/Scenes/Home/HomeCoordinator.swift index 98c73fe062d..ad4bc988ff2 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/Home/HomeCoordinator.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/Home/HomeCoordinator.swift @@ -20,6 +20,7 @@ class HomeCoordinator: RequiresAppDependencies { healthCertificateService: HealthCertificateService, healthCertificateValidationService: HealthCertificateValidationProviding, elsService: ErrorLogSubmissionProviding, + srsService: SRSServiceProviding, exposureSubmissionService: ExposureSubmissionService, qrScannerCoordinator: QRScannerCoordinator, recycleBin: RecycleBin, @@ -38,6 +39,7 @@ class HomeCoordinator: RequiresAppDependencies { self.healthCertificateService = healthCertificateService self.healthCertificateValidationService = healthCertificateValidationService self.elsService = elsService + self.srsService = srsService self.exposureSubmissionService = exposureSubmissionService self.qrScannerCoordinator = qrScannerCoordinator self.recycleBin = recycleBin @@ -202,6 +204,7 @@ class HomeCoordinator: RequiresAppDependencies { private let healthCertificateService: HealthCertificateService private let healthCertificateValidationService: HealthCertificateValidationProviding private let exposureSubmissionService: ExposureSubmissionService + private let srsService: SRSServiceProviding private let qrScannerCoordinator: QRScannerCoordinator private let recycleBin: RecycleBin private let restServiceProvider: RestServiceProviding @@ -315,6 +318,7 @@ class HomeCoordinator: RequiresAppDependencies { parentViewController: rootViewController, exposureSubmissionService: exposureSubmissionService, coronaTestService: coronaTestService, + srsService: srsService, familyMemberCoronaTestService: familyMemberCoronaTestService, healthCertificateService: healthCertificateService, healthCertificateValidationService: healthCertificateValidationService, @@ -329,7 +333,7 @@ class HomeCoordinator: RequiresAppDependencies { if let testInformationResult = testInformationResult { coordinator.start(with: testInformationResult) } else { - coordinator.start(with: testType) + coordinator.start(with: .registeredTest(testType)) } } diff --git a/src/xcode/ENA/ENA/Source/Scenes/QRScanner/QRScannerCoordinator.swift b/src/xcode/ENA/ENA/Source/Scenes/QRScanner/QRScannerCoordinator.swift index e1c362db0b9..c70a049312c 100644 --- a/src/xcode/ENA/ENA/Source/Scenes/QRScanner/QRScannerCoordinator.swift +++ b/src/xcode/ENA/ENA/Source/Scenes/QRScanner/QRScannerCoordinator.swift @@ -33,6 +33,7 @@ class QRScannerCoordinator { eventStore: EventStoringProviding, appConfiguration: AppConfigurationProviding, eventCheckoutService: EventCheckoutService, + srsService: SRSServiceProviding, healthCertificateService: HealthCertificateService, healthCertificateValidationService: HealthCertificateValidationProviding, healthCertificateValidationOnboardedCountriesProvider: HealthCertificateValidationOnboardedCountriesProviding, @@ -48,6 +49,7 @@ class QRScannerCoordinator { self.eventStore = eventStore self.appConfiguration = appConfiguration self.eventCheckoutService = eventCheckoutService + self.srsService = srsService self.healthCertificateService = healthCertificateService self.healthCertificateValidationService = healthCertificateValidationService self.healthCertificateValidationOnboardedCountriesProvider = healthCertificateValidationOnboardedCountriesProvider @@ -87,6 +89,7 @@ class QRScannerCoordinator { private let eventStore: EventStoringProviding private let appConfiguration: AppConfigurationProviding private let eventCheckoutService: EventCheckoutService + private let srsService: SRSServiceProviding private let healthCertificateService: HealthCertificateService private let healthCertificateValidationService: HealthCertificateValidationProviding private let healthCertificateValidationOnboardedCountriesProvider: HealthCertificateValidationOnboardedCountriesProviding @@ -765,6 +768,7 @@ class QRScannerCoordinator { parentViewController: parentViewController, exposureSubmissionService: exposureSubmissionService, coronaTestService: coronaTestService, + srsService: srsService, familyMemberCoronaTestService: familyMemberCoronaTestService, healthCertificateService: healthCertificateService, healthCertificateValidationService: healthCertificateValidationService, diff --git a/src/xcode/ENA/ENA/Source/Services/CoronaTestService/SubmissionTestType.swift b/src/xcode/ENA/ENA/Source/Services/CoronaTestService/SubmissionTestType.swift new file mode 100644 index 00000000000..1233f7ed453 --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Services/CoronaTestService/SubmissionTestType.swift @@ -0,0 +1,38 @@ +// +// 🦠 Corona-Warn-App +// + +import Foundation + +enum SubmissionTestType: Equatable { + case registeredTest(CoronaTestType?) + case srs(SRSSubmissionType) +} + +// Wrapping SAP_Internal_SubmissionPayload.SubmissionType with SRSSubmissionType +// as it does not conform to protocol equatable +enum SRSSubmissionType: Equatable { + case srsRegisteredRat + case srsSelfTest + case srsRegisteredPcr + case srsUnregisteredPcr + case srsRapidPcr + case srsOther + + var protobufType: SAP_Internal_SubmissionPayload.SubmissionType { + switch self { + case .srsRegisteredRat: + return .srsRegisteredRat + case .srsSelfTest: + return .srsSelfTest + case .srsRegisteredPcr: + return .srsRegisteredPcr + case .srsUnregisteredPcr: + return .srsUnregisteredPcr + case .srsRapidPcr: + return .srsRapidPcr + case .srsOther: + return .srsOther + } + } +} diff --git a/src/xcode/ENA/ENA/Source/Services/Exposure Submission/ExposureSubmissionService+Protocol.swift b/src/xcode/ENA/ENA/Source/Services/Exposure Submission/ExposureSubmissionService+Protocol.swift index 5db8fa0af0c..003bc3a9a95 100644 --- a/src/xcode/ENA/ENA/Source/Services/Exposure Submission/ExposureSubmissionService+Protocol.swift +++ b/src/xcode/ENA/ENA/Source/Services/Exposure Submission/ExposureSubmissionService+Protocol.swift @@ -21,7 +21,11 @@ protocol ExposureSubmissionService: AnyObject { func loadSupportedCountries(isLoading: @escaping (Bool) -> Void, onSuccess: @escaping ([Country]) -> Void) func getTemporaryExposureKeys(completion: @escaping ExposureSubmissionHandler) func submitExposure(coronaTestType: CoronaTestType, completion: @escaping (_ error: ExposureSubmissionServiceError?) -> Void) - + func submitSRSExposure( + submissionType: SRSSubmissionType, + srsOTP: String, + completion: @escaping (_ error: ExposureSubmissionServiceError?) -> Void + ) } struct ExposureSubmissionServiceDependencies { diff --git a/src/xcode/ENA/ENA/Source/Services/Exposure Submission/ExposureSubmissionService.swift b/src/xcode/ENA/ENA/Source/Services/Exposure Submission/ExposureSubmissionService.swift index 01e5a28382b..f57bf6914bb 100644 --- a/src/xcode/ENA/ENA/Source/Services/Exposure Submission/ExposureSubmissionService.swift +++ b/src/xcode/ENA/ENA/Source/Services/Exposure Submission/ExposureSubmissionService.swift @@ -29,7 +29,8 @@ enum ExposureSubmissionServiceError: LocalizedError, Equatable { case coronaTestServiceError(CoronaTestServiceError) case keySubmissionError(ServiceError) case preconditionError(ExposureSubmissionServicePreconditionError) - + case srsError(SRSError) + var errorDescription: String? { switch self { case .coronaTestServiceError(let error): @@ -38,6 +39,8 @@ enum ExposureSubmissionServiceError: LocalizedError, Equatable { return error.errorDescription case .preconditionError(let error): return error.errorDescription + case .srsError(let error): + return error.description } } } @@ -151,6 +154,81 @@ class ENAExposureSubmissionService: ExposureSubmissionService { } } + /// This method submits the SRS exposure keys. Additionally, after successful completion, + /// the timestamp of the key submission is updated. + func submitSRSExposure( + submissionType: SRSSubmissionType, + srsOTP: String, + completion: @escaping (_ error: ExposureSubmissionServiceError?) -> Void + ) { + Log.info("Started SRS exposure submission...", log: .api) + + guard let keys = temporaryExposureKeys else { + Log.info("Cancelled SRS exposure: No temporary exposure keys to submit.", log: .api) + completion(.preconditionError(.keysNotShared)) + return + } + + guard !keys.isEmpty || !checkins.isEmpty else { + Log.info("Cancelled SRS exposure: No temporary exposure keys or checkins to submit.", log: .api) + completion(.preconditionError(.noKeysCollected)) + + // We perform a cleanup in order to set the correct + // timestamps, despite not having communicated with the backend, + // in order to show the correct screens. + self.submitExposureCleanup(submissionTestType: .srs(submissionType)) + return + } + + // we need the app configuration first… + appConfigurationProvider + .appConfiguration() + .sink { [weak self] appConfig in + guard let self = self else { + Log.error("Failed to create strong self") + return + } + // Fetch & process keys and checkins + let processedKeys = keys.processedForSubmission( + with: self.symptomsOnset + ) + + let unencryptedCheckinsEnabled = self.appConfigurationProvider.featureProvider.boolValue(for: .unencryptedCheckinsEnabled) + + var unencryptedCheckins = [SAP_Internal_Pt_CheckIn]() + if unencryptedCheckinsEnabled { + unencryptedCheckins = self.checkins.preparedForSubmission( + appConfig: appConfig, + transmissionRiskLevelSource: .symptomsOnset(self.symptomsOnset) + ) + } + + let checkinProtectedReports = self.checkins.preparedProtectedReportsForSubmission( + appConfig: appConfig, + transmissionRiskLevelSource: .symptomsOnset(self.symptomsOnset) + ) + + // Request needs to be prepended by the fake request Playbook for srs. + + self._submitSRS( + processedKeys, + srsOtp: srsOTP, + submissionType: submissionType, + visitedCountries: self.supportedCountries, + checkins: unencryptedCheckins, + checkInProtectedReports: checkinProtectedReports, + completion: { error in + if let error = error { + completion(.keySubmissionError(error)) + } else { + completion(nil) + } + } + ) + } + .store(in: &subscriptions) + } + /// This method submits the exposure keys. Additionally, after successful completion, /// the timestamp of the key submission is updated. /// __Extension for plausible deniability__: @@ -192,7 +270,7 @@ class ENAExposureSubmissionService: ExposureSubmissionService { // We perform a cleanup in order to set the correct // timestamps, despite not having communicated with the backend, // in order to show the correct screens. - submitExposureCleanup(coronaTestType: coronaTest.type) + submitExposureCleanup(submissionTestType: .registeredTest(coronaTest.type)) return } @@ -291,7 +369,7 @@ class ENAExposureSubmissionService: ExposureSubmissionService { case let .failure(error): completion(.coronaTestServiceError(error)) case let .success(tan): - self._submit( + self._submitRegularSubmission( keys, coronaTest: coronaTest, with: tan, @@ -309,10 +387,48 @@ class ENAExposureSubmissionService: ExposureSubmissionService { } } + /// Helper method that handles only the submission of the keys for SRS. Use this only if you really just want to do the + /// part of the submission flow in which the keys are submitted. + /// For more information, please check _submitExposure(). + private func _submitSRS( + _ keys: [SAP_External_Exposurenotification_TemporaryExposureKey], + srsOtp: String, + submissionType: SRSSubmissionType, + visitedCountries: [Country], + checkins: [SAP_Internal_Pt_CheckIn], + checkInProtectedReports: [SAP_Internal_Pt_CheckInProtectedReport], + completion: @escaping (_ error: ServiceError?) -> Void + ) { + let payload = SubmissionPayload( + exposureKeys: keys, + visitedCountries: visitedCountries, + checkins: checkins, + checkinProtectedReports: checkInProtectedReports, + tan: nil, + submissionType: submissionType.protobufType + ) + + let resource = SRSKeySubmissionResource(payload: payload, srsOtp: srsOtp) + restServiceProvider.load(resource) { result in + + switch result { + case .success: + self.submitExposureCleanup(submissionTestType: .srs(submissionType)) + + Log.info("Successfully completed SRS exposure submission.", log: .api) + completion(nil) + case .failure(let error): + Log.error("Error while submitting SRS diagnosis keys: \(error.localizedDescription)", log: .api) + + completion(error) + } + } + } + /// Helper method that handles only the submission of the keys. Use this only if you really just want to do the /// part of the submission flow in which the keys are submitted. /// For more information, please check _submitExposure(). - private func _submit( + private func _submitRegularSubmission( _ keys: [SAP_External_Exposurenotification_TemporaryExposureKey], coronaTest: UserCoronaTest, with tan: String, @@ -344,7 +460,7 @@ class ENAExposureSubmissionService: ExposureSubmissionService { Analytics.collect(.keySubmissionMetadata(.submittedWithCheckins(true, coronaTest.type))) } - self.submitExposureCleanup(coronaTestType: coronaTest.type) + self.submitExposureCleanup(submissionTestType: .registeredTest(coronaTest.type)) Log.info("Successfully completed exposure submission.", log: .api) completion(nil) @@ -357,18 +473,29 @@ class ENAExposureSubmissionService: ExposureSubmissionService { } /// This method removes all left over persisted objects part of the `submitExposure` flow. - private func submitExposureCleanup(coronaTestType: CoronaTestType) { - switch coronaTestType { - case .pcr: - coronaTestService.pcrTest.value?.keysSubmitted = true - coronaTestService.pcrTest.value?.submissionTAN = nil - case .antigen: - coronaTestService.antigenTest.value?.keysSubmitted = true - coronaTestService.antigenTest.value?.submissionTAN = nil - } + private func submitExposureCleanup(submissionTestType: SubmissionTestType) { + switch submissionTestType { + case .registeredTest(let coronaTestType): + guard let coronaTestType = coronaTestType else { + Log.error("Corona test type is nil, case should not be possible") + return + } + switch coronaTestType { + case .pcr: + coronaTestService.pcrTest.value?.keysSubmitted = true + coronaTestService.pcrTest.value?.submissionTAN = nil + case .antigen: + coronaTestService.antigenTest.value?.keysSubmitted = true + coronaTestService.antigenTest.value?.submissionTAN = nil + } - /// Deactivate deadman notification while submitted test is still present - deadmanNotificationManager.resetDeadmanNotification() + /// Deactivate deadman notification while submitted test is still present + deadmanNotificationManager.resetDeadmanNotification() + + case .srs: + // No cleanup needed as we don't store SRS + break + } temporaryExposureKeys = nil diff --git a/src/xcode/ENA/ENA/Source/Services/OTP/Model/OTPError.swift b/src/xcode/ENA/ENA/Source/Services/OTP/Model/OTPError.swift index 86d44b1ea13..247df6721f7 100644 --- a/src/xcode/ENA/ENA/Source/Services/OTP/Model/OTPError.swift +++ b/src/xcode/ENA/ENA/Source/Services/OTP/Model/OTPError.swift @@ -18,42 +18,42 @@ enum OTPError: Error, Equatable, LocalizedError { case deviceTokenSyntaxError case noNetworkConnection - case restServiceError(ServiceError) + case restServiceError(ServiceError) - var description: String { - switch self { - case .generalError(let error): - if let e = error?.localizedDescription { - return "generalError with underlying: \(e)" - } else { - return "generalError" - } - case .invalidResponseError: - return "invalidResponseError" - case .internalServerError: - return "internalServerError" - case .otpAlreadyUsedThisMonth: - return "otpAlreadyUsedThisMonth" - case .otherServerError: - return "otherServerError" - case .apiTokenAlreadyIssued: - return "apiTokenAlreadyIssued" - case .apiTokenExpired: - return "apiTokenExpired" - case .apiTokenQuotaExceeded: - return "apiTokenQuotaExceeded" - case .deviceTokenInvalid: - return "deviceTokenInvalid" - case .deviceTokenRedeemed: - return "deviceTokenRedeemed" - case .deviceTokenSyntaxError: - return "deviceTokenSyntaxError" - case .noNetworkConnection: - return "noNetworkConnection" - case .restServiceError: - return "restServiceError" - } - } + var description: String { + switch self { + case .generalError(let error): + if let e = error?.localizedDescription { + return "generalError with underlying: \(e)" + } else { + return "generalError" + } + case .invalidResponseError: + return "invalidResponseError" + case .internalServerError: + return "internalServerError" + case .otpAlreadyUsedThisMonth: + return "otpAlreadyUsedThisMonth" + case .otherServerError: + return "otherServerError" + case .apiTokenAlreadyIssued: + return "apiTokenAlreadyIssued" + case .apiTokenExpired: + return "apiTokenExpired" + case .apiTokenQuotaExceeded: + return "apiTokenQuotaExceeded" + case .deviceTokenInvalid: + return "deviceTokenInvalid" + case .deviceTokenRedeemed: + return "deviceTokenRedeemed" + case .deviceTokenSyntaxError: + return "deviceTokenSyntaxError" + case .noNetworkConnection: + return "noNetworkConnection" + case .restServiceError: + return "restServiceError" + } + } var errorDescription: String? { switch self { diff --git a/src/xcode/ENA/ENA/Source/Services/OTP/OTPService.swift b/src/xcode/ENA/ENA/Source/Services/OTP/OTPService.swift index 59fd5226dd6..e936e584e1b 100644 --- a/src/xcode/ENA/ENA/Source/Services/OTP/OTPService.swift +++ b/src/xcode/ENA/ENA/Source/Services/OTP/OTPService.swift @@ -27,10 +27,25 @@ protocol OTPServiceProviding { /// - success: the authorized and stored otp as String /// - failure: an OTPError, for which the caller can build a dedicated error handling func getOTPEls(ppacToken: PPACToken, completion: @escaping (Result) -> Void) + + /// Checks if there is a valid stored otp for SRS. If so, we check if we can reuse it because it was not already used, or if it was already used. If so, we return a failure. If there is not a stored otp els token, or if the stored token's expiration date is reached, a new fresh otp token is generated and stored. + /// After these validation checks, the service tries to authorize the otp against the server. + /// - Parameters: + /// - ppacToken: a generated and valid PPACToken from the PPACService + /// - completion: The completion handler + /// - Returns: + /// - success: the authorized and stored otp as String + /// - failure: an OTPError, for which the caller can build a dedicated error handling + func getOTPSrs(ppacToken: PPACToken, completion: @escaping (Result) -> Void) + /// discards any stored otp edus. func discardOTPEdus() + /// discards any stored otp els. func discardOTPEls() + + /// discards any stored otp srs. + func discardOTPSrs() } final class OTPService: OTPServiceProviding { @@ -95,6 +110,20 @@ final class OTPService: OTPServiceProviding { authorizeEls(otp, with: ppacToken, completion: completion) } + func getOTPSrs(ppacToken: PPACToken, completion: @escaping (Result) -> Void) { + if let otpToken = store.otpTokenSrs, + let expirationDate = otpToken.expirationDate, + expirationDate > Date(), + store.otpElsAuthorizationDate == nil { + Log.info("Existing OTP ELS was not consumed before and can be used for submission.", log: .otp) + completion(.success(otpToken.token)) + return + } + Log.info("No existing or valid OTP ELS was found. Generating new one.", log: .otp) + let otp = generateOTPToken() + authorizeSRS(otp, with: ppacToken, completion: completion) + } + func discardOTPEdus() { store.otpTokenEdus = nil Log.info("OTP EDUS was discarded.", log: .otp) @@ -104,6 +133,11 @@ final class OTPService: OTPServiceProviding { store.otpTokenEls = nil Log.info("OTP ELS was discarded.", log: .otp) } + + func discardOTPSrs() { + store.otpTokenSrs = nil + Log.info("OTP SRS was discarded.", log: .otp) + } // MARK: - Private @@ -193,4 +227,37 @@ final class OTPService: OTPServiceProviding { } } } + + private func authorizeSRS(_ otp: String, with ppacToken: PPACToken, completion: @escaping (Result) -> Void) { + Log.info("Authorization of a new OTP SRS started.", log: .otp) + // We authorize the otp with the ppac Token at our server. + let resource = OTPAuthorizationForSRSResource(otpSRS: otp, ppacToken: ppacToken) + restServiceProvider.load(resource) { [weak self] result in + guard let self = self else { + Log.error("could not create strong self", log: .otp) + completion(.failure(OTPError.generalError(underlyingError: nil))) + return + } + + switch result { + case .success(let expirationDate): + // Success: We store the authorized otp with timestamp and return the token. + let verifiedToken = OTPToken( + token: otp, + timestamp: Date(), + expirationDate: expirationDate.expirationDate + ) + + self.store.otpTokenEls = verifiedToken + + Log.info("A new OTP ELS was authorized and persisted.", log: .otp) + + completion(.success(verifiedToken.token)) + case .failure(let error): + + Log.error("Authorization of a new OTP SRS failed with error: \(error)", log: .otp) + completion(.failure(.restServiceError(error))) + } + } + } } diff --git a/src/xcode/ENA/ENA/Source/Services/PPAccessControl/Model/PPACError.swift b/src/xcode/ENA/ENA/Source/Services/PPAccessControl/Model/PPACError.swift index 537b1282fff..ce80aeb61f7 100644 --- a/src/xcode/ENA/ENA/Source/Services/PPAccessControl/Model/PPACError.swift +++ b/src/xcode/ENA/ENA/Source/Services/PPAccessControl/Model/PPACError.swift @@ -9,17 +9,24 @@ enum PPACError: Error { case deviceNotSupported case timeIncorrect case timeUnverified - + case minimumTimeSinceOnboarding + case submissionTooEarly + var description: String { switch self { case .generationFailed: - return "deviceCheck Token generation failed" + return "DEVICE_TOKEN_GENERATION_FAILED" case .deviceNotSupported: - return "deviceNotSupported" + return "DEVICE_TOKEN_NOT_SUPPORTED" case .timeIncorrect: - return "timeIncorrect" + return "DEVICE_TIME_INCORRECT" case .timeUnverified: - return "timeUnverified" + return "DEVICE_TIME_UNVERIFIED" + case .minimumTimeSinceOnboarding: + return "TIME_SINCE_ONBOARDING_UNVERIFIED" + case .submissionTooEarly: + return "SUBMISSION_TOO_EARLY" + } } } diff --git a/src/xcode/ENA/ENA/Source/Services/PPAccessControl/Model/SRSError.swift b/src/xcode/ENA/ENA/Source/Services/PPAccessControl/Model/SRSError.swift new file mode 100644 index 00000000000..dcd8fddac2f --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Services/PPAccessControl/Model/SRSError.swift @@ -0,0 +1,54 @@ +// +// 🦠 Corona-Warn-App +// + +import Foundation + +enum SRSError: Error, Equatable { + case ppacError(PPACError) + case otpError(OTPError) + + case srsOTPClientError + case srsOTPNetworkError + case srsOTPServerError + case srsOTP400 + case srsOTP401 + case srsOTP403 + + case srsSUBClientError + case srsSUBNoNetwork + case srsSUBServerError + case srsSUB400 + case srsSUB403 + + var description: String { + switch self { + case .ppacError(let ppacError): + return "ppacError: \(ppacError.description)" + case .otpError(let otpError): + return "otpError: \(otpError.description)" + case .srsOTPClientError: + return "SRS_OTP_CLIENT_ERROR" + case .srsOTPNetworkError: + return "SRS_OTP_NO_NETWORK" + case .srsOTPServerError: + return "SRS_OTP_SERVER_ERROR" + case .srsOTP400: + return "SRS_OTP_400" + case .srsOTP401: + return "SRS_OTP_401" + case .srsOTP403: + return "SRS_OTP_403" + case .srsSUBClientError: + return "SRS_SUB_CLIENT_ERROR" + case .srsSUBNoNetwork: + return "SRS_SUB_NO_NETWORK" + case .srsSUBServerError: + return "SRS_SUB_SERVER_ERROR" + case .srsSUB400: + return "SRS_SUB_400" + case .srsSUB403: + return "SRS_SUB_403" + } + } +} diff --git a/src/xcode/ENA/ENA/Source/Services/PPAccessControl/PPACService.swift b/src/xcode/ENA/ENA/Source/Services/PPAccessControl/PPACService.swift index 837aa049a7d..9c4cd15fc7a 100644 --- a/src/xcode/ENA/ENA/Source/Services/PPAccessControl/PPACService.swift +++ b/src/xcode/ENA/ENA/Source/Services/PPAccessControl/PPACService.swift @@ -7,6 +7,11 @@ import Foundation protocol PrivacyPreservingAccessControl { func getPPACTokenEDUS(_ completion: @escaping (Result) -> Void) func getPPACTokenELS(_ completion: @escaping (Result) -> Void) + func getPPACTokenSRS( + minTimeSinceOnboardingInHours: Int, + minTimeBetweenSubmissionsInDays: Int, + completion: @escaping (Result) -> Void + ) #if !RELEASE func generateNewAPIEdusToken() -> TimestampedToken func generateNewAPIElsToken() -> TimestampedToken @@ -27,6 +32,59 @@ class PPACService: PrivacyPreservingAccessControl { // MARK: - Protocol PrivacyPreservingAccessControl + func getPPACTokenSRS( + minTimeSinceOnboardingInHours: Int, + minTimeBetweenSubmissionsInDays: Int, + completion: @escaping (Result) -> Void + ) { + // check if time isn't incorrect + if store.deviceTimeCheckResult == .incorrect { + Log.error("SRSError: device time is incorrect", log: .ppac) + completion(.failure(PPACError.timeIncorrect)) + return + } + + // check if time isn't unknown + if store.deviceTimeCheckResult == .assumedCorrect { + Log.error("SRSError:device time is unverified", log: .ppac) + completion(.failure(PPACError.timeUnverified)) + return + } + + // we have two parameters from the appconfig for prechecks: + // 1- a minimum number of hours since onboarding until user can self submit result. + // 2- a minimum number of days since last submission user can self submit result again. + + // 1- Check FIRST_RELIABLE_TIMESTAMP + if let firstReliableTimeStamp = store.firstReliableTimeStamp, + let difference = Calendar.current.dateComponents([.hour], from: firstReliableTimeStamp, to: Date()).hour { + let minTimeSinceOnboarding = minTimeSinceOnboardingInHours <= 0 ? 24 : minTimeSinceOnboardingInHours + Log.debug("actual time since onboarding \(minTimeSinceOnboardingInHours) hours.", log: .ppac) + Log.debug("Corrected default time since onboarding \(minTimeSinceOnboarding) hours.", log: .ppac) + + if difference < minTimeSinceOnboarding { + Log.error("SRSError: too short time since onboarding", log: .ppac) + completion(.failure(PPACError.minimumTimeSinceOnboarding)) + return + } + } + + // 2- Check time since previous submission + if let mostRecentKeySubmissionDate = store.mostRecentKeySubmissionDate, + let difference = Calendar.current.dateComponents([.day], from: mostRecentKeySubmissionDate, to: Date()).day { + let minTimeBetweenSubmissions = minTimeBetweenSubmissionsInDays <= 0 ? 90 : minTimeBetweenSubmissionsInDays + Log.debug("minTimeBetweenSubmissionsInDays = \(minTimeBetweenSubmissionsInDays) days.", log: .ppac) + Log.debug("Corrected default minTimeBetweenSubmissionsInDays = \(minTimeBetweenSubmissions) days.", log: .ppac) + + if difference < minTimeBetweenSubmissions { + Log.error("SRSError: submission too early", log: .ppac) + completion(.failure(PPACError.submissionTooEarly)) + return + } + } + deviceCheck.deviceToken(apiTokenSRS.token, completion: completion) + } + func getPPACTokenEDUS(_ completion: @escaping (Result) -> Void) { // check if time isn't incorrect @@ -54,7 +112,7 @@ class PPACService: PrivacyPreservingAccessControl { } func getPPACTokenELS(_ completion: @escaping (Result) -> Void) { - // no divide time checks for ELS + // no device time checks for ELS deviceCheck.deviceToken(apiTokenELS.token, completion: completion) } @@ -103,6 +161,16 @@ class PPACService: PrivacyPreservingAccessControl { return storedToken } + private var apiTokenSRS: TimestampedToken { + guard let storedToken = store.ppacApiTokenSrs + else { + let newToken = generateAndStoreFreshAPIToken() + store.ppacApiTokenEls = newToken + return newToken + } + return storedToken + } + /// generate a new API Token and store it private func generateAndStoreFreshAPIToken() -> TimestampedToken { let uuid = UUID().uuidString diff --git a/src/xcode/ENA/ENA/Source/Services/Risk/Provider/Helper/DeviceTimeCheck.swift b/src/xcode/ENA/ENA/Source/Services/Risk/Provider/Helper/DeviceTimeCheck.swift index 6e3f51ea235..30a32d6485b 100644 --- a/src/xcode/ENA/ENA/Source/Services/Risk/Provider/Helper/DeviceTimeCheck.swift +++ b/src/xcode/ENA/ENA/Source/Services/Risk/Provider/Helper/DeviceTimeCheck.swift @@ -31,11 +31,15 @@ final class DeviceTimeCheck: DeviceTimeChecking { func updateDeviceTimeFlags(serverTime: Date, deviceTime: Date, configUpdateSuccessful: Bool) { let oldState = store.deviceTimeCheckResult - store.deviceTimeCheckResult = isDeviceTimeCorrect( + let isDeviceTimeCorrect = isDeviceTimeCorrect( serverTime: serverTime, deviceTime: deviceTime, configUpdateSuccessful: configUpdateSuccessful ) + if isDeviceTimeCorrect == .correct && store.firstReliableTimeStamp == nil { + store.firstReliableTimeStamp = Date() + } + store.deviceTimeCheckResult = isDeviceTimeCorrect // store change date only if a state change was detected if oldState != store.deviceTimeCheckResult { store.deviceTimeLastStateChange = Date() diff --git a/src/xcode/ENA/ENA/Source/Services/SRSService/MockSrsService.swift b/src/xcode/ENA/ENA/Source/Services/SRSService/MockSrsService.swift new file mode 100644 index 00000000000..accf30abcaf --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Services/SRSService/MockSrsService.swift @@ -0,0 +1,11 @@ +// +// 🦠 Corona-Warn-App +// + +import Foundation + +class MockSRSService: SRSServiceProviding { + func authenticate(completion: @escaping SRSAuthenticationResponse) { + completion(.success("TEST")) + } +} diff --git a/src/xcode/ENA/ENA/Source/Services/SRSService/SRSService.swift b/src/xcode/ENA/ENA/Source/Services/SRSService/SRSService.swift new file mode 100644 index 00000000000..68056d1087e --- /dev/null +++ b/src/xcode/ENA/ENA/Source/Services/SRSService/SRSService.swift @@ -0,0 +1,81 @@ +// +// 🦠 Corona-Warn-App +// + + +import Foundation +import OpenCombine + +protocol SRSServiceProviding { + + typealias SRSAuthenticationResponse = (Result) -> Void + + func authenticate(completion: @escaping SRSAuthenticationResponse) +} + +final class SRSService: SRSServiceProviding { + + // MARK: - Init + + init( + restServicerProvider: RestServiceProviding, + store: SRSProviding, + ppacService: PrivacyPreservingAccessControl, + otpService: OTPServiceProviding, + configurationProvider: AppConfigurationProviding + ) { + self.restServicerProvider = restServicerProvider + self.store = store + self.ppacService = ppacService + self.otpService = otpService + self.configurationProvider = configurationProvider + } + + // MARK: - Protocol SRSSSubmitting + + func authenticate(completion: @escaping SRSAuthenticationResponse) { + // first get ppac token for SRS + configurationProvider.appConfiguration().sink { appConfiguration in + let timeSinceOnboardingInHours = Int(appConfiguration.selfReportParameters.common.timeSinceOnboardingInHours) + let timeBetweenSubmissionsInDays = Int(appConfiguration.selfReportParameters.common.timeBetweenSubmissionsInDays) + self.ppacService.getPPACTokenSRS( + minTimeSinceOnboardingInHours: timeSinceOnboardingInHours, + minTimeBetweenSubmissionsInDays: timeBetweenSubmissionsInDays, + completion: { [weak self] result in + guard let self = self else { return } + switch result { + case let .success(ppacToken): + Log.debug("Successfully retrieved for SRS a ppac token. Proceed for otp.") + // then get otp token for SRS (without restrictions for api token) + self.otpService.getOTPEls(ppacToken: ppacToken) { result in + switch result { + case let .success(otpSRS): + Log.debug("Successfully authenticated ppac and SRS OTP: \(private: otpSRS, public: "--OTP Value--") for els. Proceed with uploading error log file.") + + // now we can submit our log with valid otp. + completion(.success(otpSRS)) + case let .failure(otpError): + Log.error("Could not obtain otp for srs.", log: .els, error: otpError) + completion(.failure(.otpError(otpError))) + } + } + case let .failure(ppacError): + Log.error("Could not obtain ppac token for srs.", log: .srs, error: ppacError) + completion(.failure(.ppacError(ppacError))) + } + }) + + }.store(in: &subscriptions) + } + + + // MARK: - Private + + private let restServicerProvider: RestServiceProviding + private let store: SRSProviding + private let ppacService: PrivacyPreservingAccessControl + private let otpService: OTPServiceProviding + private let configurationProvider: AppConfigurationProviding + private var subscriptions = [AnyCancellable]() + +} diff --git a/src/xcode/ENA/ENA/Source/Services/__tests__/Mocks/MockTestStore.swift b/src/xcode/ENA/ENA/Source/Services/__tests__/Mocks/MockTestStore.swift index d6418a3e159..34b661a159e 100644 --- a/src/xcode/ENA/ENA/Source/Services/__tests__/Mocks/MockTestStore.swift +++ b/src/xcode/ENA/ENA/Source/Services/__tests__/Mocks/MockTestStore.swift @@ -8,7 +8,6 @@ import OpenCombine #if !RELEASE final class MockTestStore: Store, PPAnalyticsData { - init() { #if DEBUG if isUITesting { @@ -52,7 +51,8 @@ final class MockTestStore: Store, PPAnalyticsData { var submissionSymptomsOnset: SymptomsOnset = .noInformation var journalWithExposureHistoryInfoScreenShown: Bool = false var lastBoosterNotificationsExecutionDate: Date? - + var mostRecentKeySubmissionDate: Date? + var firstReliableTimeStamp: Date? func wipeAll(key: String?) {} #if !RELEASE // Settings from the debug menu. @@ -83,12 +83,20 @@ final class MockTestStore: Store, PPAnalyticsData { var selectedLocalStatisticsRegions: [LocalStatisticsRegion] = [] + // MARK: - SRS Providing + + var ppacApiTokenSrs: TimestampedToken? + var previousPpacApiTokenSrs: TimestampedToken? + var otpTokenSrs: OTPToken? + var otpSrsAuthorizationDate: Date? + // MARK: - PrivacyPreservingProviding var isPrivacyPreservingAnalyticsConsentGiven: Bool = false var otpTokenEdus: OTPToken? var otpEdusAuthorizationDate: Date? var ppacApiTokenEdus: TimestampedToken? + var previousPpacApiTokenEdus: TimestampedToken? var userData: UserMetadata? // MARK: - PPAnalyticsData @@ -116,6 +124,7 @@ final class MockTestStore: Store, PPAnalyticsData { var lastLoggedAppVersionNumber: Version? var lastLoggedAppVersionTimestamp: Date? var ppacApiTokenEls: TimestampedToken? + var previousPpacApiTokenEls: TimestampedToken? var otpTokenEls: OTPToken? var otpElsAuthorizationDate: Date? #if !RELEASE @@ -223,7 +232,5 @@ final class MockTestStore: Store, PPAnalyticsData { // MARK: - KeyValueCacheStoring var keyValueCacheVersion: Int = 0 - } - #endif diff --git a/src/xcode/ENA/ENA/Source/View Helpers/AccessibilityIdentifiers.swift b/src/xcode/ENA/ENA/Source/View Helpers/AccessibilityIdentifiers.swift index 3ad7a885bfa..ad648d273dc 100644 --- a/src/xcode/ENA/ENA/Source/View Helpers/AccessibilityIdentifiers.swift +++ b/src/xcode/ENA/ENA/Source/View Helpers/AccessibilityIdentifiers.swift @@ -104,13 +104,20 @@ enum AccessibilityIdentifiers { static let firstBulletPoint = "AccessibilityIdentifiers.Home.pcrCell.Positive.Item0" static let secondBulletPoint = "AccessibilityIdentifiers.Home.pcrCell.Positive.Item1" static let thirdBulletPoint = "AccessibilityIdentifiers.Home.pcrCell.Positive.Item2" - + static let fourthBulletPoint = "AccessibilityIdentifiers.Home.pcrCell.Positive.Item3" } enum RAT { static let antigenCell = "AccessibilityIdentifiers.Home.antigenCell" static let firstBulletPoint = "AccessibilityIdentifiers.Home.ratCell.Positive.Item0" static let secondBulletPoint = "AccessibilityIdentifiers.Home.ratCell.Positive.Item1" static let thirdBulletPoint = "AccessibilityIdentifiers.Home.ratCell.Positive.Item2" + static let fourthBulletPoint = "AccessibilityIdentifiers.Home.ratCell.Positive.Item3" + } + enum SRS { + static let firstBulletPoint = "AccessibilityIdentifiers.Home.srsCell.Positive.Item0" + static let secondBulletPoint = "AccessibilityIdentifiers.Home.srsCell.Positive.Item1" + static let thirdBulletPoint = "AccessibilityIdentifiers.Home.srsCell.Positive.Item2" + static let fourthBulletPoint = "AccessibilityIdentifiers.Home.srsCell.Positive.Item3" } static let submittedPCRCell = "AccessibilityIdentifiers.Home.submittedPCRCell" static let submittedAntigenCell = "AccessibilityIdentifiers.Home.submittedAntigenCell" @@ -341,7 +348,19 @@ enum AccessibilityIdentifiers { static let photo = "FileScanner_Sheet_Photo_Button" static let file = "FileScanner_Sheet_File_Button" } + + enum SRSConsentScreen { + static let primaryButton = "AppStrings.SRSConsentScreen.primaryButton" + static let accImageDescription = "AppStrings.SRSConsentScreen.accImageDescription" + static let headerSection1 = "AppStrings.SRSConsentScreen.headerSection1" + static let acknowledgementTitle = "AppStrings.SRSConsentScreen.acknowledgementTitle" + static let dataProcessingDetailInfoButton = "AppStrings.SRSConsentScreen.dataProcessingDetailInfoButton" + } + enum SRSDataProcessingDetailInfo { + static let content = "AppStrings.SRSConsentScreen.dataProcessingDetailInfo" + } + enum ExposureSubmissionQRInfo { static let headerSection1 = "AppStrings.ExposureSubmissionQRInfo.headerSection1" static let headerSection2 = "AppStrings.ExposureSubmissionQRInfo.headerSection2" @@ -355,9 +374,9 @@ enum AccessibilityIdentifiers { static let sectionHeadline = "AppStrings.ExposureSubmission_DispatchSectionHeadline" static let sectionHeadline2 = "AppStrings.ExposureSubmission_DispatchSectionHeadline2" static let qrCodeButtonDescription = "AppStrings.ExposureSubmissionDispatch.qrCodeButtonDescription" - static let tanButtonDescription = "AppStrings.ExposureSubmissionDispatch.tanButtonDescription" + static let positiveSelfTestButtonDescription = "AppStrings.ExposureSubmissionDispatch.positiveSelfTestButtonDescription" static let tanInputView = "AppStrings.ExposureSubmissionDispatch.TaninputView" - static let hotlineButtonDescription = "AppStrings.ExposureSubmissionDispatch.hotlineButtonDescription" + static let SRSButtonDescription = "AppStrings.ExposureSubmissionDispatch.SRSButtonDescription" static let findTestCentersButtonDescription = "AppStrings.ExposureSubmissionDispatch.findTestCentersButtonDescription" } @@ -571,6 +590,16 @@ enum AccessibilityIdentifiers { } + enum SRSTestTypeSelection { + static let body = "AppStrings.ExposureSubmission.SRSTestTypeSelection.body" + static let description = "AppStrings.ExposureSubmission.SRSTestTypeSelection.description" + static let optionSRSSelfTest = "AppStrings.ExposureSubmission.SRSTestTypeSelection.submissionType.srsSelfTest" + static let optionSRSRegisteredRat = "AppStrings.ExposureSubmission.SRSTestTypeSelection.submissionType.srsRegisteredRat" + static let optionSRSRegisteredPcr = "AppStrings.ExposureSubmission.SRSTestTypeSelection.submissionType.srsRegisteredPcr" + static let optionSRSUnregisteredPcr = "AppStrings.ExposureSubmission.SRSTestTypeSelection.submissionType.srsUnregisteredPcr" + static let optionSRSRapidPcr = "AppStrings.ExposureSubmission.SRSTestTypeSelection.submissionType.srsRapidPcr" + static let optionSRSOther = "AppStrings.ExposureSubmission.SRSTestTypeSelection.submissionType.srsOther" + } } enum ExposureSubmissionTestResultConsent { diff --git a/src/xcode/ENA/ENA/Source/View Helpers/AppStrings.swift b/src/xcode/ENA/ENA/Source/View Helpers/AppStrings.swift index f721b946715..2189472de26 100644 --- a/src/xcode/ENA/ENA/Source/View Helpers/AppStrings.swift +++ b/src/xcode/ENA/ENA/Source/View Helpers/AppStrings.swift @@ -182,6 +182,23 @@ enum AppStrings { } } + + enum SRSTestTypeSelection { + static let title = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_title", comment: "") + static let body = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_body", comment: "") + static let description = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_description", comment: "") + static let optionSRSSelfTestTitle = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_optionSRSSelfTest_title", comment: "") + static let optionSRSRegisteredRatTitle = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_optionSRSRegisteredRat_title", comment: "") + static let optionSRSRegisteredPcrTitle = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_optionSRSRegisteredPcr_title", comment: "") + static let optionSRSUnregisteredPcrTitle = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_optionSRSUnregisteredPcr_title", comment: "") + static let optionSRSRapidPcrTitle = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_optionSRSRapidPcr_title", comment: "") + static let optionSRSOtherTitle = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_optionSRSOther_title", comment: "") + static let warnProcessCancelAlertTitle = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_warnProcessCancelAlert_Title", comment: "") + static let warnProcessCancelAlertMessage = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_warnProcessCancelAlert_Message", comment: "") + static let warnProcessCancelAlertActionContinue = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_warnProcessCancelAlert_ActionContinue", comment: "") + static let warnProcessCancelAlertActionCancel = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_warnProcessCancelAlert_ActionCancel", comment: "") + static let primaryButtonTitle = NSLocalizedString("ExposureSubmission_SRSTestTypeSelection_primaryButtonTitle", comment: "") + } } enum ExposureSubmissionTanEntry { @@ -315,14 +332,45 @@ enum AppStrings { static let sectionHeadline2 = NSLocalizedString("ExposureSubmission_DispatchSectionHeadline2", comment: "") static let qrCodeButtonTitle = NSLocalizedString("ExposureSubmissionDispatch_QRCodeButtonTitle", comment: "") static let qrCodeButtonDescription = NSLocalizedString("ExposureSubmissionDispatch_QRCodeButtonDescription", comment: "") - static let tanButtonTitle = NSLocalizedString("ExposureSubmissionDispatch_TANButtonTitle", comment: "") - static let tanButtonDescription = NSLocalizedString("ExposureSubmissionDispatch_TANButtonDescription", comment: "") - static let hotlineButtonTitle = NSLocalizedString("ExposureSubmissionDispatch_HotlineButtonTitle", comment: "") - static let hotlineButtonDescription = NSLocalizedString("ExposureSubmissionDispatch_HotlineButtonDescription", comment: "") + static let positiveSelfTestButtonTitle = NSLocalizedString("ExposureSubmissionDispatch_PositiveSelfTestButtonTitle", comment: "") + static let postiveSelfTestButtonDescription = NSLocalizedString("ExposureSubmissionDispatch_PositiveSelfTestButtonDescription", comment: "") + static let SRSButtonTitle = NSLocalizedString("ExposureSubmissionDispatch_SRSButtonTitle", comment: "") + static let SRSButtonDescription = NSLocalizedString("ExposureSubmissionDispatch_SRSButtonDescription", comment: "") static let findTestCentersButtonTitle = NSLocalizedString("ExposureSubmissionDispatch_FindTestCentersTitle", comment: "") static let findTestCentersButtonDescription = NSLocalizedString("ExposureSubmissionDispatch_FindTestCentersDescription", comment: "") + + enum SRSSubmissionError { + static let srsSubmissionInvalidOTP = NSLocalizedString("SRS_submission_Invalid_OTP", comment: "") + } } + enum SRSConsentScreen { + static let title = NSLocalizedString("SRS_ConsentScreen_title", comment: "") + static let headerSection1 = NSLocalizedString("SRS_ConsentScreen_header_section1", comment: "") + static let titleDescription1 = NSLocalizedString("SRS_ConsentScreen_title_description1", comment: "") + static let instruction1 = NSLocalizedString("SRS_ConsentScreen_instruction1", comment: "") + static let instruction2 = NSLocalizedString("SRS_ConsentScreen_instruction2", comment: "") + + static let legalHeadline = NSLocalizedString("SRS_Consent_Legal_Headline", tableName: "Localizable.legal", comment: "") + static let legalDescription = NSLocalizedString("SRS_Consent_Legal_Description", tableName: "Localizable.legal", comment: "") + static let legalBulletPoint01 = NSLocalizedString("SRS_Consent_Legal_Text_1", tableName: "Localizable.legal", comment: "") + static let legalBulletPoint02 = NSLocalizedString("SRS_Consent_Legal_Text_2", tableName: "Localizable.legal", comment: "") + static let legalBulletPoint03 = NSLocalizedString("SRS_Consent_Legal_Text_3", tableName: "Localizable.legal", comment: "") + + static let acknowledgement1 = NSLocalizedString("SRS_ConsentScreen_Acknowledgement_1", comment: "") + static let acknowledgement2 = NSLocalizedString("SRS_ConsentScreen_Acknowledgement_2", comment: "") + static let acknowledgement3 = NSLocalizedString("SRS_ConsentScreen_Acknowledgement_3", comment: "") + static let acknowledgement4 = NSLocalizedString("SRS_ConsentScreen_Acknowledgement_4", comment: "") + static let acknowledgement5 = NSLocalizedString("SRS_ConsentScreen_Acknowledgement_5", comment: "") + static let acknowledgement6 = NSLocalizedString("SRS_ConsentScreen_Acknowledgement_6", comment: "") + static let dataProcessingDetailInfo = NSLocalizedString("SRS_ConsentScreen_DataProcessingDetailInfo", comment: "") + } + + enum SRSDataProcessingInfo { + static let title = NSLocalizedString("SRS_DataProcessingInfo_title", comment: "") + static let description = NSLocalizedString("SRS_DataProcessingInfodescription", comment: "") + } + enum ExposureSubmissionQRInfo { static let title = NSLocalizedString("ExposureSubmissionQRInfo_title", comment: "") static let imageDescription = NSLocalizedString("ExposureSubmissionQRInfo_imageDescription", comment: "") @@ -475,9 +523,15 @@ enum AppStrings { static let listItemPCR0 = NSLocalizedString("ExposureSubmissionSuccess_PCR_listItem0", comment: "") static let listItemPCR1 = NSLocalizedString("ExposureSubmissionSuccess_PCR_listItem1", comment: "") static let listItemPCR2 = NSLocalizedString("ExposureSubmissionSuccess_PCR_listItem2", comment: "") + static let listItemPCR3 = NSLocalizedString("ExposureSubmissionSuccess_PCR_listItem3", comment: "") static let listItemRAT0 = NSLocalizedString("ExposureSubmissionSuccess_RAT_listItem0", comment: "") static let listItemRAT1 = NSLocalizedString("ExposureSubmissionSuccess_RAT_listItem1", comment: "") static let listItemRAT2 = NSLocalizedString("ExposureSubmissionSuccess_RAT_listItem2", comment: "") + static let listItemRAT3 = NSLocalizedString("ExposureSubmissionSuccess_RAT_listItem3", comment: "") + static let listItemSRS0 = NSLocalizedString("ExposureSubmissionSuccess_SRS_listItem0", comment: "") + static let listItemSRS1 = NSLocalizedString("ExposureSubmissionSuccess_SRS_listItem1", comment: "") + static let listItemSRS2 = NSLocalizedString("ExposureSubmissionSuccess_SRS_listItem2", comment: "") + static let listItemSRS3 = NSLocalizedString("ExposureSubmissionSuccess_SRS_listItem3", comment: "") static let subTitle = NSLocalizedString("ExposureSubmissionSuccess_subTitle", comment: "") static let listItem2_1 = NSLocalizedString("ExposureSubmissionSuccess_listItem2_1", comment: "") static let listItem2_2 = NSLocalizedString("ExposureSubmissionSuccess_listItem2_2", comment: "") @@ -1004,6 +1058,7 @@ enum AppStrings { enum TestRegistration { static let title = NSLocalizedString("Home_TestRegistration_Title", comment: "") + static let subtitle = NSLocalizedString("Home_TestRegistration_Subtitle", comment: "") static let description = NSLocalizedString("Home_TestRegistration_Body", comment: "") static let button = NSLocalizedString("Home_TestRegistration_Button", comment: "") } diff --git a/src/xcode/ENA/ENA/Source/Views/GradientView.swift b/src/xcode/ENA/ENA/Source/Views/GradientView.swift index 54f34a0197a..307cd0f95f9 100644 --- a/src/xcode/ENA/ENA/Source/Views/GradientView.swift +++ b/src/xcode/ENA/ENA/Source/Views/GradientView.swift @@ -61,6 +61,7 @@ class GradientView: UIView { case green case whiteWithGreyBorder case whiteToLightBlue + case lightBlueToWhite var starsColor: UIColor? { @@ -96,6 +97,14 @@ class GradientView: UIView { // MARK: - Private private let imageView: UIImageView = UIImageView() + + private var isDarkMode: Bool { + if #available(iOS 13, *) { + return UITraitCollection.current.userInterfaceStyle == .dark + } else { + return false + } + } private func setupView() { imageView.translatesAutoresizingMaskIntoConstraints = false @@ -196,11 +205,6 @@ class GradientView: UIView { gradientLayer.borderWidth = 0 case .solidDarkGreen: - var isDarkMode: Bool = false - if #available(iOS 13.0, *) { - isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark - } - let lightColors = [ UIColor(red: 42 / 255, green: 142 / 255, blue: 75 / 255, alpha: 1).cgColor, UIColor(red: 42 / 255, green: 142 / 255, blue: 75 / 255, alpha: 1).cgColor @@ -227,11 +231,6 @@ class GradientView: UIView { gradientLayer.endPoint = CGPoint(x: 0.75, y: 0.5) case .whiteWithGreyBorder: - var isDarkMode: Bool = false - if #available(iOS 13.0, *) { - isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark - } - let lightColors = [ UIColor(red: 255 / 255, green: 255 / 255, blue: 255 / 255, alpha: 1).cgColor, UIColor(red: 255 / 255, green: 255 / 255, blue: 255 / 255, alpha: 1).cgColor @@ -251,11 +250,6 @@ class GradientView: UIView { gradientLayer.borderWidth = 2 case .whiteToLightBlue: - var isDarkMode: Bool = false - if #available(iOS 13.0, *) { - isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark - } - let lightColors = [ UIColor(red: 255 / 255, green: 255 / 255, blue: 255 / 255, alpha: 1).cgColor, UIColor(red: 255 / 255, green: 255 / 255, blue: 255 / 255, alpha: 1).cgColor, @@ -271,6 +265,23 @@ class GradientView: UIView { gradientLayer.locations = [0.0, 0.6, 1.0] gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0) gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.95) + + case .lightBlueToWhite: + let lightColors = [ + UIColor(red: 255 / 255, green: 255 / 255, blue: 255 / 255, alpha: 1).cgColor, + UIColor(red: 255 / 255, green: 255 / 255, blue: 255 / 255, alpha: 1).cgColor, + UIColor(red: 227 / 255, green: 243 / 255, blue: 255 / 255, alpha: 1).cgColor + ] + + let darkColors = [ + UIColor(red: 30 / 255, green: 30 / 255, blue: 31 / 255, alpha: 1).cgColor, + UIColor(red: 45 / 255, green: 65 / 255, blue: 77 / 255, alpha: 1).cgColor + ] + + gradientLayer.colors = isDarkMode ? darkColors : lightColors + gradientLayer.locations = isDarkMode ? [0, 1] : [0, 0.34, 0.85] + gradientLayer.startPoint = CGPoint(x: 0.25, y: 0) + gradientLayer.endPoint = CGPoint(x: 0.75, y: 1) } } diff --git a/src/xcode/ENA/ENA/Source/Workers/Logging/Logging.swift b/src/xcode/ENA/ENA/Source/Workers/Logging/Logging.swift index 18000fe01f7..5b61066a68b 100644 --- a/src/xcode/ENA/ENA/Source/Workers/Logging/Logging.swift +++ b/src/xcode/ENA/ENA/Source/Workers/Logging/Logging.swift @@ -59,6 +59,8 @@ extension OSLog { static let ticketValidationAllowList = OSLog(subsystem: subsystem, category: "TicketValidationAllowList") /// DebugMenu static let debugMenu = OSLog(subsystem: subsystem, category: "DebugMenu") + /// Error Log Submission + static let srs = OSLog(subsystem: subsystem, category: "srs") } /// Logging diff --git a/src/xcode/ENA/ENA/Source/Workers/Store/SecureStore.swift b/src/xcode/ENA/ENA/Source/Workers/Store/SecureStore.swift index 703237a0742..22facb1d0b9 100644 --- a/src/xcode/ENA/ENA/Source/Workers/Store/SecureStore.swift +++ b/src/xcode/ENA/ENA/Source/Workers/Store/SecureStore.swift @@ -213,6 +213,11 @@ final class SecureStore: SecureKeyValueStoring, Store { set { kvStore["journalWithExposureHistoryInfoScreenShown"] = newValue } } + var mostRecentKeySubmissionDate: Date? { + get { kvStore["mostRecentKeySubmissionDate"] as Date? } + set { kvStore["mostRecentKeySubmissionDate"] = newValue } + } + var lastBoosterNotificationsExecutionDate: Date? { get { kvStore["lastBoosterNotificationsExecutionDate"] as Date? } set { kvStore["lastBoosterNotificationsExecutionDate"] = newValue } @@ -433,6 +438,11 @@ extension SecureStore: DeviceTimeCheckStoring { get { kvStore["wasDeviceTimeErrorShown"] as Bool? ?? false } set { kvStore["wasDeviceTimeErrorShown"] = newValue } } + + var firstReliableTimeStamp: Date? { + get { kvStore["firstReliableTimeStamp"] as Date? } + set { kvStore["firstReliableTimeStamp"] = newValue } + } } extension SecureStore: AppFeaturesStoring { @@ -516,6 +526,11 @@ extension SecureStore: PrivacyPreservingProviding { get { kvStore["ppacApiToken"] as TimestampedToken? } set { kvStore["ppacApiToken"] = newValue } } + + var previousPpacApiTokenEdus: TimestampedToken? { + get { kvStore["previousPpacApiTokenEdus"] as TimestampedToken? } + set { kvStore["previousPpacApiTokenEdus"] = newValue } + } } extension SecureStore: ErrorLogProviding { @@ -535,6 +550,11 @@ extension SecureStore: ErrorLogProviding { set { kvStore["ppacApiTokenEls"] = newValue } } + var previousPpacApiTokenEls: TimestampedToken? { + get { kvStore["previousPpacApiTokenEls"] as TimestampedToken? } + set { kvStore["previousPpacApiTokenEls"] = newValue } + } + var otpTokenEls: OTPToken? { get { kvStore["otpTokenEls"] as OTPToken? } set { kvStore["otpTokenEls"] = newValue } @@ -553,6 +573,29 @@ extension SecureStore: ErrorLogProviding { #endif } +extension SecureStore: SRSProviding { + + var ppacApiTokenSrs: TimestampedToken? { + get { kvStore["ppacApiTokenSrs"] as TimestampedToken? } + set { kvStore["ppacApiTokenSrs"] = newValue } + } + + var previousPpacApiTokenSrs: TimestampedToken? { + get { kvStore["previousPpacApiTokenSrs"] as TimestampedToken? } + set { kvStore["previousPpacApiTokenSrs"] = newValue } + } + + var otpTokenSrs: OTPToken? { + get { kvStore["otpTokenSrs"] as OTPToken? } + set { kvStore["otpTokenSrs"] = newValue } + } + + var otpSrsAuthorizationDate: Date? { + get { kvStore["otpSrsAuthorizationDate"] as Date? } + set { kvStore["otpSrsAuthorizationDate"] = newValue } + } +} + extension SecureStore: ErrorLogUploadHistoryProviding { var elsUploadHistory: [ErrorLogUploadReceipt] { diff --git a/src/xcode/ENA/ENA/Source/Workers/Store/Store.swift b/src/xcode/ENA/ENA/Source/Workers/Store/Store.swift index 77c7b9c59fd..0bd201a1d00 100644 --- a/src/xcode/ENA/ENA/Source/Workers/Store/Store.swift +++ b/src/xcode/ENA/ENA/Source/Workers/Store/Store.swift @@ -71,6 +71,8 @@ protocol StoreProtocol: AnyObject { var journalWithExposureHistoryInfoScreenShown: Bool { get set } + var mostRecentKeySubmissionDate: Date? { get set } + func wipeAll(key: String?) #if !RELEASE @@ -93,6 +95,8 @@ protocol DeviceTimeCheckStoring: AnyObject { var deviceTimeCheckResult: DeviceTimeCheck.TimeCheckResult { get set } var deviceTimeLastStateChange: Date { get set } var wasDeviceTimeErrorShown: Bool { get set } + var firstReliableTimeStamp: Date? { get set } + } protocol AppFeaturesStoring: AnyObject { @@ -121,6 +125,17 @@ protocol LocalStatisticsCaching: AnyObject { var selectedLocalStatisticsRegions: [LocalStatisticsRegion] { get set } } +protocol SRSProviding: AnyObject { + /// PPAC token for Self-Report Submission (SRS) + var ppacApiTokenSrs: TimestampedToken? { get set } + /// Previous PPAC token for Self-Report Submission (SRS) + var previousPpacApiTokenSrs: TimestampedToken? { get set } + /// OTP for Self-Report Submission (SRS) + var otpTokenSrs: OTPToken? { get set } + /// Date of last otp authorization + var otpSrsAuthorizationDate: Date? { get set } +} + protocol PrivacyPreservingProviding: AnyObject { /// A boolean storing if the user has already confirmed to collect and submit the data for PPA. By setting it, the existing anlytics data will be reset. var isPrivacyPreservingAnalyticsConsentGiven: Bool { get set } @@ -132,11 +147,15 @@ protocol PrivacyPreservingProviding: AnyObject { var otpEdusAuthorizationDate: Date? { get set } /// PPAC Edus token var ppacApiTokenEdus: TimestampedToken? { get set } + /// PPAC Edus Previous token + var previousPpacApiTokenEdus: TimestampedToken? { get set } } protocol ErrorLogProviding: AnyObject { /// PPAC token for error log support (Els) var ppacApiTokenEls: TimestampedToken? { get set } + /// Previous PPAC token for error log support (Els) + var previousPpacApiTokenEls: TimestampedToken? { get set } /// OTP for error log support (Els) var otpTokenEls: OTPToken? { get set } /// Date of last otp authorization @@ -321,6 +340,7 @@ protocol Store: RecycleBinStoring, TicketValidationStoring, HomeBadgeStoring, - KeyValueCacheStoring + KeyValueCacheStoring, + SRSProviding {} // swiftlint:enable all diff --git a/src/xcode/ENA/ENAUITests/AppDelegate/ENAUITests_11_QuickActions.swift b/src/xcode/ENA/ENAUITests/AppDelegate/ENAUITests_11_QuickActions.swift index 9cde1a21d3d..017142d2f4d 100644 --- a/src/xcode/ENA/ENAUITests/AppDelegate/ENAUITests_11_QuickActions.swift +++ b/src/xcode/ENA/ENAUITests/AppDelegate/ENAUITests_11_QuickActions.swift @@ -81,7 +81,7 @@ class ENAUITests_11_QuickActions: CWATestCase { XCTAssertTrue(app.segmentedControls[AccessibilityIdentifiers.ContactDiary.segmentedControl].waitForExistence(timeout: .short)) } - + /* Commented out in favour SRS story func testShortcutAvailabilityDuringSubmissionFlow() throws { let app = XCUIApplication() app.setDefaults() @@ -130,7 +130,7 @@ class ENAUITests_11_QuickActions: CWATestCase { XCTAssertTrue(app.buttons[AccessibilityIdentifiers.Home.submitCardButton].waitForExistence(timeout: .medium)) try checkAppMenu(expectNewDiaryItem: true, expectEventCheckin: true) // available again? } - + */ /// Checks the state of the quick action menu according to given parameter. /// diff --git a/src/xcode/ENA/ENAUITests/ExposureSubmission/ENAUITests_04a_ExposureSubmission.swift b/src/xcode/ENA/ENAUITests/ExposureSubmission/ENAUITests_04a_ExposureSubmission.swift index b5bc8c44c60..c5f1da26e4f 100644 --- a/src/xcode/ENA/ENAUITests/ExposureSubmission/ENAUITests_04a_ExposureSubmission.swift +++ b/src/xcode/ENA/ENAUITests/ExposureSubmission/ENAUITests_04a_ExposureSubmission.swift @@ -250,6 +250,7 @@ class ENAUITests_04a_ExposureSubmission: CWATestCase { } + /* Commented out in favour of SRS story func test_SubmitTAN_CancelOnTestResultScreen() { app.setLaunchArgument(LaunchArguments.common.ENStatus, to: ENStatus.active.stringValue) launch() @@ -320,7 +321,8 @@ class ENAUITests_04a_ExposureSubmission: CWATestCase { // Click secondary button to skip symptoms screen. app.buttons[AccessibilityIdentifiers.ExposureSubmission.secondaryButton].waitAndTap() } - + */ + // Navigate to the Thank You screen after getting the positive test result. func test_ThankYouScreen_withWarnOthers() { app.setLaunchArgument(LaunchArguments.exposureSubmission.isFetchingSubmissionTan, to: true) @@ -341,17 +343,7 @@ class ENAUITests_04a_ExposureSubmission: CWATestCase { acceptInformationSharingIfNecessary() - app.buttons[AccessibilityIdentifiers.ExposureSubmission.primaryButton].waitAndTap() - app.navigationBars[AccessibilityIdentifiers.General.exposureSubmissionNavigationControllerTitle].buttons.element(boundBy: 0).waitAndTap() - app.buttons[AccessibilityIdentifiers.ExposureSubmission.secondaryButton].waitAndTap() - // quick hack - can't easily use `addUIInterruptionMonitor` in this test - app.alerts.firstMatch.buttons.element(boundBy: 1).waitAndTap() // no - app.buttons[AccessibilityIdentifiers.ExposureSubmission.secondaryButton].waitAndTap() - // quick hack - can't easily use `addUIInterruptionMonitor` in this test - app.alerts.firstMatch.buttons.firstMatch.waitAndTap() // yes - - // Back to homescreen - app.cells[AccessibilityIdentifiers.Home.ShownPositiveTestResultCell.submittedPCRCell].waitAndTap() + XCTAssertTrue(app.buttons[AccessibilityIdentifiers.ExposureSubmission.primaryButton].waitForExistence(timeout: .medium)) } // Navigate to the Thank You screen with alert on Test Result Screen. @@ -388,22 +380,8 @@ class ENAUITests_04a_ExposureSubmission: CWATestCase { // quick hack - can't easily use `addUIInterruptionMonitor` in this test app.alerts.firstMatch.buttons.element(boundBy: 1).waitAndTap() // warn - app.buttons[AccessibilityIdentifiers.ExposureSubmission.primaryButton].waitAndTap() - app.navigationBars[AccessibilityIdentifiers.General.exposureSubmissionNavigationControllerTitle].buttons.element(boundBy: 0).waitAndTap() - - app.buttons[AccessibilityIdentifiers.ExposureSubmission.secondaryButton].waitAndTap() - - // quick hack - can't easily use `addUIInterruptionMonitor` in this test - app.alerts.firstMatch.buttons.element(boundBy: 1).waitAndTap() // no - app.buttons[AccessibilityIdentifiers.ExposureSubmission.secondaryButton].waitAndTap() - - // quick hack - can't easily use `addUIInterruptionMonitor` in this test - app.alerts.firstMatch.buttons.firstMatch.waitAndTap() // yes - - // Back to homescreen - XCTAssertTrue(app.cells[AccessibilityIdentifiers.Home.activateCardOnTitle].waitForExistence(timeout: .long)) - XCTAssertTrue(app.cells[AccessibilityIdentifiers.Home.activateCardOnTitle].isHittable) + XCTAssertTrue(app.buttons[AccessibilityIdentifiers.ExposureSubmission.primaryButton].waitForExistence(timeout: .medium)) } func test_Tester_Centers_Opens_Website_In_Safari() { @@ -424,6 +402,7 @@ class ENAUITests_04a_ExposureSubmission: CWATestCase { } + /* Commented out in favour of SRS story func test_SubmissionNotPossible() throws { try XCTSkipIf(Locale.current.identifier == "bg_BG") // temporary hack! @@ -450,6 +429,7 @@ class ENAUITests_04a_ExposureSubmission: CWATestCase { // expect an error dialogue due to disabled exposure notification XCTAssertTrue(app.alerts.firstMatch.waitForExistence(timeout: .short)) } + */ func test_test_result_negative() { app.setLaunchArgument(LaunchArguments.common.ENStatus, to: ENStatus.active.stringValue) @@ -646,6 +626,7 @@ class ENAUITests_04a_ExposureSubmission: CWATestCase { snapshot("tan_submissionflow_symptoms_selection\(String(format: "%04d", (screenshotCounter.inc() )))") } + /* Commented out in favour of SRS story func test_screenshot_SubmitTAN() { var screenshotCounter = 0 @@ -675,6 +656,7 @@ class ENAUITests_04a_ExposureSubmission: CWATestCase { // Click secondary button to skip symptoms. app.buttons[AccessibilityIdentifiers.ExposureSubmission.secondaryButton].waitAndTap() } + */ func test_screenshot_SubmitQR() throws { var screenshotCounter = 0 @@ -792,7 +774,6 @@ class ENAUITests_04a_ExposureSubmission: CWATestCase { snapshot("submissionflow_screenshot_symptoms_onset_date_option") } - } // MARK: - Helpers. @@ -851,7 +832,6 @@ extension ENAUITests_04a_ExposureSubmission { // Thank You screen. XCTAssertTrue(app.navigationBars[AccessibilityIdentifiers.General.exposureSubmissionNavigationControllerTitle].waitForExistence(timeout: .medium)) app.buttons[AccessibilityIdentifiers.ExposureSubmission.primaryButton].waitAndTap() - } func launchAndNavigateToSymptomsOnsetScreen() { diff --git a/src/xcode/gen/output/internal/ppdd/ppac_ios.pb.swift b/src/xcode/gen/output/internal/ppdd/ppac_ios.pb.swift index 42c99365140..4b747225546 100644 --- a/src/xcode/gen/output/internal/ppdd/ppac_ios.pb.swift +++ b/src/xcode/gen/output/internal/ppdd/ppac_ios.pb.swift @@ -31,6 +31,8 @@ struct SAP_Internal_Ppdd_PPACIOS { var apiToken: String = String() + var previousApiToken: String = String() + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -49,6 +51,7 @@ extension SAP_Internal_Ppdd_PPACIOS: SwiftProtobuf.Message, SwiftProtobuf._Messa static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "deviceToken"), 2: .same(proto: "apiToken"), + 3: .same(proto: "previousApiToken"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -59,6 +62,7 @@ extension SAP_Internal_Ppdd_PPACIOS: SwiftProtobuf.Message, SwiftProtobuf._Messa switch fieldNumber { case 1: try { try decoder.decodeSingularStringField(value: &self.deviceToken) }() case 2: try { try decoder.decodeSingularStringField(value: &self.apiToken) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.previousApiToken) }() default: break } } @@ -71,12 +75,16 @@ extension SAP_Internal_Ppdd_PPACIOS: SwiftProtobuf.Message, SwiftProtobuf._Messa if !self.apiToken.isEmpty { try visitor.visitSingularStringField(value: self.apiToken, fieldNumber: 2) } + if !self.previousApiToken.isEmpty { + try visitor.visitSingularStringField(value: self.previousApiToken, fieldNumber: 3) + } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: SAP_Internal_Ppdd_PPACIOS, rhs: SAP_Internal_Ppdd_PPACIOS) -> Bool { if lhs.deviceToken != rhs.deviceToken {return false} if lhs.apiToken != rhs.apiToken {return false} + if lhs.previousApiToken != rhs.previousApiToken {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/src/xcode/gen/output/internal/ppdd/srs_otp.pb.swift b/src/xcode/gen/output/internal/ppdd/srs_otp.pb.swift new file mode 100644 index 00000000000..f06339b220d --- /dev/null +++ b/src/xcode/gen/output/internal/ppdd/srs_otp.pb.swift @@ -0,0 +1,75 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: internal/ppdd/srs_otp.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +/// This file is auto-generated, DO NOT make any changes here + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +struct SAP_Internal_Ppdd_SRSOneTimePassword { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var otp: String = String() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +#if swift(>=5.5) && canImport(_Concurrency) +extension SAP_Internal_Ppdd_SRSOneTimePassword: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "SAP.internal.ppdd" + +extension SAP_Internal_Ppdd_SRSOneTimePassword: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".SRSOneTimePassword" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "otp"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.otp) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.otp.isEmpty { + try visitor.visitSingularStringField(value: self.otp, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SAP_Internal_Ppdd_SRSOneTimePassword, rhs: SAP_Internal_Ppdd_SRSOneTimePassword) -> Bool { + if lhs.otp != rhs.otp {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/src/xcode/gen/output/internal/ppdd/srs_otp_request_ios.pb.swift b/src/xcode/gen/output/internal/ppdd/srs_otp_request_ios.pb.swift new file mode 100644 index 00000000000..dedc5f6943e --- /dev/null +++ b/src/xcode/gen/output/internal/ppdd/srs_otp_request_ios.pb.swift @@ -0,0 +1,112 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: internal/ppdd/srs_otp_request_ios.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +/// This file is auto-generated, DO NOT make any changes here + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +struct SAP_Internal_Ppdd_SRSOneTimePasswordRequestIOS { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var authentication: SAP_Internal_Ppdd_PPACIOS { + get {return _authentication ?? SAP_Internal_Ppdd_PPACIOS()} + set {_authentication = newValue} + } + /// Returns true if `authentication` has been explicitly set. + var hasAuthentication: Bool {return self._authentication != nil} + /// Clears the value of `authentication`. Subsequent reads from it will return its default value. + mutating func clearAuthentication() {self._authentication = nil} + + var payload: SAP_Internal_Ppdd_SRSOneTimePassword { + get {return _payload ?? SAP_Internal_Ppdd_SRSOneTimePassword()} + set {_payload = newValue} + } + /// Returns true if `payload` has been explicitly set. + var hasPayload: Bool {return self._payload != nil} + /// Clears the value of `payload`. Subsequent reads from it will return its default value. + mutating func clearPayload() {self._payload = nil} + + var requestPadding: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _authentication: SAP_Internal_Ppdd_PPACIOS? = nil + fileprivate var _payload: SAP_Internal_Ppdd_SRSOneTimePassword? = nil +} + +#if swift(>=5.5) && canImport(_Concurrency) +extension SAP_Internal_Ppdd_SRSOneTimePasswordRequestIOS: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "SAP.internal.ppdd" + +extension SAP_Internal_Ppdd_SRSOneTimePasswordRequestIOS: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".SRSOneTimePasswordRequestIOS" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "authentication"), + 2: .same(proto: "payload"), + 3: .same(proto: "requestPadding"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._authentication) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._payload) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.requestPadding) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._authentication { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._payload { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + if !self.requestPadding.isEmpty { + try visitor.visitSingularBytesField(value: self.requestPadding, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SAP_Internal_Ppdd_SRSOneTimePasswordRequestIOS, rhs: SAP_Internal_Ppdd_SRSOneTimePasswordRequestIOS) -> Bool { + if lhs._authentication != rhs._authentication {return false} + if lhs._payload != rhs._payload {return false} + if lhs.requestPadding != rhs.requestPadding {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/src/xcode/gen/output/internal/submission_payload.pb.swift b/src/xcode/gen/output/internal/submission_payload.pb.swift index c626f876146..a38a59a40e0 100644 --- a/src/xcode/gen/output/internal/submission_payload.pb.swift +++ b/src/xcode/gen/output/internal/submission_payload.pb.swift @@ -78,6 +78,13 @@ struct SAP_Internal_SubmissionPayload { case pcrTest // = 0 case rapidTest // = 1 case hostWarning // = 2 + case srsSelfTest // = 3 + case srsRegisteredRat // = 4 + case srsUnregisteredRat // = 5 + case srsRegisteredPcr // = 6 + case srsUnregisteredPcr // = 7 + case srsRapidPcr // = 8 + case srsOther // = 9 init() { self = .pcrTest @@ -88,6 +95,13 @@ struct SAP_Internal_SubmissionPayload { case 0: self = .pcrTest case 1: self = .rapidTest case 2: self = .hostWarning + case 3: self = .srsSelfTest + case 4: self = .srsRegisteredRat + case 5: self = .srsUnregisteredRat + case 6: self = .srsRegisteredPcr + case 7: self = .srsUnregisteredPcr + case 8: self = .srsRapidPcr + case 9: self = .srsOther default: return nil } } @@ -97,6 +111,13 @@ struct SAP_Internal_SubmissionPayload { case .pcrTest: return 0 case .rapidTest: return 1 case .hostWarning: return 2 + case .srsSelfTest: return 3 + case .srsRegisteredRat: return 4 + case .srsUnregisteredRat: return 5 + case .srsRegisteredPcr: return 6 + case .srsUnregisteredPcr: return 7 + case .srsRapidPcr: return 8 + case .srsOther: return 9 } } @@ -210,5 +231,12 @@ extension SAP_Internal_SubmissionPayload.SubmissionType: SwiftProtobuf._ProtoNam 0: .same(proto: "SUBMISSION_TYPE_PCR_TEST"), 1: .same(proto: "SUBMISSION_TYPE_RAPID_TEST"), 2: .same(proto: "SUBMISSION_TYPE_HOST_WARNING"), + 3: .same(proto: "SUBMISSION_TYPE_SRS_SELF_TEST"), + 4: .same(proto: "SUBMISSION_TYPE_SRS_REGISTERED_RAT"), + 5: .same(proto: "SUBMISSION_TYPE_SRS_UNREGISTERED_RAT"), + 6: .same(proto: "SUBMISSION_TYPE_SRS_REGISTERED_PCR"), + 7: .same(proto: "SUBMISSION_TYPE_SRS_UNREGISTERED_PCR"), + 8: .same(proto: "SUBMISSION_TYPE_SRS_RAPID_PCR"), + 9: .same(proto: "SUBMISSION_TYPE_SRS_OTHER"), ] } diff --git a/src/xcode/gen/output/internal/v2/app_config_ios.pb.swift b/src/xcode/gen/output/internal/v2/app_config_ios.pb.swift index f3acd44795f..0afd071f779 100644 --- a/src/xcode/gen/output/internal/v2/app_config_ios.pb.swift +++ b/src/xcode/gen/output/internal/v2/app_config_ios.pb.swift @@ -149,6 +149,15 @@ struct SAP_Internal_V2_ApplicationConfigurationIOS { /// Clears the value of `dgcParameters`. Subsequent reads from it will return its default value. mutating func clearDgcParameters() {_uniqueStorage()._dgcParameters = nil} + var selfReportParameters: SAP_Internal_V2_PPDDSelfReportSubmissionParametersIOS { + get {return _storage._selfReportParameters ?? SAP_Internal_V2_PPDDSelfReportSubmissionParametersIOS()} + set {_uniqueStorage()._selfReportParameters = newValue} + } + /// Returns true if `selfReportParameters` has been explicitly set. + var hasSelfReportParameters: Bool {return _storage._selfReportParameters != nil} + /// Clears the value of `selfReportParameters`. Subsequent reads from it will return its default value. + mutating func clearSelfReportParameters() {_uniqueStorage()._selfReportParameters = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -220,6 +229,7 @@ extension SAP_Internal_V2_ApplicationConfigurationIOS: SwiftProtobuf.Message, Sw 12: .same(proto: "presenceTracingParameters"), 13: .same(proto: "coronaTestParameters"), 14: .same(proto: "dgcParameters"), + 15: .same(proto: "selfReportParameters"), ] fileprivate class _StorageClass { @@ -237,6 +247,7 @@ extension SAP_Internal_V2_ApplicationConfigurationIOS: SwiftProtobuf.Message, Sw var _presenceTracingParameters: SAP_Internal_V2_PresenceTracingParameters? = nil var _coronaTestParameters: SAP_Internal_V2_CoronaTestParameters? = nil var _dgcParameters: SAP_Internal_V2_DGCParameters? = nil + var _selfReportParameters: SAP_Internal_V2_PPDDSelfReportSubmissionParametersIOS? = nil static let defaultInstance = _StorageClass() @@ -257,6 +268,7 @@ extension SAP_Internal_V2_ApplicationConfigurationIOS: SwiftProtobuf.Message, Sw _presenceTracingParameters = source._presenceTracingParameters _coronaTestParameters = source._coronaTestParameters _dgcParameters = source._dgcParameters + _selfReportParameters = source._selfReportParameters } } @@ -289,6 +301,7 @@ extension SAP_Internal_V2_ApplicationConfigurationIOS: SwiftProtobuf.Message, Sw case 12: try { try decoder.decodeSingularMessageField(value: &_storage._presenceTracingParameters) }() case 13: try { try decoder.decodeSingularMessageField(value: &_storage._coronaTestParameters) }() case 14: try { try decoder.decodeSingularMessageField(value: &_storage._dgcParameters) }() + case 15: try { try decoder.decodeSingularMessageField(value: &_storage._selfReportParameters) }() default: break } } @@ -343,6 +356,9 @@ extension SAP_Internal_V2_ApplicationConfigurationIOS: SwiftProtobuf.Message, Sw try { if let v = _storage._dgcParameters { try visitor.visitSingularMessageField(value: v, fieldNumber: 14) } }() + try { if let v = _storage._selfReportParameters { + try visitor.visitSingularMessageField(value: v, fieldNumber: 15) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -366,6 +382,7 @@ extension SAP_Internal_V2_ApplicationConfigurationIOS: SwiftProtobuf.Message, Sw if _storage._presenceTracingParameters != rhs_storage._presenceTracingParameters {return false} if _storage._coronaTestParameters != rhs_storage._coronaTestParameters {return false} if _storage._dgcParameters != rhs_storage._dgcParameters {return false} + if _storage._selfReportParameters != rhs_storage._selfReportParameters {return false} return true } if !storagesAreEqual {return false} diff --git a/src/xcode/gen/output/internal/v2/ppdd_ppa_parameters.pb.swift b/src/xcode/gen/output/internal/v2/ppdd_ppa_parameters.pb.swift index e7cca8057e3..daee2eae455 100644 --- a/src/xcode/gen/output/internal/v2/ppdd_ppa_parameters.pb.swift +++ b/src/xcode/gen/output/internal/v2/ppdd_ppa_parameters.pb.swift @@ -97,6 +97,29 @@ struct SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsParametersCommon { var hoursSinceTestResultToSubmitKeySubmissionMetadata: Int32 = 0 + var plausibleDeniabilityParameters: SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsPlausibleDeniabilityParameters { + get {return _plausibleDeniabilityParameters ?? SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsPlausibleDeniabilityParameters()} + set {_plausibleDeniabilityParameters = newValue} + } + /// Returns true if `plausibleDeniabilityParameters` has been explicitly set. + var hasPlausibleDeniabilityParameters: Bool {return self._plausibleDeniabilityParameters != nil} + /// Clears the value of `plausibleDeniabilityParameters`. Subsequent reads from it will return its default value. + mutating func clearPlausibleDeniabilityParameters() {self._plausibleDeniabilityParameters = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _plausibleDeniabilityParameters: SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsPlausibleDeniabilityParameters? = nil +} + +struct SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsPlausibleDeniabilityParameters { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var probabilityOfFakeKeySubmission: Double = 0 + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -106,6 +129,7 @@ struct SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsParametersCommon { extension SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsParametersIOS: @unchecked Sendable {} extension SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsParametersAndroid: @unchecked Sendable {} extension SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsParametersCommon: @unchecked Sendable {} +extension SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsPlausibleDeniabilityParameters: @unchecked Sendable {} #endif // swift(>=5.5) && canImport(_Concurrency) // MARK: - Code below here is support for the SwiftProtobuf runtime. @@ -203,6 +227,7 @@ extension SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsParametersCommon: SwiftP 2: .same(proto: "probabilityToSubmitExposureWindows"), 3: .same(proto: "hoursSinceTestRegistrationToSubmitTestResultMetadata"), 4: .same(proto: "hoursSinceTestResultToSubmitKeySubmissionMetadata"), + 5: .same(proto: "plausibleDeniabilityParameters"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -215,12 +240,17 @@ extension SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsParametersCommon: SwiftP case 2: try { try decoder.decodeSingularDoubleField(value: &self.probabilityToSubmitExposureWindows) }() case 3: try { try decoder.decodeSingularInt32Field(value: &self.hoursSinceTestRegistrationToSubmitTestResultMetadata) }() case 4: try { try decoder.decodeSingularInt32Field(value: &self.hoursSinceTestResultToSubmitKeySubmissionMetadata) }() + case 5: try { try decoder.decodeSingularMessageField(value: &self._plausibleDeniabilityParameters) }() default: break } } } func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if self.probabilityToSubmit != 0 { try visitor.visitSingularDoubleField(value: self.probabilityToSubmit, fieldNumber: 1) } @@ -233,6 +263,9 @@ extension SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsParametersCommon: SwiftP if self.hoursSinceTestResultToSubmitKeySubmissionMetadata != 0 { try visitor.visitSingularInt32Field(value: self.hoursSinceTestResultToSubmitKeySubmissionMetadata, fieldNumber: 4) } + try { if let v = self._plausibleDeniabilityParameters { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -241,6 +274,39 @@ extension SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsParametersCommon: SwiftP if lhs.probabilityToSubmitExposureWindows != rhs.probabilityToSubmitExposureWindows {return false} if lhs.hoursSinceTestRegistrationToSubmitTestResultMetadata != rhs.hoursSinceTestRegistrationToSubmitTestResultMetadata {return false} if lhs.hoursSinceTestResultToSubmitKeySubmissionMetadata != rhs.hoursSinceTestResultToSubmitKeySubmissionMetadata {return false} + if lhs._plausibleDeniabilityParameters != rhs._plausibleDeniabilityParameters {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsPlausibleDeniabilityParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PPDDPrivacyPreservingAnalyticsPlausibleDeniabilityParameters" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "probabilityOfFakeKeySubmission"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularDoubleField(value: &self.probabilityOfFakeKeySubmission) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.probabilityOfFakeKeySubmission != 0 { + try visitor.visitSingularDoubleField(value: self.probabilityOfFakeKeySubmission, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsPlausibleDeniabilityParameters, rhs: SAP_Internal_V2_PPDDPrivacyPreservingAnalyticsPlausibleDeniabilityParameters) -> Bool { + if lhs.probabilityOfFakeKeySubmission != rhs.probabilityOfFakeKeySubmission {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/src/xcode/gen/output/internal/v2/ppdd_srs_parameters.pb.swift b/src/xcode/gen/output/internal/v2/ppdd_srs_parameters.pb.swift new file mode 100644 index 00000000000..e01f2b1c7df --- /dev/null +++ b/src/xcode/gen/output/internal/v2/ppdd_srs_parameters.pb.swift @@ -0,0 +1,305 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: internal/v2/ppdd_srs_parameters.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +/// This file is auto-generated, DO NOT make any changes here + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +struct SAP_Internal_V2_PPDDSelfReportSubmissionParametersIOS { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var common: SAP_Internal_V2_PPDDSelfReportSubmissionParametersCommon { + get {return _common ?? SAP_Internal_V2_PPDDSelfReportSubmissionParametersCommon()} + set {_common = newValue} + } + /// Returns true if `common` has been explicitly set. + var hasCommon: Bool {return self._common != nil} + /// Clears the value of `common`. Subsequent reads from it will return its default value. + mutating func clearCommon() {self._common = nil} + + var ppac: SAP_Internal_V2_PPDDPrivacyPreservingAccessControlParametersIOS { + get {return _ppac ?? SAP_Internal_V2_PPDDPrivacyPreservingAccessControlParametersIOS()} + set {_ppac = newValue} + } + /// Returns true if `ppac` has been explicitly set. + var hasPpac: Bool {return self._ppac != nil} + /// Clears the value of `ppac`. Subsequent reads from it will return its default value. + mutating func clearPpac() {self._ppac = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _common: SAP_Internal_V2_PPDDSelfReportSubmissionParametersCommon? = nil + fileprivate var _ppac: SAP_Internal_V2_PPDDPrivacyPreservingAccessControlParametersIOS? = nil +} + +struct SAP_Internal_V2_PPDDSelfReportSubmissionParametersAndroid { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var common: SAP_Internal_V2_PPDDSelfReportSubmissionParametersCommon { + get {return _common ?? SAP_Internal_V2_PPDDSelfReportSubmissionParametersCommon()} + set {_common = newValue} + } + /// Returns true if `common` has been explicitly set. + var hasCommon: Bool {return self._common != nil} + /// Clears the value of `common`. Subsequent reads from it will return its default value. + mutating func clearCommon() {self._common = nil} + + var ppac: SAP_Internal_V2_PPDDPrivacyPreservingAccessControlParametersAndroid { + get {return _ppac ?? SAP_Internal_V2_PPDDPrivacyPreservingAccessControlParametersAndroid()} + set {_ppac = newValue} + } + /// Returns true if `ppac` has been explicitly set. + var hasPpac: Bool {return self._ppac != nil} + /// Clears the value of `ppac`. Subsequent reads from it will return its default value. + mutating func clearPpac() {self._ppac = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _common: SAP_Internal_V2_PPDDSelfReportSubmissionParametersCommon? = nil + fileprivate var _ppac: SAP_Internal_V2_PPDDPrivacyPreservingAccessControlParametersAndroid? = nil +} + +struct SAP_Internal_V2_PPDDSelfReportSubmissionParametersCommon { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var timeSinceOnboardingInHours: Int32 = 0 + + var timeBetweenSubmissionsInDays: Int32 = 0 + + var plausibleDeniabilityParameters: SAP_Internal_V2_PPDDSelfReportSubmissionPlausibleDeniabilityParameters { + get {return _plausibleDeniabilityParameters ?? SAP_Internal_V2_PPDDSelfReportSubmissionPlausibleDeniabilityParameters()} + set {_plausibleDeniabilityParameters = newValue} + } + /// Returns true if `plausibleDeniabilityParameters` has been explicitly set. + var hasPlausibleDeniabilityParameters: Bool {return self._plausibleDeniabilityParameters != nil} + /// Clears the value of `plausibleDeniabilityParameters`. Subsequent reads from it will return its default value. + mutating func clearPlausibleDeniabilityParameters() {self._plausibleDeniabilityParameters = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _plausibleDeniabilityParameters: SAP_Internal_V2_PPDDSelfReportSubmissionPlausibleDeniabilityParameters? = nil +} + +struct SAP_Internal_V2_PPDDSelfReportSubmissionPlausibleDeniabilityParameters { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var minRequestPaddingBytes: Int32 = 0 + + var maxRequestPaddingBytes: Int32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +#if swift(>=5.5) && canImport(_Concurrency) +extension SAP_Internal_V2_PPDDSelfReportSubmissionParametersIOS: @unchecked Sendable {} +extension SAP_Internal_V2_PPDDSelfReportSubmissionParametersAndroid: @unchecked Sendable {} +extension SAP_Internal_V2_PPDDSelfReportSubmissionParametersCommon: @unchecked Sendable {} +extension SAP_Internal_V2_PPDDSelfReportSubmissionPlausibleDeniabilityParameters: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "SAP.internal.v2" + +extension SAP_Internal_V2_PPDDSelfReportSubmissionParametersIOS: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PPDDSelfReportSubmissionParametersIOS" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "common"), + 2: .same(proto: "ppac"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._common) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._ppac) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._common { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._ppac { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SAP_Internal_V2_PPDDSelfReportSubmissionParametersIOS, rhs: SAP_Internal_V2_PPDDSelfReportSubmissionParametersIOS) -> Bool { + if lhs._common != rhs._common {return false} + if lhs._ppac != rhs._ppac {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SAP_Internal_V2_PPDDSelfReportSubmissionParametersAndroid: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PPDDSelfReportSubmissionParametersAndroid" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "common"), + 2: .same(proto: "ppac"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._common) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._ppac) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._common { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + try { if let v = self._ppac { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SAP_Internal_V2_PPDDSelfReportSubmissionParametersAndroid, rhs: SAP_Internal_V2_PPDDSelfReportSubmissionParametersAndroid) -> Bool { + if lhs._common != rhs._common {return false} + if lhs._ppac != rhs._ppac {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SAP_Internal_V2_PPDDSelfReportSubmissionParametersCommon: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PPDDSelfReportSubmissionParametersCommon" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "timeSinceOnboardingInHours"), + 2: .same(proto: "timeBetweenSubmissionsInDays"), + 3: .same(proto: "plausibleDeniabilityParameters"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt32Field(value: &self.timeSinceOnboardingInHours) }() + case 2: try { try decoder.decodeSingularInt32Field(value: &self.timeBetweenSubmissionsInDays) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._plausibleDeniabilityParameters) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.timeSinceOnboardingInHours != 0 { + try visitor.visitSingularInt32Field(value: self.timeSinceOnboardingInHours, fieldNumber: 1) + } + if self.timeBetweenSubmissionsInDays != 0 { + try visitor.visitSingularInt32Field(value: self.timeBetweenSubmissionsInDays, fieldNumber: 2) + } + try { if let v = self._plausibleDeniabilityParameters { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SAP_Internal_V2_PPDDSelfReportSubmissionParametersCommon, rhs: SAP_Internal_V2_PPDDSelfReportSubmissionParametersCommon) -> Bool { + if lhs.timeSinceOnboardingInHours != rhs.timeSinceOnboardingInHours {return false} + if lhs.timeBetweenSubmissionsInDays != rhs.timeBetweenSubmissionsInDays {return false} + if lhs._plausibleDeniabilityParameters != rhs._plausibleDeniabilityParameters {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension SAP_Internal_V2_PPDDSelfReportSubmissionPlausibleDeniabilityParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PPDDSelfReportSubmissionPlausibleDeniabilityParameters" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "minRequestPaddingBytes"), + 2: .same(proto: "maxRequestPaddingBytes"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt32Field(value: &self.minRequestPaddingBytes) }() + case 2: try { try decoder.decodeSingularInt32Field(value: &self.maxRequestPaddingBytes) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.minRequestPaddingBytes != 0 { + try visitor.visitSingularInt32Field(value: self.minRequestPaddingBytes, fieldNumber: 1) + } + if self.maxRequestPaddingBytes != 0 { + try visitor.visitSingularInt32Field(value: self.maxRequestPaddingBytes, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: SAP_Internal_V2_PPDDSelfReportSubmissionPlausibleDeniabilityParameters, rhs: SAP_Internal_V2_PPDDSelfReportSubmissionPlausibleDeniabilityParameters) -> Bool { + if lhs.minRequestPaddingBytes != rhs.minRequestPaddingBytes {return false} + if lhs.maxRequestPaddingBytes != rhs.maxRequestPaddingBytes {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +}