diff --git a/context.jsonc b/Context/Release/context.jsonc similarity index 72% rename from context.jsonc rename to Context/Release/context.jsonc index 887de54..cd1fb55 100644 --- a/context.jsonc +++ b/Context/Release/context.jsonc @@ -19,6 +19,15 @@ "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" ] + }, + "countryList": { + "url": "https://dgca-businessrule-service.cfapps.eu10.hana.ondemand.com/countrylist" + }, + "rules": { + "url": "https://dgca-businessrule-service.cfapps.eu10.hana.ondemand.com/rules" + }, + "valuesets": { + "url": "https://dgca-businessrule-service.cfapps.eu10.hana.ondemand.com/valuesets" } } }, diff --git a/Context/Test/context.jsonc b/Context/Test/context.jsonc new file mode 100644 index 0000000..00b4468 --- /dev/null +++ b/Context/Test/context.jsonc @@ -0,0 +1,39 @@ +{ + // Origin in ISO alpha 2 code: + "origin": "DE", + "versions": { + "default": { + // catch-all for normal versions + "privacyUrl": "https://publications.europa.eu/en/web/about-us/legal-notices/eu-mobile-apps", + "context": { + "url": "https://dgca-issuance-web-eu-acc.cfapps.eu10.hana.ondemand.com/dgca-issuance-service/context", + "pubKeys": [ + "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", + "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" + ] + }, + "endpoints": { + "claim": { + "url": "https://issuance-dgca-test.cfapps.eu10.hana.ondemand.com/dgca-issuance-service/dgci/wallet/claim", + "pubKeys": [ + "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", + "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" + ] + }, + "countryList": { + "url": "https://dgca-businessrule-service.cfapps.eu10.hana.ondemand.com/countrylist" + }, + "rules": { + "url": "https://dgca-businessrule-service.cfapps.eu10.hana.ondemand.com/rules" + }, + "valuesets": { + "url": "https://dgca-businessrule-service.cfapps.eu10.hana.ondemand.com/valuesets" + } + } + }, + "0.1.0": { + // Example for a version that is insecure and shouldn't start. + "outdated": true + } + } +} diff --git a/DGCAWallet.xcodeproj/project.pbxproj b/DGCAWallet.xcodeproj/project.pbxproj index d16dea2..0208a30 100644 --- a/DGCAWallet.xcodeproj/project.pbxproj +++ b/DGCAWallet.xcodeproj/project.pbxproj @@ -7,6 +7,64 @@ objects = { /* Begin PBXBuildFile section */ + 5C50CF09269700550015EE29 /* CheckValidityVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF07269700540015EE29 /* CheckValidityVC.swift */; }; + 5C50CF0A269700550015EE29 /* CheckValidityVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF07269700540015EE29 /* CheckValidityVC.swift */; }; + 5C50CF0B269700550015EE29 /* CheckValidityVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF07269700540015EE29 /* CheckValidityVC.swift */; }; + 5C50CF0C269700550015EE29 /* CheckValidityVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF08269700540015EE29 /* CheckValidityVC.xib */; }; + 5C50CF0D269700550015EE29 /* CheckValidityVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF08269700540015EE29 /* CheckValidityVC.xib */; }; + 5C50CF0E269700550015EE29 /* CheckValidityVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF08269700540015EE29 /* CheckValidityVC.xib */; }; + 5C50CF11269700710015EE29 /* RuleValidationResultVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF0F269700710015EE29 /* RuleValidationResultVC.swift */; }; + 5C50CF12269700710015EE29 /* RuleValidationResultVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF0F269700710015EE29 /* RuleValidationResultVC.swift */; }; + 5C50CF13269700710015EE29 /* RuleValidationResultVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF0F269700710015EE29 /* RuleValidationResultVC.swift */; }; + 5C50CF14269700710015EE29 /* RuleValidationResultVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF10269700710015EE29 /* RuleValidationResultVC.xib */; }; + 5C50CF15269700710015EE29 /* RuleValidationResultVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF10269700710015EE29 /* RuleValidationResultVC.xib */; }; + 5C50CF16269700710015EE29 /* RuleValidationResultVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF10269700710015EE29 /* RuleValidationResultVC.xib */; }; + 5C50CF1B2697023B0015EE29 /* RuleErrorTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF192697023B0015EE29 /* RuleErrorTVC.swift */; }; + 5C50CF1C2697023B0015EE29 /* RuleErrorTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF192697023B0015EE29 /* RuleErrorTVC.swift */; }; + 5C50CF1D2697023B0015EE29 /* RuleErrorTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF192697023B0015EE29 /* RuleErrorTVC.swift */; }; + 5C50CF1E2697023B0015EE29 /* RuleErrorTVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF1A2697023B0015EE29 /* RuleErrorTVC.xib */; }; + 5C50CF1F2697023B0015EE29 /* RuleErrorTVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF1A2697023B0015EE29 /* RuleErrorTVC.xib */; }; + 5C50CF202697023B0015EE29 /* RuleErrorTVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF1A2697023B0015EE29 /* RuleErrorTVC.xib */; }; + 5C50CF22269704240015EE29 /* UIViewController+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF21269704240015EE29 /* UIViewController+.swift */; }; + 5C50CF23269704240015EE29 /* UIViewController+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF21269704240015EE29 /* UIViewController+.swift */; }; + 5C50CF24269704240015EE29 /* UIViewController+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF21269704240015EE29 /* UIViewController+.swift */; }; + 5C50CF2726972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF2526972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift */; }; + 5C50CF2826972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF2526972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift */; }; + 5C50CF2926972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF2526972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift */; }; + 5C50CF2A26972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF2626972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib */; }; + 5C50CF2B26972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF2626972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib */; }; + 5C50CF2C26972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF2626972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib */; }; + 5C50CF2F26972BFD0015EE29 /* CellWithDateAndCountryTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF2D26972BFD0015EE29 /* CellWithDateAndCountryTVC.swift */; }; + 5C50CF3026972BFD0015EE29 /* CellWithDateAndCountryTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF2D26972BFD0015EE29 /* CellWithDateAndCountryTVC.swift */; }; + 5C50CF3126972BFD0015EE29 /* CellWithDateAndCountryTVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF2D26972BFD0015EE29 /* CellWithDateAndCountryTVC.swift */; }; + 5C50CF3226972BFD0015EE29 /* CellWithDateAndCountryTVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF2E26972BFD0015EE29 /* CellWithDateAndCountryTVC.xib */; }; + 5C50CF3326972BFD0015EE29 /* CellWithDateAndCountryTVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF2E26972BFD0015EE29 /* CellWithDateAndCountryTVC.xib */; }; + 5C50CF3426972BFD0015EE29 /* CellWithDateAndCountryTVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C50CF2E26972BFD0015EE29 /* CellWithDateAndCountryTVC.xib */; }; + 5C50CF362697315B0015EE29 /* ValidityCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF352697315B0015EE29 /* ValidityCellModel.swift */; }; + 5C50CF372697315B0015EE29 /* ValidityCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF352697315B0015EE29 /* ValidityCellModel.swift */; }; + 5C50CF382697315B0015EE29 /* ValidityCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF352697315B0015EE29 /* ValidityCellModel.swift */; }; + 5C50CF3A26973D9D0015EE29 /* UserDefaults+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF3926973D9D0015EE29 /* UserDefaults+.swift */; }; + 5C50CF3B26973D9D0015EE29 /* UserDefaults+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF3926973D9D0015EE29 /* UserDefaults+.swift */; }; + 5C50CF3C26973D9D0015EE29 /* UserDefaults+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF3926973D9D0015EE29 /* UserDefaults+.swift */; }; + 5C50CF3E26983E090015EE29 /* UITableView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF3D26983E090015EE29 /* UITableView+.swift */; }; + 5C50CF3F26983E090015EE29 /* UITableView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF3D26983E090015EE29 /* UITableView+.swift */; }; + 5C50CF4026983E090015EE29 /* UITableView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF3D26983E090015EE29 /* UITableView+.swift */; }; + 5CE07C762692E0D900A032F9 /* CertLogic in Frameworks */ = {isa = PBXBuildFile; productRef = 5CE07C752692E0D900A032F9 /* CertLogic */; }; + 5CE07C7A2692E1CD00A032F9 /* CountryDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C772692E1CC00A032F9 /* CountryDataStorage.swift */; }; + 5CE07C7B2692E1CD00A032F9 /* CountryDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C772692E1CC00A032F9 /* CountryDataStorage.swift */; }; + 5CE07C7C2692E1CD00A032F9 /* CountryDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C772692E1CC00A032F9 /* CountryDataStorage.swift */; }; + 5CE07C7D2692E1CD00A032F9 /* ValueSetsDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C782692E1CD00A032F9 /* ValueSetsDataStorage.swift */; }; + 5CE07C7E2692E1CD00A032F9 /* ValueSetsDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C782692E1CD00A032F9 /* ValueSetsDataStorage.swift */; }; + 5CE07C7F2692E1CD00A032F9 /* ValueSetsDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C782692E1CD00A032F9 /* ValueSetsDataStorage.swift */; }; + 5CE07C802692E1CD00A032F9 /* RulesDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C792692E1CD00A032F9 /* RulesDataStorage.swift */; }; + 5CE07C812692E1CD00A032F9 /* RulesDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C792692E1CD00A032F9 /* RulesDataStorage.swift */; }; + 5CE07C822692E1CD00A032F9 /* RulesDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C792692E1CD00A032F9 /* RulesDataStorage.swift */; }; + 5CE07C842692E21E00A032F9 /* CertLogicEngineManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C832692E21E00A032F9 /* CertLogicEngineManager.swift */; }; + 5CE07C852692E21E00A032F9 /* CertLogicEngineManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C832692E21E00A032F9 /* CertLogicEngineManager.swift */; }; + 5CE07C862692E21E00A032F9 /* CertLogicEngineManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C832692E21E00A032F9 /* CertLogicEngineManager.swift */; }; + 5CEF300526A5CDDD005ED86B /* context.jsonc in Resources */ = {isa = PBXBuildFile; fileRef = 5CFEDB18269DBB4C00074F66 /* context.jsonc */; }; + 5CEF300626A5CDDD005ED86B /* context.jsonc in Resources */ = {isa = PBXBuildFile; fileRef = 5CFEDB18269DBB4C00074F66 /* context.jsonc */; }; + 5CEF300726A5CDDE005ED86B /* context.jsonc in Resources */ = {isa = PBXBuildFile; fileRef = 5CFEDB18269DBB4C00074F66 /* context.jsonc */; }; CE13CF00262DCC180070C80E /* FloatingPanel in Frameworks */ = {isa = PBXBuildFile; productRef = CE13CEFF262DCC180070C80E /* FloatingPanel */; }; CE13CF0A262DCDDA0070C80E /* CertificateViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE13CF09262DCDDA0070C80E /* CertificateViewer.swift */; }; CE13CF0F262DD0D80070C80E /* FullFloatingPanelLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE13CF0E262DD0D80070C80E /* FullFloatingPanelLayout.swift */; }; @@ -37,7 +95,6 @@ CEA6D6F8261F8D2900715333 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CEA6D6F6261F8D2900715333 /* LaunchScreen.storyboard */; }; CEA6D703261F8D2900715333 /* DGCAWalletTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6D702261F8D2900715333 /* DGCAWalletTests.swift */; }; CEA6D70E261F8D2900715333 /* DGCAWalletUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6D70D261F8D2900715333 /* DGCAWalletUITests.swift */; }; - CEC7FEDF264C5A41005561BA /* context.jsonc in Resources */ = {isa = PBXBuildFile; fileRef = CEC7FEDE264C5A41005561BA /* context.jsonc */; }; CED2726026398683003D47A9 /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED2725F26398683003D47A9 /* UIFont.swift */; }; CED949CA263B50CE00883558 /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED949C9263B50CE00883558 /* List.swift */; }; CEDABD40263C5FF4007A9B97 /* CertTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDABD3F263C5FF4007A9B97 /* CertTable.swift */; }; @@ -70,6 +127,26 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 5C50CF07269700540015EE29 /* CheckValidityVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckValidityVC.swift; sourceTree = ""; }; + 5C50CF08269700540015EE29 /* CheckValidityVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CheckValidityVC.xib; sourceTree = ""; }; + 5C50CF0F269700710015EE29 /* RuleValidationResultVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleValidationResultVC.swift; sourceTree = ""; }; + 5C50CF10269700710015EE29 /* RuleValidationResultVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RuleValidationResultVC.xib; sourceTree = ""; }; + 5C50CF192697023B0015EE29 /* RuleErrorTVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleErrorTVC.swift; sourceTree = ""; }; + 5C50CF1A2697023B0015EE29 /* RuleErrorTVC.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RuleErrorTVC.xib; sourceTree = ""; }; + 5C50CF21269704240015EE29 /* UIViewController+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+.swift"; sourceTree = ""; }; + 5C50CF2526972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellWithTitleAndDescriptionTVC.swift; sourceTree = ""; }; + 5C50CF2626972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CellWithTitleAndDescriptionTVC.xib; sourceTree = ""; }; + 5C50CF2D26972BFD0015EE29 /* CellWithDateAndCountryTVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellWithDateAndCountryTVC.swift; sourceTree = ""; }; + 5C50CF2E26972BFD0015EE29 /* CellWithDateAndCountryTVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CellWithDateAndCountryTVC.xib; sourceTree = ""; }; + 5C50CF352697315B0015EE29 /* ValidityCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidityCellModel.swift; sourceTree = ""; }; + 5C50CF3926973D9D0015EE29 /* UserDefaults+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+.swift"; sourceTree = ""; }; + 5C50CF3D26983E090015EE29 /* UITableView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+.swift"; sourceTree = ""; }; + 5CE07C772692E1CC00A032F9 /* CountryDataStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountryDataStorage.swift; sourceTree = ""; }; + 5CE07C782692E1CD00A032F9 /* ValueSetsDataStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueSetsDataStorage.swift; sourceTree = ""; }; + 5CE07C792692E1CD00A032F9 /* RulesDataStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulesDataStorage.swift; sourceTree = ""; }; + 5CE07C832692E21E00A032F9 /* CertLogicEngineManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CertLogicEngineManager.swift; sourceTree = ""; }; + 5CFEDB18269DBB4C00074F66 /* context.jsonc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = context.jsonc; sourceTree = ""; }; + 5CFEDB1A269DBB4C00074F66 /* context.jsonc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = context.jsonc; sourceTree = ""; }; CE13CF09262DCDDA0070C80E /* CertificateViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateViewer.swift; sourceTree = ""; }; CE13CF0E262DD0D80070C80E /* FullFloatingPanelLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullFloatingPanelLayout.swift; sourceTree = ""; }; CE13CF22262DDF810070C80E /* RoundedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedButton.swift; sourceTree = ""; }; @@ -101,7 +178,6 @@ CEA6D709261F8D2900715333 /* DGCAWalletUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DGCAWalletUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; CEA6D70D261F8D2900715333 /* DGCAWalletUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DGCAWalletUITests.swift; sourceTree = ""; }; CEA6D70F261F8D2900715333 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - CEC7FEDE264C5A41005561BA /* context.jsonc */ = {isa = PBXFileReference; lastKnownFileType = text; path = context.jsonc; sourceTree = SOURCE_ROOT; }; CED2725F26398683003D47A9 /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = ""; }; CED949C9263B50CE00883558 /* List.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = List.swift; sourceTree = ""; }; CEDABD3F263C5FF4007A9B97 /* CertTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertTable.swift; sourceTree = ""; }; @@ -123,6 +199,7 @@ buildActionMask = 2147483647; files = ( CE8912FB2634C6B900CB92AF /* Alamofire in Frameworks */, + 5CE07C762692E0D900A032F9 /* CertLogic in Frameworks */, CE56B5052639F48E00FB31B1 /* SwiftDGC in Frameworks */, CE6B330E2651A54800845B8E /* SwiftDGC in Frameworks */, CE1F155C2639F9E700736D48 /* SwiftyJSON in Frameworks */, @@ -147,6 +224,51 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5C50CF172697009E0015EE29 /* CheckValidityCells */ = { + isa = PBXGroup; + children = ( + 5C50CF2526972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift */, + 5C50CF2626972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib */, + 5C50CF2D26972BFD0015EE29 /* CellWithDateAndCountryTVC.swift */, + 5C50CF2E26972BFD0015EE29 /* CellWithDateAndCountryTVC.xib */, + ); + path = CheckValidityCells; + sourceTree = ""; + }; + 5C50CF18269700B30015EE29 /* RuleCell */ = { + isa = PBXGroup; + children = ( + 5C50CF192697023B0015EE29 /* RuleErrorTVC.swift */, + 5C50CF1A2697023B0015EE29 /* RuleErrorTVC.xib */, + ); + path = RuleCell; + sourceTree = ""; + }; + 5CFEDB16269DBB4C00074F66 /* Context */ = { + isa = PBXGroup; + children = ( + 5CFEDB17269DBB4C00074F66 /* Test */, + 5CFEDB19269DBB4C00074F66 /* Release */, + ); + path = Context; + sourceTree = SOURCE_ROOT; + }; + 5CFEDB17269DBB4C00074F66 /* Test */ = { + isa = PBXGroup; + children = ( + 5CFEDB18269DBB4C00074F66 /* context.jsonc */, + ); + path = Test; + sourceTree = ""; + }; + 5CFEDB19269DBB4C00074F66 /* Release */ = { + isa = PBXGroup; + children = ( + 5CFEDB1A269DBB4C00074F66 /* context.jsonc */, + ); + path = Release; + sourceTree = ""; + }; CE13CF19262DDE200070C80E /* Storyboards */ = { isa = PBXGroup; children = ( @@ -165,6 +287,7 @@ CE37B642263867D700DEE13D /* SecureBackground.swift */, CEE9DA5C263C865200A31532 /* Brightness.swift */, CE56BC06264068110044FD3F /* GatewayConnection.swift */, + 5CE07C832692E21E00A032F9 /* CertLogicEngineManager.swift */, ); path = Services; sourceTree = ""; @@ -174,6 +297,9 @@ children = ( CED2725F26398683003D47A9 /* UIFont.swift */, CE8096D8263B07BB00A65AD6 /* UIColor.swift */, + 5C50CF21269704240015EE29 /* UIViewController+.swift */, + 5C50CF3926973D9D0015EE29 /* UserDefaults+.swift */, + 5C50CF3D26983E090015EE29 /* UITableView+.swift */, ); path = Extensions; sourceTree = ""; @@ -191,6 +317,10 @@ CE4BD253264FF28900689FD6 /* Settings.swift */, DA01661B265541C0005B73A1 /* LicenseList.swift */, DA01662126558B2F005B73A1 /* LicenseVC.swift */, + 5C50CF07269700540015EE29 /* CheckValidityVC.swift */, + 5C50CF08269700540015EE29 /* CheckValidityVC.xib */, + 5C50CF0F269700710015EE29 /* RuleValidationResultVC.swift */, + 5C50CF10269700710015EE29 /* RuleValidationResultVC.xib */, ); path = ViewControllers; sourceTree = ""; @@ -198,6 +328,8 @@ CE13CF1D262DDE730070C80E /* Components */ = { isa = PBXGroup; children = ( + 5C50CF18269700B30015EE29 /* RuleCell */, + 5C50CF172697009E0015EE29 /* CheckValidityCells */, CE13CF0E262DD0D80070C80E /* FullFloatingPanelLayout.swift */, CE13CF22262DDF810070C80E /* RoundedButton.swift */, CE260F55263DD1720083A200 /* WalletCell.swift */, @@ -211,6 +343,7 @@ CE13CF1E262DDE800070C80E /* SupportingFiles */ = { isa = PBXGroup; children = ( + 5CFEDB16269DBB4C00074F66 /* Context */, CE815339263FF7EC0030D777 /* README.md */, CEDCA79B2639E77800FCB83E /* Package.resolved */, CEA6D6EB261F8D2700715333 /* AppDelegate.swift */, @@ -218,7 +351,6 @@ CEA6D6F4261F8D2900715333 /* Assets.xcassets */, CEA6D6F9261F8D2900715333 /* Info.plist */, CE6D4A46264835F100A5D33D /* Localizable.strings */, - CEC7FEDE264C5A41005561BA /* context.jsonc */, DA01661D26554992005B73A1 /* OpenSourceNotices.json */, ); path = SupportingFiles; @@ -228,6 +360,10 @@ isa = PBXGroup; children = ( CE1D1EF5263597A2004C8919 /* LocalData.swift */, + 5CE07C772692E1CC00A032F9 /* CountryDataStorage.swift */, + 5CE07C792692E1CD00A032F9 /* RulesDataStorage.swift */, + 5CE07C782692E1CD00A032F9 /* ValueSetsDataStorage.swift */, + 5C50CF352697315B0015EE29 /* ValidityCellModel.swift */, ); path = Models; sourceTree = ""; @@ -325,6 +461,7 @@ CE56B5042639F48E00FB31B1 /* SwiftDGC */, CE1F155B2639F9E700736D48 /* SwiftyJSON */, CE6B330D2651A54800845B8E /* SwiftDGC */, + 5CE07C752692E0D900A032F9 /* CertLogic */, ); productName = DGCAWallet; productReference = CEA6D6E8261F8D2700715333 /* DGCAWallet.app */; @@ -402,6 +539,7 @@ CE8912F92634C6B900CB92AF /* XCRemoteSwiftPackageReference "Alamofire" */, CE1F155A2639F9E700736D48 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, CE6B330C2651A54800845B8E /* XCRemoteSwiftPackageReference "dgca-app-core-ios" */, + 5CE07C742692E0D900A032F9 /* XCRemoteSwiftPackageReference "dgc-certlogic-ios" */, ); productRefGroup = CEA6D6E9261F8D2700715333 /* Products */; projectDirPath = ""; @@ -421,14 +559,19 @@ files = ( CE4BD258264FF39D00689FD6 /* CertificateViewer.storyboard in Resources */, CE6D4A44264835F100A5D33D /* Localizable.strings in Resources */, + 5C50CF3226972BFD0015EE29 /* CellWithDateAndCountryTVC.xib in Resources */, CEA6D6F8261F8D2900715333 /* LaunchScreen.storyboard in Resources */, CE4BD255264FF39400689FD6 /* Settings.storyboard in Resources */, - CEC7FEDF264C5A41005561BA /* context.jsonc in Resources */, CE81533A263FF7EC0030D777 /* README.md in Resources */, + 5C50CF1E2697023B0015EE29 /* RuleErrorTVC.xib in Resources */, DA01661E26554992005B73A1 /* OpenSourceNotices.json in Resources */, CEA6D6F5261F8D2900715333 /* Assets.xcassets in Resources */, + 5C50CF0C269700550015EE29 /* CheckValidityVC.xib in Resources */, CEA6D6F3261F8D2700715333 /* Main.storyboard in Resources */, + 5C50CF14269700710015EE29 /* RuleValidationResultVC.xib in Resources */, + 5CEF300526A5CDDD005ED86B /* context.jsonc in Resources */, DA017DE626552259006E4D49 /* Licenses.storyboard in Resources */, + 5C50CF2A26972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -436,6 +579,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5C50CF15269700710015EE29 /* RuleValidationResultVC.xib in Resources */, + 5C50CF2B26972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib in Resources */, + 5CEF300626A5CDDD005ED86B /* context.jsonc in Resources */, + 5C50CF3326972BFD0015EE29 /* CellWithDateAndCountryTVC.xib in Resources */, + 5C50CF0D269700550015EE29 /* CheckValidityVC.xib in Resources */, + 5C50CF1F2697023B0015EE29 /* RuleErrorTVC.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -443,6 +592,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5C50CF16269700710015EE29 /* RuleValidationResultVC.xib in Resources */, + 5C50CF2C26972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib in Resources */, + 5CEF300726A5CDDE005ED86B /* context.jsonc in Resources */, + 5C50CF3426972BFD0015EE29 /* CellWithDateAndCountryTVC.xib in Resources */, + 5C50CF0E269700550015EE29 /* CheckValidityVC.xib in Resources */, + 5C50CF202697023B0015EE29 /* RuleErrorTVC.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -474,20 +629,25 @@ buildActionMask = 2147483647; files = ( CEE9DA5D263C865200A31532 /* Brightness.swift in Sources */, + 5C50CF362697315B0015EE29 /* ValidityCellModel.swift in Sources */, + 5C50CF3A26973D9D0015EE29 /* UserDefaults+.swift in Sources */, CE13CF0A262DCDDA0070C80E /* CertificateViewer.swift in Sources */, CEDABD40263C5FF4007A9B97 /* CertTable.swift in Sources */, CE37B643263867D700DEE13D /* SecureBackground.swift in Sources */, CE13CF0F262DD0D80070C80E /* FullFloatingPanelLayout.swift in Sources */, + 5CE07C7D2692E1CD00A032F9 /* ValueSetsDataStorage.swift in Sources */, CEA1556B262F784E0024B7AC /* SelfSizedTableView.swift in Sources */, CE8096D9263B07BB00A65AD6 /* UIColor.swift in Sources */, CEDABD49263C70EF007A9B97 /* CertPages.swift in Sources */, CE4BD254264FF28900689FD6 /* Settings.swift in Sources */, CE1D1EF6263597A2004C8919 /* LocalData.swift in Sources */, + 5CE07C842692E21E00A032F9 /* CertLogicEngineManager.swift in Sources */, CED2726026398683003D47A9 /* UIFont.swift in Sources */, CEE9DA55263C7D4000A31532 /* CertCode.swift in Sources */, CE56BC07264068110044FD3F /* GatewayConnection.swift in Sources */, CEA6D6F0261F8D2700715333 /* Scan.swift in Sources */, CE13CF23262DDF810070C80E /* RoundedButton.swift in Sources */, + 5C50CF2726972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift in Sources */, DA01662026554E02005B73A1 /* LicenseCell.swift in Sources */, CEA6D6EC261F8D2700715333 /* AppDelegate.swift in Sources */, DA01661C265541C0005B73A1 /* LicenseList.swift in Sources */, @@ -495,9 +655,17 @@ CEA15563262F6DAB0024B7AC /* CertViewerDelegate.swift in Sources */, CE260F56263DD1720083A200 /* WalletCell.swift in Sources */, CEA15570262F79DE0024B7AC /* InfoCell.swift in Sources */, + 5CE07C7A2692E1CD00A032F9 /* CountryDataStorage.swift in Sources */, CED949CA263B50CE00883558 /* List.swift in Sources */, + 5C50CF11269700710015EE29 /* RuleValidationResultVC.swift in Sources */, DA01662226558B2F005B73A1 /* LicenseVC.swift in Sources */, + 5C50CF1B2697023B0015EE29 /* RuleErrorTVC.swift in Sources */, + 5C50CF09269700550015EE29 /* CheckValidityVC.swift in Sources */, + 5C50CF3E26983E090015EE29 /* UITableView+.swift in Sources */, CEA6D6EE261F8D2700715333 /* SceneDelegate.swift in Sources */, + 5C50CF22269704240015EE29 /* UIViewController+.swift in Sources */, + 5CE07C802692E1CD00A032F9 /* RulesDataStorage.swift in Sources */, + 5C50CF2F26972BFD0015EE29 /* CellWithDateAndCountryTVC.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -505,8 +673,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5C50CF2826972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift in Sources */, + 5C50CF0A269700550015EE29 /* CheckValidityVC.swift in Sources */, + 5C50CF3B26973D9D0015EE29 /* UserDefaults+.swift in Sources */, + 5C50CF3F26983E090015EE29 /* UITableView+.swift in Sources */, + 5C50CF1C2697023B0015EE29 /* RuleErrorTVC.swift in Sources */, + 5CE07C7E2692E1CD00A032F9 /* ValueSetsDataStorage.swift in Sources */, + 5C50CF12269700710015EE29 /* RuleValidationResultVC.swift in Sources */, CEA6D703261F8D2900715333 /* DGCAWalletTests.swift in Sources */, + 5C50CF23269704240015EE29 /* UIViewController+.swift in Sources */, + 5CE07C852692E21E00A032F9 /* CertLogicEngineManager.swift in Sources */, + 5C50CF372697315B0015EE29 /* ValidityCellModel.swift in Sources */, + 5C50CF3026972BFD0015EE29 /* CellWithDateAndCountryTVC.swift in Sources */, + 5CE07C812692E1CD00A032F9 /* RulesDataStorage.swift in Sources */, CEFAD87F262714C4009AFEF9 /* EHNTests.swift in Sources */, + 5CE07C7B2692E1CD00A032F9 /* CountryDataStorage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -514,7 +695,20 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5CE07C7C2692E1CD00A032F9 /* CountryDataStorage.swift in Sources */, + 5C50CF382697315B0015EE29 /* ValidityCellModel.swift in Sources */, + 5C50CF3C26973D9D0015EE29 /* UserDefaults+.swift in Sources */, + 5C50CF4026983E090015EE29 /* UITableView+.swift in Sources */, CEA6D70E261F8D2900715333 /* DGCAWalletUITests.swift in Sources */, + 5CE07C7F2692E1CD00A032F9 /* ValueSetsDataStorage.swift in Sources */, + 5C50CF3126972BFD0015EE29 /* CellWithDateAndCountryTVC.swift in Sources */, + 5C50CF0B269700550015EE29 /* CheckValidityVC.swift in Sources */, + 5C50CF1D2697023B0015EE29 /* RuleErrorTVC.swift in Sources */, + 5CE07C862692E21E00A032F9 /* CertLogicEngineManager.swift in Sources */, + 5C50CF13269700710015EE29 /* RuleValidationResultVC.swift in Sources */, + 5C50CF24269704240015EE29 /* UIViewController+.swift in Sources */, + 5CE07C822692E1CD00A032F9 /* RulesDataStorage.swift in Sources */, + 5C50CF2926972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -698,17 +892,20 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = KH99XNF745; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_TEAM = 9FH6TNGWF2; INFOPLIST_FILE = DGCAWallet/SupportingFiles/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.4; - PRODUCT_BUNDLE_IDENTIFIER = "com.t-systems.DGCAWallet.dev"; + MARKETING_VERSION = 1.1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.t-systems.pu-ds.dgca.wallet.dev"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "Digital Green Certificate Wallet"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; @@ -719,17 +916,20 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = KH99XNF745; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_TEAM = 9FH6TNGWF2; INFOPLIST_FILE = DGCAWallet/SupportingFiles/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.4; - PRODUCT_BUNDLE_IDENTIFIER = "com.t-systems.DGCAWallet.dev"; + MARKETING_VERSION = 1.1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.t-systems.pu-ds.dgca.wallet.dev"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "Digital Green Certificate Wallet"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; @@ -861,6 +1061,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 5CE07C742692E0D900A032F9 /* XCRemoteSwiftPackageReference "dgc-certlogic-ios" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/eu-digital-green-certificates/dgc-certlogic-ios.git"; + requirement = { + branch = main; + kind = branch; + }; + }; CE13CEFE262DCC180070C80E /* XCRemoteSwiftPackageReference "FloatingPanel" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SCENEE/FloatingPanel"; @@ -881,7 +1089,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/eu-digital-green-certificates/dgca-app-core-ios"; requirement = { - branch = main; + branch = bugfix/hc1; kind = branch; }; }; @@ -896,6 +1104,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 5CE07C752692E0D900A032F9 /* CertLogic */ = { + isa = XCSwiftPackageProductDependency; + package = 5CE07C742692E0D900A032F9 /* XCRemoteSwiftPackageReference "dgc-certlogic-ios" */; + productName = CertLogic; + }; CE13CEFF262DCC180070C80E /* FloatingPanel */ = { isa = XCSwiftPackageProductDependency; package = CE13CEFE262DCC180070C80E /* XCRemoteSwiftPackageReference "FloatingPanel" */; diff --git a/DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d8e0f15..a8e2293 100644 --- a/DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -19,12 +19,21 @@ "version": "1.8.0" } }, + { + "package": "CertLogic", + "repositoryURL": "https://github.com/eu-digital-green-certificates/dgc-certlogic-ios.git", + "state": { + "branch": "main", + "revision": "8b7f86a2aed4f2c60dd2c7c4f28eb129d9fe379d", + "version": null + } + }, { "package": "SwiftDGC", "repositoryURL": "https://github.com/eu-digital-green-certificates/dgca-app-core-ios", "state": { - "branch": "main", - "revision": "96a5bf0ec02fc56bb5c59a4fd579f381f5da9a55", + "branch": "bugfix/hc1", + "revision": "629f1a31adfbf62bd37a165558ce1a37492c05a2", "version": null } }, @@ -37,6 +46,15 @@ "version": "2.4.0" } }, + { + "package": "jsonlogic", + "repositoryURL": "https://github.com/eu-digital-green-certificates/json-logic-swift.git", + "state": { + "branch": null, + "revision": "8dc339147b27ae633a1857503ed8d091c7935d0d", + "version": "1.1.5" + } + }, { "package": "JSONSchema", "repositoryURL": "https://github.com/eu-digital-green-certificates/JSONSchema.swift", diff --git a/DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.swift b/DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.swift new file mode 100644 index 0000000..a7cbbaf --- /dev/null +++ b/DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.swift @@ -0,0 +1,132 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-wallet-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// CellWithDateAndCountryTVC.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 08.07.2021. +// + + +import UIKit +import SwiftDGC + +public typealias OnDateChangedHandler = (Date) -> Void +public typealias OnCountryChangedHandler = (String?) -> Void + +class CellWithDateAndCountryTVC: UITableViewCell { + @IBOutlet weak var destinationLabel: UILabel! + @IBOutlet weak var countryPicker: UIPickerView! + @IBOutlet weak var dateLabel: UILabel! + @IBOutlet weak var datePicker: UIDatePicker! + private var countryItems: [CountryModel] = [] + var dataHandler: OnDateChangedHandler? + var countryHandler: OnCountryChangedHandler? + // Selected country code + private var selectedCounty: CountryModel? { + set { + let userDefaults = UserDefaults.standard + do { + try userDefaults.setObject(newValue, forKey: Constants.userDefaultsCountryKey) + } catch { + print(error.localizedDescription) + } + } + get { + let userDefaults = UserDefaults.standard + do { + let selected = try userDefaults.getObject(forKey: Constants.userDefaultsCountryKey, castTo: CountryModel.self) + return selected + } catch { + print(error.localizedDescription) + return nil + } + } + } + func setupView() { + destinationLabel.text = l10n("destination_country") + dateLabel.text = l10n("destination_date") + countryPicker.delegate = self + countryPicker.dataSource = self + datePicker.minimumDate = Date() + if #available(iOS 13.4, *) { + datePicker.preferredDatePickerStyle = .wheels + } else { + // Fallback on earlier versions + } + datePicker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged) + GatewayConnection.countryList { countryList in + DispatchQueue.main.async { + self.setListOfRuleCounties(list: countryList) + } + } + } +} + +extension CellWithDateAndCountryTVC { + @objc func dateChanged(_ sender: UIDatePicker) { + dataHandler?(sender.date) + } +} + +extension CellWithDateAndCountryTVC: UIPickerViewDataSource, UIPickerViewDelegate { + public func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + if countryItems.count == 0 { return 1 } + return countryItems.count + } + public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + if countryItems.count == 0 { return l10n("scaner.no.countrys") } + return countryItems[row].name + } + public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + self.selectedCounty = countryItems[row] + countryHandler?(self.selectedCounty?.code) + } +} + +extension CellWithDateAndCountryTVC { + public func setListOfRuleCounties(list: [CountryModel]) { + self.countryItems = list + self.countryPicker.reloadAllComponents() + guard self.countryItems.count > 0 else { return } + if let selected = self.selectedCounty, + let indexOfCountry = self.countryItems.firstIndex(where: {$0.code == selected.code}) { + countryPicker.selectRow(indexOfCountry, inComponent: 0, animated: false) + countryHandler?(selected.code) + } else { + self.selectedCounty = self.countryItems.first + countryPicker.selectRow(0, inComponent: 0, animated: false) + countryHandler?(self.selectedCounty?.code) + } + } + public func getSelectedCountryCode() -> String? { + return self.selectedCounty?.code + } +} + +extension CellWithDateAndCountryTVC { + private enum Constants { + static let userDefaultsCountryKey = "UDWalletCountryKey" + } +} diff --git a/DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.xib b/DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.xib new file mode 100644 index 0000000..fcae5a3 --- /dev/null +++ b/DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.xib @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DGCAWallet/Components/CheckValidityCells/CellWithTitleAndDescriptionTVC.swift b/DGCAWallet/Components/CheckValidityCells/CellWithTitleAndDescriptionTVC.swift new file mode 100644 index 0000000..a2ec47d --- /dev/null +++ b/DGCAWallet/Components/CheckValidityCells/CellWithTitleAndDescriptionTVC.swift @@ -0,0 +1,74 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-wallet-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// CellWithTitleAndDescriptionTVC.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 08.07.2021. +// + + +import UIKit + +class CellWithTitleAndDescriptionTVC: UITableViewCell { + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var descriptionLabel: UILabel! + + private weak var cellModel: ValidityCellModel? { + didSet { + self.setupView() + } + } + + override func awakeFromNib() { + super.awakeFromNib() + setInitialStrings() + } + + override func prepareForReuse() { + super.prepareForReuse() + setInitialStrings() + } + + func setupCell(with model: ValidityCellModel) { + self.cellModel = model + } + + private func setInitialStrings() { + titleLabel.text = "" + descriptionLabel.text = "" + } + + private func setupView() { + guard let cellModel = cellModel else { + setInitialStrings() + return + } + titleLabel.text = cellModel.title + descriptionLabel.text = cellModel.description + if cellModel.needChangeTitleFont { + titleLabel.font = UIFont.boldSystemFont(ofSize: 24) + } else { + titleLabel.font = UIFont.boldSystemFont(ofSize: 14) + } + } + +} diff --git a/DGCAWallet/Components/CheckValidityCells/CellWithTitleAndDescriptionTVC.xib b/DGCAWallet/Components/CheckValidityCells/CellWithTitleAndDescriptionTVC.xib new file mode 100644 index 0000000..3d95d6f --- /dev/null +++ b/DGCAWallet/Components/CheckValidityCells/CellWithTitleAndDescriptionTVC.xib @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DGCAWallet/Components/RuleCell/RuleErrorTVC.swift b/DGCAWallet/Components/RuleCell/RuleErrorTVC.swift new file mode 100644 index 0000000..8f4a69c --- /dev/null +++ b/DGCAWallet/Components/RuleCell/RuleErrorTVC.swift @@ -0,0 +1,100 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-wallet-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// RuleErrorTVC.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 05.07.2021. +// + +import UIKit +import SwiftDGC + +class RuleErrorTVC: UITableViewCell { + + @IBOutlet weak var ruleLabel: UILabel! + @IBOutlet weak var ruleValueLabel: UILabel! + @IBOutlet weak var currentLabel: UILabel! + @IBOutlet weak var currentValueLabel: UILabel! + @IBOutlet weak var resultLabel: UILabel! + @IBOutlet weak var resultValueLabel: UILabel! + @IBOutlet weak var failedLabel: UILabel! + private var infoItem: InfoSection? { + didSet { + setupView() + } + } + override func awakeFromNib() { + super.awakeFromNib() + setLabels() + } + + override func prepareForReuse() { + setLabels() + } + private func setLabels() { + ruleLabel.text = l10n("rule") + ruleValueLabel.text = "" + currentLabel.text = l10n("current") + currentValueLabel.text = "" + resultLabel.text = l10n("result") + resultValueLabel.text = "" + } + private func setupView() { + guard let infoItem = infoItem else { return } + ruleValueLabel.text = infoItem.header + currentValueLabel.text = infoItem.content + switch infoItem.ruleValidationResult { + case .error: + failedLabel.textColor = .red + failedLabel.text = l10n("failed") + case .passed: + failedLabel.textColor = .green + failedLabel.text = l10n("passed") + case .open: + failedLabel.textColor = .green + failedLabel.text = l10n("open") + } + + if let countryName = infoItem.countryName { + switch infoItem.ruleValidationResult { + case .error: + resultValueLabel.text = String(format: l10n("failed_for_country"), countryName) + case .passed: + resultValueLabel.text = String(format: l10n("passed_for_country"), countryName) + case .open: + resultValueLabel.text = String(format: l10n("open_for_country"), countryName) + } + } else { + switch infoItem.ruleValidationResult { + case .error: + resultValueLabel.text = l10n("failed") + case .passed: + resultValueLabel.text = l10n("passed") + case .open: + resultValueLabel.text = l10n("open") + } + } + } + public func setupCell(with info: InfoSection) { + self.infoItem = info + } +} diff --git a/DGCAWallet/Components/RuleCell/RuleErrorTVC.xib b/DGCAWallet/Components/RuleCell/RuleErrorTVC.xib new file mode 100644 index 0000000..55704dd --- /dev/null +++ b/DGCAWallet/Components/RuleCell/RuleErrorTVC.xib @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DGCAWallet/Extensions/UITableView+.swift b/DGCAWallet/Extensions/UITableView+.swift new file mode 100644 index 0000000..2a120e1 --- /dev/null +++ b/DGCAWallet/Extensions/UITableView+.swift @@ -0,0 +1,46 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-wallet-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// UITableView+.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 09.07.2021. +// +import UIKit + +extension UITableView { + func setEmptyMessage(_ message: String) { + let messageLabel = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height)) + messageLabel.text = message + messageLabel.textColor = .black + messageLabel.numberOfLines = 0 + messageLabel.textAlignment = .center + messageLabel.font = UIFont.systemFont(ofSize: 16) + messageLabel.sizeToFit() + + self.backgroundView = messageLabel + self.separatorStyle = .none + } + func restore() { + self.backgroundView = nil + self.separatorStyle = .singleLine + } +} diff --git a/DGCAWallet/Extensions/UIViewController+.swift b/DGCAWallet/Extensions/UIViewController+.swift new file mode 100644 index 0000000..aa00eb0 --- /dev/null +++ b/DGCAWallet/Extensions/UIViewController+.swift @@ -0,0 +1,38 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-wallet-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// File.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 08.07.2021. +// + + +import UIKit + +extension UIViewController { + static func loadFromNib() -> Self { + func instantiateFromNib() -> T { + return T.init(nibName: String(describing: T.self), bundle: Bundle.init(for: Self.self)) + } + return instantiateFromNib() + } +} diff --git a/DGCAWallet/Extensions/UserDefaults+.swift b/DGCAWallet/Extensions/UserDefaults+.swift new file mode 100644 index 0000000..189cbfe --- /dev/null +++ b/DGCAWallet/Extensions/UserDefaults+.swift @@ -0,0 +1,68 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-wallet-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// UserDefaults+.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 08.07.2021. +// + + +import Foundation + +enum ObjectSavableError: String, LocalizedError { + case unableToEncode = "Unable to encode object into data" + case noValue = "No data object found for the given key" + case unableToDecode = "Unable to decode object into given type" + + var errorDescription: String? { + rawValue + } +} + + +protocol ObjectSavable { + func setObject(_ object: Object, forKey: String) throws where Object: Encodable + func getObject(forKey: String, castTo type: Object.Type) throws -> Object where Object: Decodable +} + +extension UserDefaults: ObjectSavable { + func setObject(_ object: Object, forKey: String) throws where Object: Encodable { + let encoder = JSONEncoder() + do { + let data = try encoder.encode(object) + set(data, forKey: forKey) + } catch { + throw ObjectSavableError.unableToEncode + } + } + + func getObject(forKey: String, castTo type: Object.Type) throws -> Object where Object: Decodable { + guard let data = data(forKey: forKey) else { throw ObjectSavableError.noValue } + let decoder = JSONDecoder() + do { + let object = try decoder.decode(type, from: data) + return object + } catch { + throw ObjectSavableError.unableToDecode + } + } +} diff --git a/DGCAWallet/Models/CountryDataStorage.swift b/DGCAWallet/Models/CountryDataStorage.swift new file mode 100644 index 0000000..968bb0a --- /dev/null +++ b/DGCAWallet/Models/CountryDataStorage.swift @@ -0,0 +1,73 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-verifier-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// CountryDataStorage.swift +// DGCAVerifier +// +// Created by Alexandr Chernyy on 22.06.2021. +// +import Foundation +import SwiftDGC +import SwiftyJSON + +struct CountryDataStorage: Codable { + static var sharedInstance = CountryDataStorage() + + var countryCodes = [CountryModel]() + var lastFetchRaw: Date? + var lastFetch: Date { + get { + lastFetchRaw ?? .init(timeIntervalSince1970: 0) + } + set(value) { + lastFetchRaw = value + } + } + var config = Config.load() + + mutating func add(country: CountryModel) { + let list = countryCodes + if list.contains(where: { savedCountry in + savedCountry.code == country.code + }) { + return + } + countryCodes.append(country) + } + + public func save() { + Self.storage.save(self) + } + + static let storage = SecureStorage(fileName: "country_secure") + + static func initialize(completion: @escaping () -> Void) { + storage.loadOverride(fallback: CountryDataStorage.sharedInstance) { success in + guard let result = success else { + return + } + let format = l10n("log.country") + print(String.localizedStringWithFormat(format, result.countryCodes.count)) + CountryDataStorage.sharedInstance = result + completion() + } + } +} diff --git a/DGCAWallet/Models/LocalData.swift b/DGCAWallet/Models/LocalData.swift index 343f5b9..58f1267 100644 --- a/DGCAWallet/Models/LocalData.swift +++ b/DGCAWallet/Models/LocalData.swift @@ -48,6 +48,7 @@ struct LocalData: Codable { } static let appVersion = (Bundle.main.infoDictionary?[Constants.bundleVersion] as? String) ?? Constants.unknown static var sharedInstance = LocalData() + static let storage = SecureStorage(fileName: Constants.pubKeysStorageFilename) var certStrings = [DatedCertString]() var config = Config.load() @@ -56,14 +57,10 @@ struct LocalData: Codable { public func save() { Self.storage.save(self) } - public static func add(_ cert: HCert, with tan: String?) { - sharedInstance.certStrings.append(.init(date: Date(), certString: cert.payloadString, storedTAN: tan)) + sharedInstance.certStrings.append(.init(date: Date(), certString: cert.fullPayloadString, storedTAN: tan)) sharedInstance.save() } - - static let storage = SecureStorage(fileName: Constants.pubKeysStorageFilename) - static func initialize(completion: @escaping () -> Void) { storage.loadOverride(fallback: LocalData.sharedInstance) { success in guard var result = success else { @@ -76,10 +73,10 @@ struct LocalData: Codable { } LocalData.sharedInstance = result completion() - GatewayConnection.fetchContext() + //WARNING mocked data +// GatewayConnection.fetchContext() } } - var versionedConfig: JSON { if config[Constants.versions][Self.appVersion].exists() { return config[Constants.versions][Self.appVersion] diff --git a/DGCAWallet/Models/RulesDataStorage.swift b/DGCAWallet/Models/RulesDataStorage.swift new file mode 100644 index 0000000..15a3b08 --- /dev/null +++ b/DGCAWallet/Models/RulesDataStorage.swift @@ -0,0 +1,81 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-verifier-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// File.swift +// DGCAVerifier +// +// Created by Alexandr Chernyy on 22.06.2021. +// +import Foundation +import SwiftDGC +import SwiftyJSON +import CertLogic + +struct RulesDataStorage: Codable { + static var sharedInstance = RulesDataStorage() + static let storage = SecureStorage(fileName: "rules_secure") + + var rules = [CertLogic.Rule]() + var lastFetchRaw: Date? + var lastFetch: Date { + get { + lastFetchRaw ?? .init(timeIntervalSince1970: 0) + } + set(value) { + lastFetchRaw = value + } + } + + mutating func add(rule: CertLogic.Rule) { + let list = rules + if list.contains(where: { savedRule in + savedRule.identifier == rule.identifier && savedRule.version == rule.version + }) { + return + } + rules.append(rule) + } + + public func save() { + Self.storage.save(self) + } + + public mutating func deleteRuleWithHash(hash: String) { + self.rules = self.rules.filter { $0.hash != hash } + } + public func isRuleExistWithHash(hash: String) -> Bool { + let list = rules + return list.contains(where: { rule in + rule.hash == hash + }) + } + static func initialize(completion: @escaping () -> Void) { + storage.loadOverride(fallback: RulesDataStorage.sharedInstance) { success in + guard let result = success else { + return + } + let format = l10n("log.rules") + print(String.localizedStringWithFormat(format, result.rules.count)) + RulesDataStorage.sharedInstance = result + completion() + } + } +} diff --git a/DGCAWallet/Models/ValidityCellModel.swift b/DGCAWallet/Models/ValidityCellModel.swift new file mode 100644 index 0000000..05c347d --- /dev/null +++ b/DGCAWallet/Models/ValidityCellModel.swift @@ -0,0 +1,48 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-wallet-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// ValidityCellModel.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 08.07.2021. +// + + +import Foundation + +enum ValidityCellModelType: Int { + case titleAndDescription + case countryAndTimeSelection +} + +public final class ValidityCellModel { + var cellType: ValidityCellModelType = .titleAndDescription + var title: String? + var description: String? + var needChangeTitleFont: Bool = false + + init(cellType: ValidityCellModelType = .titleAndDescription, title: String? = nil, description: String? = nil, needChangeTitleFont: Bool = false) { + self.needChangeTitleFont = needChangeTitleFont + self.cellType = cellType + self.title = title + self.description = description + } +} diff --git a/DGCAWallet/Models/ValueSetsDataStorage.swift b/DGCAWallet/Models/ValueSetsDataStorage.swift new file mode 100644 index 0000000..78f65dd --- /dev/null +++ b/DGCAWallet/Models/ValueSetsDataStorage.swift @@ -0,0 +1,96 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-verifier-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// File.swift +// DGCAVerifier +// +// Created by Alexandr Chernyy on 25.06.2021. +// +import Foundation +import SwiftDGC +import SwiftyJSON +import CertLogic + +struct ValueSetsDataStorage: Codable { + static var sharedInstance = ValueSetsDataStorage() + + var valueSets = [CertLogic.ValueSet]() + var lastFetchRaw: Date? + var lastFetch: Date { + get { + lastFetchRaw ?? .init(timeIntervalSince1970: 0) + } + set(value) { + lastFetchRaw = value + } + } + var config = Config.load() + + mutating func add(valueSet: CertLogic.ValueSet) { + let list = valueSets + if list.contains(where: { savedValueSet in + savedValueSet.valueSetId == valueSet.valueSetId + }) { + return + } + valueSets.append(valueSet) + } + + public func save() { + Self.storage.save(self) + } + + public mutating func deleteValueSetWithHash(hash: String) { + self.valueSets = self.valueSets.filter { $0.hash != hash } + } + public func isValueSetExistWithHash(hash: String) -> Bool { + let list = valueSets + return list.contains(where: { valueSet in + valueSet.hash == hash + }) + } + static let storage = SecureStorage(fileName: "valueSets_secure") + + static func initialize(completion: @escaping () -> Void) { + storage.loadOverride(fallback: ValueSetsDataStorage.sharedInstance) { success in + guard let result = success else { + return + } + let format = l10n("log.valueSets") + print(String.localizedStringWithFormat(format, result.valueSets.count)) + ValueSetsDataStorage.sharedInstance = result + completion() + } + } +} + +// MARK: ValueSets for External Parameters +extension ValueSetsDataStorage { + public func getValueSetsForExternalParameters() -> Dictionary { + var returnValue = Dictionary() + valueSets.forEach { valueSet in + if let keys: [String] = Array(valueSet.valueSetValues.keys) as? [String] { + returnValue[valueSet.valueSetId] = keys + } + } + return returnValue + } +} diff --git a/DGCAWallet/Services/CertLogicEngineManager.swift b/DGCAWallet/Services/CertLogicEngineManager.swift new file mode 100644 index 0000000..1c51af4 --- /dev/null +++ b/DGCAWallet/Services/CertLogicEngineManager.swift @@ -0,0 +1,47 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-verifier-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// CertLogicEngineManager.swift +// DGCAVerifier +// +// Created by Alexandr Chernyy on 23.06.2021. +// +import Foundation +import CertLogic +import SwiftDGC + +class CertLogicEngineManager { + static var sharedInstance: CertLogicEngineManager = { + let instance = CertLogicEngineManager() + return instance + }() + + var certLogicEngine: CertLogicEngine = CertLogicEngine(schema: SwiftDGC.euDgcSchemaV1, rules: []) + func setRules(ruleList: [CertLogic.Rule]) { + certLogicEngine.updateRules(rules: ruleList) + } + func validate(filter: FilterParameter, external: ExternalParameter, payload: String) -> [ValidationResult] { + return certLogicEngine.validate(filter: filter, external: external, payload: payload) + } + func getRuleDetailsError(rule: Rule, filter: FilterParameter) -> Dictionary { + return certLogicEngine.getDetailsOfError(rule: rule, filter: filter) + } +} diff --git a/DGCAWallet/Services/GatewayConnection.swift b/DGCAWallet/Services/GatewayConnection.swift index da8ee06..738ede5 100644 --- a/DGCAWallet/Services/GatewayConnection.swift +++ b/DGCAWallet/Services/GatewayConnection.swift @@ -30,6 +30,8 @@ import Alamofire import SwiftDGC import SwiftyJSON import UIKit +import CertLogic +import CryptoKit struct GatewayConnection: ContextConnection { public static func claim(cert: HCert, with tan: String?, completion: ((Bool, String?) -> Void)?) { @@ -107,3 +109,205 @@ struct GatewayConnection: ContextConnection { LocalData.sharedInstance.versionedConfig } } + +// MARK: Country, Rules, Valuesets extension + +extension GatewayConnection { + // Country list + public static func getListOfCountry(completion: (([CountryModel]) -> Void)?) { + request(["endpoints", "countryList"], method: .get).response { + guard + case let .success(result) = $0.result, + let response = result, + let responseStr = String(data: response, encoding: .utf8), + let json = JSON(parseJSON: responseStr).array + else { + return + } + let codes = json.compactMap { $0.string } + var countryList: [CountryModel] = [] + codes.forEach { code in + countryList.append(CountryModel(code: code)) + } + completion?(countryList) + } + } + static func countryList(completion: (([CountryModel]) -> Void)? = nil) { + CountryDataStorage.initialize { + if CountryDataStorage.sharedInstance.countryCodes.count > 0 { + completion?(CountryDataStorage.sharedInstance.countryCodes.sorted(by: { countryOne, countryTwo in + return countryOne.name < countryTwo.name + })) + } + getListOfCountry { countryList in + CountryDataStorage.sharedInstance.countryCodes.removeAll() + countryList.forEach { country in + CountryDataStorage.sharedInstance.add(country: country) + } + CountryDataStorage.sharedInstance.lastFetch = Date() + CountryDataStorage.sharedInstance.save() + completion?(CountryDataStorage.sharedInstance.countryCodes.sorted(by: { countryOne, countryTwo in + return countryOne.name < countryTwo.name + })) + } + } + } + // Rules + public static func getListOfRules(completion: (([CertLogic.Rule]) -> Void)?) { + request(["endpoints", "rules"], method: .get).response { + guard + case let .success(result) = $0.result, + let response = result, + let responseStr = String(data: response, encoding: .utf8) + else { + return + } + let ruleHashes: [RuleHash] = CertLogicEngine.getItems(from: responseStr) + // Remove old hashes + RulesDataStorage.sharedInstance.rules = RulesDataStorage.sharedInstance.rules.filter { rule in + return !ruleHashes.contains(where: { ruleHash in + return ruleHash.hash == rule.hash + }) + } + // Downloading new hashes + var rulesItems = [CertLogic.Rule]() + let downloadingGroup = DispatchGroup() + ruleHashes.forEach { ruleHash in + downloadingGroup.enter() + if !RulesDataStorage.sharedInstance.isRuleExistWithHash(hash: ruleHash.hash) { + getRules(ruleHash: ruleHash) { rule in + if let rule = rule { + rulesItems.append(rule) + } + downloadingGroup.leave() + } + } else { + downloadingGroup.leave() + } + } + downloadingGroup.notify(queue: .main) { + completion?(rulesItems) + print("Finished all requests.") + } + } + } + public static func getRules(ruleHash: CertLogic.RuleHash, completion: ((CertLogic.Rule?) -> Void)?) { + request(["endpoints", "rules"], externalLink: "/\(ruleHash.country)/\(ruleHash.hash)", method: .get).response { + guard + case let .success(result) = $0.result, + let response = result, + let responseStr = String(data: response, encoding: .utf8) + else { + completion?(nil) + return + } + if let rule: Rule = CertLogicEngine.getItem(from: responseStr) { + let downloadedRuleHash = SHA256.digest(input: response as NSData) + if downloadedRuleHash.hexString == ruleHash.hash { + rule.setHash(hash: ruleHash.hash) + completion?(rule) + } else { + completion?(nil) + } + return + } + completion?(nil) + } + } + static func rulesList(completion: (([CertLogic.Rule]) -> Void)? = nil) { + RulesDataStorage.initialize { + completion?(RulesDataStorage.sharedInstance.rules) + } + } + + static func loadRulesFromServer(completion: (([CertLogic.Rule]) -> Void)? = nil) { + getListOfRules { rulesList in + rulesList.forEach { rule in + RulesDataStorage.sharedInstance.add(rule: rule) + } + RulesDataStorage.sharedInstance.lastFetch = Date() + RulesDataStorage.sharedInstance.save() + completion?(RulesDataStorage.sharedInstance.rules) + } + } + + // ValueSets + public static func getListOfValueSets(completion: (([CertLogic.ValueSet]) -> Void)?) { + request(["endpoints", "valuesets"], method: .get).response { + guard + case let .success(result) = $0.result, + let response = result, + let responseStr = String(data: response, encoding: .utf8) + else { + return + } + let valueSetsHashes: [ValueSetHash] = CertLogicEngine.getItems(from: responseStr) + // Remove old hashes + ValueSetsDataStorage.sharedInstance.valueSets = ValueSetsDataStorage.sharedInstance.valueSets.filter { valueSet in + return !valueSetsHashes.contains(where: { valueSetHashe in + return valueSetHashe.hash == valueSet.hash + }) + } + // Downloading new hashes + var valueSetsItems = [CertLogic.ValueSet]() + let downloadingGroup = DispatchGroup() + valueSetsHashes.forEach { valueSetHash in + downloadingGroup.enter() + if !ValueSetsDataStorage.sharedInstance.isValueSetExistWithHash(hash: valueSetHash.hash) { + getValueSets(valueSetHash: valueSetHash) { valueSet in + if let valueSet = valueSet { + valueSetsItems.append(valueSet) + } + downloadingGroup.leave() + } + } else { + downloadingGroup.leave() + } + } + downloadingGroup.notify(queue: .main) { + completion?(valueSetsItems) + print("Finished all requests.") + } + } + } + public static func getValueSets(valueSetHash: CertLogic.ValueSetHash, completion: ((CertLogic.ValueSet?) -> Void)?) { + request(["endpoints", "valuesets"], externalLink: "/\(valueSetHash.hash)", method: .get).response { + guard + case let .success(result) = $0.result, + let response = result, + let responseStr = String(data: response, encoding: .utf8) + else { + completion?(nil) + return + } + if let valueSet: ValueSet = CertLogicEngine.getItem(from: responseStr) { + let downloadedValueSetHash = SHA256.digest(input: response as NSData) + if downloadedValueSetHash.hexString == valueSetHash.hash { + valueSet.setHash(hash: valueSetHash.hash) + completion?(valueSet) + } else { + completion?(nil) + } + return + } + completion?(nil) + } + } + static func valueSetsList(completion: (([CertLogic.ValueSet]) -> Void)? = nil) { + ValueSetsDataStorage.initialize { + completion?(ValueSetsDataStorage.sharedInstance.valueSets) + } + } + + static func loadValueSetsFromServer(completion: (([CertLogic.ValueSet]) -> Void)? = nil){ + getListOfValueSets { valueSetsList in + valueSetsList.forEach { valueSet in + ValueSetsDataStorage.sharedInstance.add(valueSet: valueSet) + } + ValueSetsDataStorage.sharedInstance.lastFetch = Date() + ValueSetsDataStorage.sharedInstance.save() + completion?(ValueSetsDataStorage.sharedInstance.valueSets) + } + } +} + diff --git a/DGCAWallet/Storyboards/Base.lproj/CertificateViewer.storyboard b/DGCAWallet/Storyboards/Base.lproj/CertificateViewer.storyboard index 46fc745..373e3df 100644 --- a/DGCAWallet/Storyboards/Base.lproj/CertificateViewer.storyboard +++ b/DGCAWallet/Storyboards/Base.lproj/CertificateViewer.storyboard @@ -26,10 +26,11 @@ + + @@ -83,13 +106,14 @@ + - + @@ -109,6 +133,7 @@ + diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_valid.imageset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_valid.imageset/Contents.json new file mode 100644 index 0000000..d00140b --- /dev/null +++ b/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_valid.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon_large_valid.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_valid.imageset/icon_large_valid.svg b/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_valid.imageset/icon_large_valid.svg new file mode 100644 index 0000000..99a1916 --- /dev/null +++ b/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_valid.imageset/icon_large_valid.svg @@ -0,0 +1,3 @@ + + + diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_warning.imageset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_warning.imageset/Contents.json new file mode 100644 index 0000000..d8c5178 --- /dev/null +++ b/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_warning.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon_large_warning.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_warning.imageset/icon_large_warning.svg b/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_warning.imageset/icon_large_warning.svg new file mode 100644 index 0000000..265ce85 --- /dev/null +++ b/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_warning.imageset/icon_large_warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/pass-icon.imageset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/pass-icon.imageset/Contents.json new file mode 100644 index 0000000..922dc41 --- /dev/null +++ b/DGCAWallet/SupportingFiles/Assets.xcassets/pass-icon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tick-icon-png-17.jpg.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/pass-icon.imageset/tick-icon-png-17.jpg.png b/DGCAWallet/SupportingFiles/Assets.xcassets/pass-icon.imageset/tick-icon-png-17.jpg.png new file mode 100644 index 0000000..4a7a9f9 Binary files /dev/null and b/DGCAWallet/SupportingFiles/Assets.xcassets/pass-icon.imageset/tick-icon-png-17.jpg.png differ diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/warning-icon.imageset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/warning-icon.imageset/Contents.json new file mode 100644 index 0000000..e8fbef7 --- /dev/null +++ b/DGCAWallet/SupportingFiles/Assets.xcassets/warning-icon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "warning-icon-svg-10.jpg.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/warning-icon.imageset/warning-icon-svg-10.jpg.png b/DGCAWallet/SupportingFiles/Assets.xcassets/warning-icon.imageset/warning-icon-svg-10.jpg.png new file mode 100644 index 0000000..14dfcf1 Binary files /dev/null and b/DGCAWallet/SupportingFiles/Assets.xcassets/warning-icon.imageset/warning-icon-svg-10.jpg.png differ diff --git a/DGCAWallet/SupportingFiles/en.lproj/Localizable.strings b/DGCAWallet/SupportingFiles/en.lproj/Localizable.strings index 241f383..e92472d 100644 --- a/DGCAWallet/SupportingFiles/en.lproj/Localizable.strings +++ b/DGCAWallet/SupportingFiles/en.lproj/Localizable.strings @@ -46,3 +46,26 @@ "cert.delete.body" = "Are you sure you want to delete this certificate? This action cannot be undone."; "tap-to-reveal" = "tap to reveal"; "app-version" = "App Version %@"; +"button_check_validity" = "Check Validity"; +"button_i_agree" = "I Agree, check validity"; +"failed_for_country" = "Failed for %@ (see settings)"; +"passed_for_country" = "Passed for %@ (see settings)"; +"open_for_country" = "Open for %@ (see settings)"; +"failed" = "Failed"; +"passed" = "Passed"; +"open" = "Open"; +"country_certificate_text" = "Check country rules conformance of your certificate"; +"disclaimer_text" = "This check gives an indication on eligibility to travel based on your certificate. Restriction of free movement in response to the coronavirus pandemic are subject to change and might be based on additional information not available to and within this application. Please refer to authoritative national sources for more information and the Re-open EU website: https://reopen.europa.eu/. Your data is being processed locally on this device."; +"validity_of_certificate" = "Validity of your certificate"; +"disclaimer" = "Disclaimer"; +"destination_country" = "Your destination country"; +"destination_date" = "Check the date"; +"valid_certificate" = "Valid certificate"; +"your_certificate_allow" = "Your certificate allows you to enter the chosen country"; +"invalid_certificate" = "Invalid certificate"; +"your_certificate_did_not_allow" = "Your certificate did not allows you to enter the chosen country"; +"certificate_limitation" = "Certificate has limitation"; +"certification_has_limitation" = "Your certificate is valid but has the following restrictions:"; +"validate_certificate_with_rules" = "Validating certificate with country rules"; +"please_wait" = "Please wait we are validating your certificate"; +"info_without_waranty" = "Please note that this information is without guaranty. This functions checks if the certificate matches the rules of the country you are travelling to at the given date."; diff --git a/DGCAWallet/ViewControllers/CertificateViewer.swift b/DGCAWallet/ViewControllers/CertificateViewer.swift index d3f230c..33b35e7 100644 --- a/DGCAWallet/ViewControllers/CertificateViewer.swift +++ b/DGCAWallet/ViewControllers/CertificateViewer.swift @@ -35,7 +35,8 @@ class CertificateViewerVC: UIViewController { @IBOutlet weak var dismissButton: UIButton! @IBOutlet weak var cancelButton: UIButton! @IBOutlet weak var cancelButtonConstraint: NSLayoutConstraint! - + @IBOutlet weak var checkValidityButton: UIButton! + var hCert: HCert? var tan: String? weak var childDismissedDelegate: CertViewerDelegate? @@ -48,11 +49,15 @@ class CertificateViewerVC: UIViewController { nameLabel.text = hCert.fullName if !isSaved { dismissButton.setTitle(l10n("btn.save"), for: .normal) + checkValidityButton.isHidden = true + } else { + checkValidityButton.isHidden = false } headerBackground.backgroundColor = isSaved ? .blue : .grey10 nameLabel.textColor = isSaved ? .white : .black cancelButton.alpha = isSaved ? 0 : 1 cancelButtonConstraint.priority = .init(isSaved ? 997 : 999) + checkValidityButton.setTitle(l10n("button_check_validity"), for: .normal) view.layoutIfNeeded() } @@ -89,12 +94,17 @@ class CertificateViewerVC: UIViewController { dismiss(animated: true, completion: nil) } + @IBAction func checkValidityAction(_ sender: Any) { + let checkValidityVC = CheckValidityVC.loadFromNib() + checkValidityVC.setHCert(cert: self.hCert) + self.present(checkValidityVC, animated: true, completion: nil) + } + func saveCert() { showInputDialog( title: l10n("tan.confirm.title"), subtitle: l10n("tan.confirm.text"), - inputPlaceholder: l10n("tan.confirm.placeholder"), - capitalization: .allCharacters + inputPlaceholder: l10n("tan.confirm.placeholder") ) { [weak self] in guard let cert = self?.hCert else { return diff --git a/DGCAWallet/ViewControllers/CheckValidityVC.swift b/DGCAWallet/ViewControllers/CheckValidityVC.swift new file mode 100644 index 0000000..62f482c --- /dev/null +++ b/DGCAWallet/ViewControllers/CheckValidityVC.swift @@ -0,0 +1,122 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-wallet-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// CheckValidityVC.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 08.07.2021. +// + + +import UIKit +import SwiftDGC + +final class CheckValidityVC: UIViewController { + + private enum Constants { + static let titleCellIndentifier = "CellWithTitleAndDescriptionTVC" + static let countryCellIndentifier = "CellWithDateAndCountryTVC" + static let bottomOffset: CGFloat = 32.0 + } + @IBOutlet private weak var closeButton: UIButton! + @IBOutlet private weak var checkValidityButton: UIButton! + @IBOutlet private weak var tableView: UITableView! + private var items: [ValidityCellModel] = [] + private var hCert: HCert? + private var selectedDate = Date() + private var selectedCountryCode: String? + override func viewDidLoad() { + super.viewDidLoad() + setupView() + checkValidityButton.setTitle(l10n("button_i_agree"), for: .normal) + } + @IBAction func closeButtonAction(_ sender: Any) { + self.dismiss(animated: true, completion: nil) + } + private func setupView() { + setupInitialDate() + setupTableView() + tableView.reloadData() + } + private func setupTableView() { + tableView.dataSource = self + tableView.register(UINib(nibName: Constants.titleCellIndentifier, bundle: nil), + forCellReuseIdentifier: Constants.titleCellIndentifier) + tableView.register(UINib(nibName: Constants.countryCellIndentifier, bundle: nil), + forCellReuseIdentifier: Constants.countryCellIndentifier) + tableView.contentInset = .init(top: .zero, left: .zero, bottom: Constants.bottomOffset, right: .zero) + + } + private func setupInitialDate() { + items.append(ValidityCellModel(title: l10n("country_certificate_text"), + description: "", + needChangeTitleFont: true)) + items.append(ValidityCellModel(cellType: .countryAndTimeSelection)) + items.append(ValidityCellModel(title: l10n("disclaimer"), description: l10n("disclaimer_text"))) + } + func setHCert(cert: HCert?) { + self.hCert = cert + } + @IBAction func checkValidityAction(_ sender: Any) { + let ruleValidationVC = RuleValidationResultVC.loadFromNib() + ruleValidationVC.closeHandler = { + self.closeButtonAction(self) + } + self.present(ruleValidationVC, animated: true) { + guard let hCert = self.hCert else { return } + ruleValidationVC.setupView(with: hCert, selectedDate: self.selectedDate) + } + } +} + +extension CheckValidityVC: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return items.count + } + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let item: ValidityCellModel = items[indexPath.row] + if item.cellType == .titleAndDescription { + let base = tableView.dequeueReusableCell(withIdentifier: Constants.titleCellIndentifier, for: indexPath) + guard let cell = base as? CellWithTitleAndDescriptionTVC else { + return base + } + cell.setupCell(with: item) + return cell + } else { + let base = tableView.dequeueReusableCell(withIdentifier: Constants.countryCellIndentifier, for: indexPath) + guard let cell = base as? CellWithDateAndCountryTVC else { + return base + } + cell.countryHandler = { [weak self] countryCode in + self?.hCert?.ruleCountryCode = countryCode + } + cell.dataHandler = {[weak self] date in + self?.selectedDate = date + } + cell.setupView() + return cell + } + } +} diff --git a/DGCAWallet/ViewControllers/CheckValidityVC.xib b/DGCAWallet/ViewControllers/CheckValidityVC.xib new file mode 100644 index 0000000..b8d9785 --- /dev/null +++ b/DGCAWallet/ViewControllers/CheckValidityVC.xib @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DGCAWallet/ViewControllers/Home.swift b/DGCAWallet/ViewControllers/Home.swift index 3c078e0..e083566 100644 --- a/DGCAWallet/ViewControllers/Home.swift +++ b/DGCAWallet/ViewControllers/Home.swift @@ -35,6 +35,23 @@ class HomeVC: UIViewController { HCert.config.prefetchAllCodes = true HCert.config.checkSignatures = false + + RulesDataStorage.initialize { + GatewayConnection.rulesList { _ in + CertLogicEngineManager.sharedInstance.setRules(ruleList: RulesDataStorage.sharedInstance.rules) + GatewayConnection.loadRulesFromServer { _ in + CertLogicEngineManager.sharedInstance.setRules(ruleList: RulesDataStorage.sharedInstance.rules) + ValueSetsDataStorage.initialize { + GatewayConnection.valueSetsList { _ in + GatewayConnection.loadValueSetsFromServer { _ in + GatewayConnection.countryList { _ in + } + } + } + } + } + } + } LocalData.initialize { DispatchQueue.main.async { [weak self] in guard let self = self else { diff --git a/DGCAWallet/ViewControllers/RuleValidationResultVC.swift b/DGCAWallet/ViewControllers/RuleValidationResultVC.swift new file mode 100644 index 0000000..c69fbd6 --- /dev/null +++ b/DGCAWallet/ViewControllers/RuleValidationResultVC.swift @@ -0,0 +1,232 @@ +// +/*- + * ---license-start + * eu-digital-green-certificates / dgca-wallet-app-ios + * --- + * Copyright (C) 2021 T-Systems International GmbH and all other contributors + * --- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ---license-end + */ +// +// RuleValidationResultVC.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 08.07.2021. +// + + +import UIKit +import SwiftDGC +import CertLogic + +public typealias OnCloseHandler = () -> Void + +final class RuleValidationResultVC: UIViewController { + + private enum Constants { + static let ruleCellId = "RuleErrorTVC" + } + + @IBOutlet weak var closeButton: UIButton! + @IBOutlet weak var backButton: UIButton! + @IBOutlet weak var resultLabel: UILabel! + @IBOutlet weak var resultIcon: UIImageView! + @IBOutlet weak var resultDescriptionLabel: UILabel! + @IBOutlet weak var noWarrantyLabel: UILabel! + @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var activityIndicator: UIActivityIndicatorView! + private var hCert: HCert? + private var selectedDate = Date() + var closeHandler: OnCloseHandler? + var items: [InfoSection] = [] + override func viewDidLoad() { + super.viewDidLoad() + setupLabels() + setupTableView() + activityIndicator.startAnimating() + } + + private func setupTableView() { + tableView.dataSource = self + tableView.register(UINib(nibName: Constants.ruleCellId, bundle: nil), forCellReuseIdentifier: Constants.ruleCellId) + tableView.contentInset = .init(top: 0, left: 0, bottom: 32, right: 0) + } + private func setupLabels() { + resultLabel.text = l10n("validate_certificate_with_rules") + resultDescriptionLabel.text = "" + noWarrantyLabel.text = l10n("info_without_waranty") + } + @IBAction func closeAction(_ sender: Any) { + self.dismiss(animated: true) { [weak self] in + self?.closeHandler?() + } + } + @IBAction func backAction(_ sender: Any) { + self.dismiss(animated: true, completion: nil) + } + + func setupView(with hcert: HCert, selectedDate: Date) { + self.hCert = hcert + self.selectedDate = selectedDate + let validity: HCertValidity = self.validateCertLogicRules() + if validity == .valid { + resultLabel.text = l10n("valid_certificate") + resultDescriptionLabel.text = l10n("your_certificate_allow") + resultIcon.image = UIImage(named: "icon_large_valid") + } + if validity == .invalid { + resultLabel.text = l10n("invalid_certificate") + resultDescriptionLabel.text = l10n("your_certificate_did_not_allow") + } + if validity == .ruleInvalid { + resultLabel.text = l10n("certificate_limitation") + resultDescriptionLabel.text = l10n("certification_has_limitation") + resultIcon.image = UIImage(named: "icon_large_warning") + } + activityIndicator.stopAnimating() + resultIcon.isHidden = false + tableView.isHidden = false + noWarrantyLabel.isHidden = false + } +} + +extension RuleValidationResultVC: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if items.count == 0 { + self.tableView.setEmptyMessage("Sorry! \n no rules for this country") + } else { + self.tableView.restore() + } + return items.count + } + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let item: InfoSection = items[indexPath.row] + let base = tableView.dequeueReusableCell(withIdentifier: Constants.ruleCellId, for: indexPath) + guard let cell = base as? RuleErrorTVC else { + return base + } + cell.setupCell(with: item) + return cell + } +} + +extension RuleValidationResultVC { + func validateCertLogicRules() -> HCertValidity { + var validity: HCertValidity = .valid + guard let hCert = hCert else { + return validity + } + let certType = getCertificationType(type: hCert.type) + if let countryCode = hCert.ruleCountryCode { + let valueSets = ValueSetsDataStorage.sharedInstance.getValueSetsForExternalParameters() + let filterParameter = FilterParameter(validationClock: self.selectedDate, + countryCode: countryCode, + certificationType: certType) + let externalParameters = ExternalParameter(validationClock: self.selectedDate, + valueSets: valueSets, + exp: hCert.exp, + iat: hCert.iat, + issuerCountryCode: hCert.issCode, + kid: hCert.kidStr) + let result = CertLogicEngineManager.sharedInstance.validate(filter: filterParameter, external: externalParameters, + payload: hCert.body.description) + let failsAndOpen = result.filter { validationResult in + return validationResult.result != .passed + } + if failsAndOpen.count > 0 { + validity = .ruleInvalid + result.sorted(by: { vdResultOne, vdResultTwo in + vdResultOne.result.rawValue < vdResultTwo.result.rawValue + }).forEach { validationResult in + if let error = validationResult.validationErrors?.first { + switch validationResult.result { + case .fail: + items.append(InfoSection(header: "CirtLogic Engine error", + content: error.localizedDescription, + countryName: hCert.ruleCountryCode, + ruleValidationResult: SwiftDGC.RuleValidationResult.error)) + case .open: + items.append(InfoSection(header: "CirtLogic Engine error", + content: error.localizedDescription, + countryName: hCert.ruleCountryCode, + ruleValidationResult: SwiftDGC.RuleValidationResult.open)) + case .passed: + items.append(InfoSection(header: "CirtLogic Engine error", + content: error.localizedDescription, + countryName: hCert.ruleCountryCode, + ruleValidationResult: SwiftDGC.RuleValidationResult.passed)) + } + } else { + let preferredLanguage = Locale.preferredLanguages[0] as String + let arr = preferredLanguage.components(separatedBy: "-") + let deviceLanguage = (arr.first ?? "EN") + var errorString = "" + if let error = validationResult.rule?.getLocalizedErrorString(locale: deviceLanguage) { + errorString = error + } + var detailsError = "" + if let rule = validationResult.rule { + let dict = CertLogicEngineManager.sharedInstance.getRuleDetailsError(rule: rule, + filter: filterParameter) + dict.keys.forEach({ key in + detailsError += key + ": " + (dict[key] ?? "") + " " + }) + } + switch validationResult.result { + case .fail: + items.append(InfoSection(header: errorString, + content: detailsError, + countryName: hCert.ruleCountryCode, + ruleValidationResult: SwiftDGC.RuleValidationResult.error)) + case .open: + items.append(InfoSection(header: errorString, + content: detailsError, + countryName: hCert.ruleCountryCode, + ruleValidationResult: SwiftDGC.RuleValidationResult.open)) + case .passed: + items.append(InfoSection(header: errorString, + content: detailsError, + countryName: hCert.ruleCountryCode, + ruleValidationResult: SwiftDGC.RuleValidationResult.passed)) + } + } + } + self.tableView.reloadData() + } + } + return validity + } +} + +// MARK: External CertType from HCert type +extension RuleValidationResultVC { + func getCertificationType(type: SwiftDGC.HCertType) -> CertificateType { + var certType: CertificateType = .general + switch type { + case .recovery: + certType = .recovery + case .test: + certType = .test + case .vaccine: + certType = .vaccination + case .unknown: + certType = .general + } + return certType + } +} diff --git a/DGCAWallet/ViewControllers/RuleValidationResultVC.xib b/DGCAWallet/ViewControllers/RuleValidationResultVC.xib new file mode 100644 index 0000000..1527b39 --- /dev/null +++ b/DGCAWallet/ViewControllers/RuleValidationResultVC.xib @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +