From e70994da10d721a109662f7c9eb64f101f57a8ce Mon Sep 17 00:00:00 2001 From: Igor Khomiak Date: Mon, 28 Feb 2022 19:12:08 +0200 Subject: [PATCH] Feature/ticketing (#126) * no message * added new UI * Ticketing solution logic update - Added model to handle QR scan from ticket service - Added blueprint of method to obtain list of auth services * Refactored list of service method * Implemented method to fetch list of services * added new UI * server list view controller * Access token for ticketing - Request implemented - Model for response added * added selected and deselected state * List of services implemented * Public key generation added * Small refactoring to avoid crash * AccessTokenRequest fixed - Filtering for certificates added * Selection code adjusted with model * Comment removed * fixed small UI bugs * Consent screen added - request for validation added * Fixed UI part of selection of cert * xnonce added * Fixed navigation in wallet app * Fixed navigation and cells. Added Certificate controller to navigation. * Added services controller * Remove obsolete sources * Removed unnecessary fields * minor fixes * Replaced x-buttons * Removed unnecessary code * Validation request added * Refactored ut8 string to base64decoded data * Code clean up * Identity services refactored * Code clean up * Validation Result view controller added * fixed QR class * Fixed navigation * Fixed navigation * Bug fix session #108 #111 #113 * Added remove certificate functionality * Added delete alert * Fixed deleting * Fixed reload bug in ListCertificates * Add Developer Team * Changed cert compare * Fixed certificates table with edit * Added 2 controller files to storyboard * Fixed crash on verification result if no validation info passed * Update project * Scan Moved from Core * Added viewers to storyboard * Added remove messages to ImageViewer and PDF * Fixed Remove action * Removed xibs from controllers * Minor fix storyboard * Removed last xib * Fixed deleting, crash with race and some textes * Fix public in Date extention * Added activity check * Fixed crash with reload table * Added flash of added item * Added OK button * Fixed rule cell * Updated buttons * Fixed Grant layout * Minor fix * Fix crash with adding certs * minor fix * Fix selecting of sertificates * Added new version * added logsto requests * remove print * Fixed posting certificates * Added delete button * Removed FloatingPanel, Added delete certificate button * Finalized remove certificate * Fixed layout * Fixed deleting calbacks * Removed FloatingPanel package * Removed FloatingPanel resources * removed unnecessary import directives * Aded common core * Removed errors with cert * Fixed wallet app after core mege * fix in project tree * Access token info added on Certificate list screen * fixed storyboards * minor fixes * Added activity indicator * Added config * Fixed navigation issues * Fixed classe names in storyboards * fixed fetch time * Fixed reloadingData * fixed storyboard - added reload cell * Added reload after dismissing * Fixed Scan of ticketing QR code * minor changes in project tree * Fixed page controller * removed selected country code * Fixed landscape orientation for iPad * refactor UI cells * increased build num * temp data * correct data * fixed project settings * Fixed issue - Certificate for Ticketing can not be found * Added loading to confirm validity * x5c type changed in PublicKeyJWK structure Replaced x5c type of String with [String] in order to follow backend changes * Refactor controller names and data managers * fixed crash in ticketing * Business data were removed from TicketingAcceptanceController controller * Fixed back buttons * added background * Added Alerts to the Ticketing * refactor project tree * Replaced UserDefaults storage with keyChain * removed queue from request * Updated localization strings * Changed in localizations * Removed keys from Localization file * Added Safe thread array. * Fixed string format * Added de-localization * Added localisations to resource files * Added app localised name * Fixed localizations * Added localized property. Added alert on save PDF, fixed alerts * Optimised loading on main screen * Fixed reload page on main screen * Fixed layout in Image and PDF cells * Fixed reload table on main screen * upgrade version num * Fixed Localized strings * Added one line to localization * Refactored Access Token functionality * Added error processing to the ticketing * Fix verification fields * Fixed search of validation data * Fixed main thread methods * Fixed incorrect alert messages * Updated localized strings * Fixed json logic * Added revocation to walllet * removed database * removed dismiss delegate (added timer) Co-authored-by: Alexandr Chernyy Co-authored-by: Illia Vlasov Co-authored-by: Test Co-authored-by: ikhomiak --- .gitignore | 3 +- Context/Release/context.jsonc | 9 +- Context/Test/context.jsonc | 25 +- DGCAWallet.xcodeproj/project.pbxproj | 711 ++++++----- .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/swiftpm/Package.resolved | 106 -- .../Components/Cells/CertificateTVC.swift | 61 + DGCAWallet/Components/Cells/ServerTVC.swift | 62 + DGCAWallet/Components/CertificateCell.swift | 63 + .../CellWithDateAndCountryTVC.xib | 79 -- .../CellWithTitleAndDescriptionTVC.xib | 50 - ...ryTVC.swift => ExtendedValidityCell.swift} | 64 +- ...tionTVC.swift => SimpleValidityCell.swift} | 15 +- .../Components/ImageTableViewCell.swift | 24 +- DGCAWallet/Components/InfoCell.swift | 6 +- DGCAWallet/Components/LicenseCell.swift | 3 +- .../LimitationCell.swift} | 29 +- DGCAWallet/Components/PDFTableViewCell.swift | 21 +- DGCAWallet/Components/RoundedButton.swift | 1 - .../Components/RuleCell/RuleErrorTVC.xib | 139 --- ...RuleErrorTVC.swift => RuleErrorCell.swift} | 65 +- DGCAWallet/Components/ServerCell.swift | 61 + DGCAWallet/Components/SquareViewFinder.swift | 77 ++ ...gPanelLayout.swift => TokenInfoCell.swift} | 36 +- DGCAWallet/Components/WalletCell.swift | 14 +- DGCAWallet/Extensions/UIColor.swift | 14 +- .../{UIImage+.swift => UIImage+String.swift} | 15 +- DGCAWallet/Extensions/UITableView+.swift | 3 +- DGCAWallet/Extensions/UIViewController+.swift | 9 +- DGCAWallet/Extensions/UserDefaults+.swift | 1 - DGCAWallet/Models/AccessTokenResponse.swift | 67 + DGCAWallet/Models/CertificateValidator.swift | 265 ++++ DGCAWallet/Models/CountryDataStorage.swift | 73 -- DGCAWallet/Models/DGCLogger.swift | 61 + .../DataStorageManagement/DataCenter.swift | 232 ++++ .../DataStorageManagement/DataStuctures.swift | 78 ++ .../ImageDataManager.swift | 69 + .../LocalDataManager.swift | 146 +++ .../PdfDataManager.swift | 68 + DGCAWallet/Models/ImageDataStorage.swift | 75 -- DGCAWallet/Models/LocalData.swift | 85 -- DGCAWallet/Models/PdfDataStorage.swift | 76 -- DGCAWallet/Models/RulesDataStorage.swift | 81 -- DGCAWallet/Models/SavedImage.swift | 1 - DGCAWallet/Models/SavedPDF.swift | 1 - DGCAWallet/Models/SharedConstants.swift | 45 + .../TicketingData.swift} | 19 +- DGCAWallet/Models/ValidityCellModel.swift | 5 +- DGCAWallet/Models/ValueSetsDataStorage.swift | 96 -- .../DCCRevocation.xcdatamodel/contents | 53 + DGCAWallet/Services/Brightness.swift | 1 - .../Services/CertLogicEngineManager.swift | 47 - DGCAWallet/Services/CertLogicManager.swift | 58 + DGCAWallet/Services/GatewayConnection.swift | 405 +++--- DGCAWallet/Services/KeyChain.swift | 71 ++ DGCAWallet/Services/SecureBackground.swift | 32 +- .../Base.lproj/CertificateViewer.storyboard | 727 +++++++++-- .../Base.lproj/LaunchScreen.storyboard | 4 +- .../Storyboards/Base.lproj/Main.storyboard | 1110 +++++++++++++++-- .../Base.lproj/Settings.storyboard | 290 +++-- DGCAWallet/Storyboards/Licenses.storyboard | 176 --- .../Storyboards/de.lproj/LaunchScreen.strings | 3 + .../Storyboards/en.lproj/LaunchScreen.strings | 3 + .../Assets.xcassets/Colors/Contents.json | 6 + .../disabledText.colorset/Contents.json | 0 .../walletBlack.colorset}/Contents.json | 0 .../walletBlue.colorset}/Contents.json | 0 .../walletDarkGray.colorset}/Contents.json | 0 .../walletGray10.colorset}/Contents.json | 0 .../walletGreen.colorset}/Contents.json | 0 .../walletLightBlue.colorset/Contents.json | 38 + .../walletLightGreen.colorset/Contents.json | 38 + .../walletLightYellow.colorset/Contents.json | 38 + .../walletRed.colorset}/Contents.json | 0 .../walletYellow.colorset}/Contents.json | 0 .../icon_large_invalid.imageset/Contents.json | 24 + .../icon_large_invalid.png | Bin 0 -> 11002 bytes DGCAWallet/SupportingFiles/Info.plist | 4 +- .../SupportingFiles/OpenSourceNotices.json | 4 - .../de.lproj/InfoPlist.strings} | 38 +- .../de.lproj/Localizable.strings | 166 +++ .../en.lproj/InfoPlist.strings | 39 + .../en.lproj/Localizable.strings | 219 ++-- .../Identity/AccessTokenService.swift | 29 + .../Ticketing/Identity/IdentityService.swift | 93 ++ DGCAWallet/ViewControllers/CertTable.swift | 60 - .../ViewControllers/CertificateViewer.swift | 195 --- .../CertCodeController.swift} | 22 +- .../CertPagesController.swift} | 50 +- .../CertificateController.swift | 84 ++ .../CertificateViewerController.swift | 258 ++++ .../ImageViewerController.swift} | 49 +- .../PDFViewerController.swift} | 40 +- .../ViewControllers/CheckValidityVC.xib | 97 -- DGCAWallet/ViewControllers/Home.swift | 98 -- .../ViewControllers/HomeController.swift | 69 + DGCAWallet/ViewControllers/ImageViewerVC.xib | 124 -- DGCAWallet/ViewControllers/List.swift | 623 --------- .../ViewControllers/MainListController.swift | 701 +++++++++++ DGCAWallet/ViewControllers/PDFViewerVC.xib | 107 -- .../RuleValidationResultVC.swift | 235 ---- .../RuleValidationResultVC.xib | 144 --- .../ScanWalletController.swift | 235 ++++ DGCAWallet/ViewControllers/Settings.swift | 78 -- .../LicenseController.swift} | 14 +- .../LicenseTableController.swift} | 61 +- .../SettingsTableController.swift | 124 ++ .../CertificateListController.swift | 152 +++ .../Models/CertificateRecord.swift} | 17 +- .../Models/TicketingAcceptance.swift | 181 +++ .../ServerListController.swift | 170 +++ .../TicketingAcceptanceController.swift | 123 ++ .../TicketingShared.swift | 42 + .../ValidationResultController.swift | 110 ++ .../CheckValidityController.swift} | 90 +- .../RuleValidationResultVC.swift | 225 ++++ DGCAWalletTests/EHNTests.swift | 8 +- codestyle/checkstyle.xml | 318 ----- 118 files changed, 7507 insertions(+), 4430 deletions(-) delete mode 100644 DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 DGCAWallet/Components/Cells/CertificateTVC.swift create mode 100644 DGCAWallet/Components/Cells/ServerTVC.swift create mode 100644 DGCAWallet/Components/CertificateCell.swift delete mode 100644 DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.xib delete mode 100644 DGCAWallet/Components/CheckValidityCells/CellWithTitleAndDescriptionTVC.xib rename DGCAWallet/Components/CheckValidityCells/{CellWithDateAndCountryTVC.swift => ExtendedValidityCell.swift} (67%) rename DGCAWallet/Components/CheckValidityCells/{CellWithTitleAndDescriptionTVC.swift => SimpleValidityCell.swift} (83%) rename DGCAWallet/{ViewControllers/Scan.swift => Components/LimitationCell.swift} (68%) delete mode 100644 DGCAWallet/Components/RuleCell/RuleErrorTVC.xib rename DGCAWallet/Components/{RuleCell/RuleErrorTVC.swift => RuleErrorCell.swift} (53%) create mode 100644 DGCAWallet/Components/ServerCell.swift create mode 100644 DGCAWallet/Components/SquareViewFinder.swift rename DGCAWallet/Components/{FullFloatingPanelLayout.swift => TokenInfoCell.swift} (62%) rename DGCAWallet/Extensions/{UIImage+.swift => UIImage+String.swift} (75%) create mode 100644 DGCAWallet/Models/AccessTokenResponse.swift create mode 100644 DGCAWallet/Models/CertificateValidator.swift delete mode 100644 DGCAWallet/Models/CountryDataStorage.swift create mode 100644 DGCAWallet/Models/DGCLogger.swift create mode 100644 DGCAWallet/Models/DataStorageManagement/DataCenter.swift create mode 100644 DGCAWallet/Models/DataStorageManagement/DataStuctures.swift create mode 100644 DGCAWallet/Models/DataStorageManagement/ImageDataManager.swift create mode 100644 DGCAWallet/Models/DataStorageManagement/LocalDataManager.swift create mode 100644 DGCAWallet/Models/DataStorageManagement/PdfDataManager.swift delete mode 100644 DGCAWallet/Models/ImageDataStorage.swift delete mode 100644 DGCAWallet/Models/LocalData.swift delete mode 100644 DGCAWallet/Models/PdfDataStorage.swift delete mode 100644 DGCAWallet/Models/RulesDataStorage.swift create mode 100644 DGCAWallet/Models/SharedConstants.swift rename DGCAWallet/{Extensions/String+.swift => Models/TicketingData.swift} (71%) delete mode 100644 DGCAWallet/Models/ValueSetsDataStorage.swift create mode 100644 DGCAWallet/Resources/DCCRevocation.xcdatamodeld/DCCRevocation.xcdatamodel/contents delete mode 100644 DGCAWallet/Services/CertLogicEngineManager.swift create mode 100644 DGCAWallet/Services/CertLogicManager.swift create mode 100644 DGCAWallet/Services/KeyChain.swift delete mode 100644 DGCAWallet/Storyboards/Licenses.storyboard create mode 100644 DGCAWallet/Storyboards/de.lproj/LaunchScreen.strings create mode 100644 DGCAWallet/Storyboards/en.lproj/LaunchScreen.strings create mode 100644 DGCAWallet/SupportingFiles/Assets.xcassets/Colors/Contents.json rename DGCAWallet/SupportingFiles/Assets.xcassets/{ => Colors}/disabledText.colorset/Contents.json (100%) rename DGCAWallet/SupportingFiles/Assets.xcassets/{black.colorset => Colors/walletBlack.colorset}/Contents.json (100%) rename DGCAWallet/SupportingFiles/Assets.xcassets/{blue.colorset => Colors/walletBlue.colorset}/Contents.json (100%) rename DGCAWallet/SupportingFiles/Assets.xcassets/{darkGrey.colorset => Colors/walletDarkGray.colorset}/Contents.json (100%) rename DGCAWallet/SupportingFiles/Assets.xcassets/{grey10.colorset => Colors/walletGray10.colorset}/Contents.json (100%) rename DGCAWallet/SupportingFiles/Assets.xcassets/{green.colorset => Colors/walletGreen.colorset}/Contents.json (100%) create mode 100644 DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletLightBlue.colorset/Contents.json create mode 100644 DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletLightGreen.colorset/Contents.json create mode 100644 DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletLightYellow.colorset/Contents.json rename DGCAWallet/SupportingFiles/Assets.xcassets/{red.colorset => Colors/walletRed.colorset}/Contents.json (100%) rename DGCAWallet/SupportingFiles/Assets.xcassets/{yellow.colorset => Colors/walletYellow.colorset}/Contents.json (100%) create mode 100644 DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_invalid.imageset/Contents.json create mode 100644 DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_invalid.imageset/icon_large_invalid.png rename DGCAWallet/{Components/SelfSizedTableView.swift => SupportingFiles/de.lproj/InfoPlist.strings} (54%) create mode 100644 DGCAWallet/SupportingFiles/de.lproj/Localizable.strings create mode 100644 DGCAWallet/SupportingFiles/en.lproj/InfoPlist.strings create mode 100644 DGCAWallet/Ticketing/Identity/AccessTokenService.swift create mode 100644 DGCAWallet/Ticketing/Identity/IdentityService.swift delete mode 100644 DGCAWallet/ViewControllers/CertTable.swift delete mode 100644 DGCAWallet/ViewControllers/CertificateViewer.swift rename DGCAWallet/ViewControllers/{CertCode.swift => CertificateViewer/CertCodeController.swift} (68%) rename DGCAWallet/ViewControllers/{CertPages.swift => CertificateViewer/CertPagesController.swift} (69%) create mode 100644 DGCAWallet/ViewControllers/CertificateViewer/CertificateController.swift create mode 100644 DGCAWallet/ViewControllers/CertificateViewer/CertificateViewerController.swift rename DGCAWallet/ViewControllers/{ImageViewerVC.swift => CertificateViewer/ImageViewerController.swift} (70%) rename DGCAWallet/ViewControllers/{PDFViewerVC.swift => CertificateViewer/PDFViewerController.swift} (73%) delete mode 100644 DGCAWallet/ViewControllers/CheckValidityVC.xib delete mode 100644 DGCAWallet/ViewControllers/Home.swift create mode 100644 DGCAWallet/ViewControllers/HomeController.swift delete mode 100644 DGCAWallet/ViewControllers/ImageViewerVC.xib delete mode 100644 DGCAWallet/ViewControllers/List.swift create mode 100644 DGCAWallet/ViewControllers/MainListController.swift delete mode 100644 DGCAWallet/ViewControllers/PDFViewerVC.xib delete mode 100644 DGCAWallet/ViewControllers/RuleValidationResultVC.swift delete mode 100644 DGCAWallet/ViewControllers/RuleValidationResultVC.xib create mode 100644 DGCAWallet/ViewControllers/ScanWalletController.swift delete mode 100644 DGCAWallet/ViewControllers/Settings.swift rename DGCAWallet/ViewControllers/{LicenseVC.swift => SettingsControllers/LicenseController.swift} (83%) rename DGCAWallet/ViewControllers/{LicenseList.swift => SettingsControllers/LicenseTableController.swift} (57%) create mode 100644 DGCAWallet/ViewControllers/SettingsControllers/SettingsTableController.swift create mode 100644 DGCAWallet/ViewControllers/TicketingControllers/CertificateListController.swift rename DGCAWallet/{Protocols/CertViewerDelegate.swift => ViewControllers/TicketingControllers/Models/CertificateRecord.swift} (82%) create mode 100644 DGCAWallet/ViewControllers/TicketingControllers/Models/TicketingAcceptance.swift create mode 100644 DGCAWallet/ViewControllers/TicketingControllers/ServerListController.swift create mode 100644 DGCAWallet/ViewControllers/TicketingControllers/TicketingAcceptanceController.swift create mode 100644 DGCAWallet/ViewControllers/TicketingControllers/TicketingShared.swift create mode 100644 DGCAWallet/ViewControllers/TicketingControllers/ValidationResultController.swift rename DGCAWallet/ViewControllers/{CheckValidityVC.swift => ValidityControllers/CheckValidityController.swift} (52%) create mode 100644 DGCAWallet/ViewControllers/ValidityControllers/RuleValidationResultVC.swift delete mode 100644 codestyle/checkstyle.xml diff --git a/.gitignore b/.gitignore index 83296ca..4e814a6 100644 --- a/.gitignore +++ b/.gitignore @@ -217,4 +217,5 @@ xcuserdata/ # -/Pods/ \ No newline at end of file +/Pods/ +DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata diff --git a/Context/Release/context.jsonc b/Context/Release/context.jsonc index 4b863b6..fdbd3af 100644 --- a/Context/Release/context.jsonc +++ b/Context/Release/context.jsonc @@ -1,9 +1,7 @@ { - // 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", @@ -21,21 +19,21 @@ ] }, "countryList": { - "url": "https://dgca-businessrule-service.cfapps.eu10.hana.ondemand.com/countrylist", + "url": "https://dgca-businessrule-service-eu-acc2.cfapps.eu10.hana.ondemand.com/countrylist", "pubKeys": [ "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" ] }, "rules": { - "url": "https://dgca-businessrule-service.cfapps.eu10.hana.ondemand.com/rules", + "url": "https://dgca-businessrule-service-eu-acc2.cfapps.eu10.hana.ondemand.com/rules", "pubKeys": [ "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" ] }, "valuesets": { - "url": "https://dgca-businessrule-service.cfapps.eu10.hana.ondemand.com/valuesets", + "url": "https://dgca-businessrule-service-eu-acc2.cfapps.eu10.hana.ondemand.com/valuesets", "pubKeys": [ "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" @@ -44,7 +42,6 @@ } }, "0.1.0": { - // Example for a version that is insecure and shouldn't start. "outdated": true } } diff --git a/Context/Test/context.jsonc b/Context/Test/context.jsonc index 8772b14..a19ee44 100644 --- a/Context/Test/context.jsonc +++ b/Context/Test/context.jsonc @@ -1,41 +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-verifier-service.cfapps.eu10.hana.ondemand.com/context", - "pubKeys": [ - "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", - "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" - ] - }, + "context": { + "url": "https://issuance-dgca-test.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", + "url": "https://dgca-issuance-web-eu-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", + "url": "https://dgca-businessrule-service-eu-test.cfapps.eu10.hana.ondemand.com/countrylist", "pubKeys": [ "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" ] }, "rules": { - "url": "https://dgca-businessrule-service.cfapps.eu10.hana.ondemand.com/rules", + "url": "https://dgca-businessrule-service-eu-test.cfapps.eu10.hana.ondemand.com/rules", "pubKeys": [ "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" ] }, "valuesets": { - "url": "https://dgca-businessrule-service.cfapps.eu10.hana.ondemand.com/valuesets", + "url": "https://dgca-businessrule-service-eu-test.cfapps.eu10.hana.ondemand.com/valuesets", "pubKeys": [ "lKdU1EbQubxyDDm2q3N8KclZ2C94Num3xXjG0pk+3eI=", "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=" @@ -44,7 +42,6 @@ } }, "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 de83298..3c5fc33 100644 --- a/DGCAWallet.xcodeproj/project.pbxproj +++ b/DGCAWallet.xcodeproj/project.pbxproj @@ -7,76 +7,42 @@ objects = { /* Begin PBXBuildFile section */ - 5C00071926CFAED2002E5013 /* UIImage+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00071826CFAED2002E5013 /* UIImage+.swift */; }; + 5C00071926CFAED2002E5013 /* UIImage+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00071826CFAED2002E5013 /* UIImage+String.swift */; }; 5C00071C26D38CC3002E5013 /* NFCHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00071B26D38CC3002E5013 /* NFCHelper.swift */; }; 5C00071D26D38CC3002E5013 /* NFCHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00071B26D38CC3002E5013 /* NFCHelper.swift */; }; 5C00071E26D38CC3002E5013 /* NFCHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00071B26D38CC3002E5013 /* NFCHelper.swift */; }; 5C00072226D3A40E002E5013 /* SavedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00072126D3A40E002E5013 /* SavedImage.swift */; }; 5C00072326D3A40E002E5013 /* SavedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00072126D3A40E002E5013 /* SavedImage.swift */; }; 5C00072426D3A40E002E5013 /* SavedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00072126D3A40E002E5013 /* SavedImage.swift */; }; - 5C00072626D3A6CD002E5013 /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00072526D3A6CD002E5013 /* String+.swift */; }; - 5C00072726D3A6CD002E5013 /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00072526D3A6CD002E5013 /* String+.swift */; }; - 5C00072826D3A6CD002E5013 /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00072526D3A6CD002E5013 /* String+.swift */; }; - 5C00072A26D3A77D002E5013 /* ImageDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00072926D3A77D002E5013 /* ImageDataStorage.swift */; }; - 5C00072B26D3A77D002E5013 /* ImageDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00072926D3A77D002E5013 /* ImageDataStorage.swift */; }; - 5C00072C26D3A77D002E5013 /* ImageDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00072926D3A77D002E5013 /* ImageDataStorage.swift */; }; 5C00072E26D3AC11002E5013 /* SavedPDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00072D26D3AC11002E5013 /* SavedPDF.swift */; }; 5C00072F26D3AC11002E5013 /* SavedPDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00072D26D3AC11002E5013 /* SavedPDF.swift */; }; 5C00073026D3AC11002E5013 /* SavedPDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00072D26D3AC11002E5013 /* SavedPDF.swift */; }; - 5C00073226D3AEDE002E5013 /* PdfDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073126D3AEDE002E5013 /* PdfDataStorage.swift */; }; - 5C00073326D3AEDE002E5013 /* PdfDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073126D3AEDE002E5013 /* PdfDataStorage.swift */; }; - 5C00073426D3AEDE002E5013 /* PdfDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073126D3AEDE002E5013 /* PdfDataStorage.swift */; }; 5C00073626D625D0002E5013 /* PDFTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073526D625D0002E5013 /* PDFTableViewCell.swift */; }; 5C00073726D625D0002E5013 /* PDFTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073526D625D0002E5013 /* PDFTableViewCell.swift */; }; 5C00073826D625D0002E5013 /* PDFTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073526D625D0002E5013 /* PDFTableViewCell.swift */; }; 5C00073A26D625DE002E5013 /* ImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073926D625DE002E5013 /* ImageTableViewCell.swift */; }; 5C00073B26D625DE002E5013 /* ImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073926D625DE002E5013 /* ImageTableViewCell.swift */; }; 5C00073C26D625DE002E5013 /* ImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073926D625DE002E5013 /* ImageTableViewCell.swift */; }; - 5C00073F26D641CC002E5013 /* ImageViewerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073D26D641CC002E5013 /* ImageViewerVC.swift */; }; - 5C00074026D641CC002E5013 /* ImageViewerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073D26D641CC002E5013 /* ImageViewerVC.swift */; }; - 5C00074126D641CC002E5013 /* ImageViewerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073D26D641CC002E5013 /* ImageViewerVC.swift */; }; - 5C00074226D641CC002E5013 /* ImageViewerVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C00073E26D641CC002E5013 /* ImageViewerVC.xib */; }; - 5C00074326D641CC002E5013 /* ImageViewerVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C00073E26D641CC002E5013 /* ImageViewerVC.xib */; }; - 5C00074426D641CC002E5013 /* ImageViewerVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C00073E26D641CC002E5013 /* ImageViewerVC.xib */; }; - 5C00074726D641E2002E5013 /* PDFViewerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00074526D641E2002E5013 /* PDFViewerVC.swift */; }; - 5C00074826D641E2002E5013 /* PDFViewerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00074526D641E2002E5013 /* PDFViewerVC.swift */; }; - 5C00074926D641E2002E5013 /* PDFViewerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00074526D641E2002E5013 /* PDFViewerVC.swift */; }; - 5C00074A26D641E2002E5013 /* PDFViewerVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C00074626D641E2002E5013 /* PDFViewerVC.xib */; }; - 5C00074B26D641E2002E5013 /* PDFViewerVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C00074626D641E2002E5013 /* PDFViewerVC.xib */; }; - 5C00074C26D641E2002E5013 /* PDFViewerVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C00074626D641E2002E5013 /* PDFViewerVC.xib */; }; - 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 */; }; + 5C00073F26D641CC002E5013 /* ImageViewerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00073D26D641CC002E5013 /* ImageViewerController.swift */; }; + 5C00074726D641E2002E5013 /* PDFViewerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00074526D641E2002E5013 /* PDFViewerController.swift */; }; + 5C50CF09269700550015EE29 /* CheckValidityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF07269700540015EE29 /* CheckValidityController.swift */; }; + 5C50CF0A269700550015EE29 /* CheckValidityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF07269700540015EE29 /* CheckValidityController.swift */; }; + 5C50CF0B269700550015EE29 /* CheckValidityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF07269700540015EE29 /* CheckValidityController.swift */; }; 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 */; }; + 5C50CF1B2697023B0015EE29 /* RuleErrorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF192697023B0015EE29 /* RuleErrorCell.swift */; }; + 5C50CF1C2697023B0015EE29 /* RuleErrorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF192697023B0015EE29 /* RuleErrorCell.swift */; }; + 5C50CF1D2697023B0015EE29 /* RuleErrorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF192697023B0015EE29 /* RuleErrorCell.swift */; }; 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 */; }; + 5C50CF2726972BCA0015EE29 /* SimpleValidityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF2526972BCA0015EE29 /* SimpleValidityCell.swift */; }; + 5C50CF2826972BCA0015EE29 /* SimpleValidityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF2526972BCA0015EE29 /* SimpleValidityCell.swift */; }; + 5C50CF2926972BCA0015EE29 /* SimpleValidityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF2526972BCA0015EE29 /* SimpleValidityCell.swift */; }; + 5C50CF2F26972BFD0015EE29 /* ExtendedValidityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF2D26972BFD0015EE29 /* ExtendedValidityCell.swift */; }; + 5C50CF3026972BFD0015EE29 /* ExtendedValidityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF2D26972BFD0015EE29 /* ExtendedValidityCell.swift */; }; + 5C50CF3126972BFD0015EE29 /* ExtendedValidityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50CF2D26972BFD0015EE29 /* ExtendedValidityCell.swift */; }; 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 */; }; @@ -86,64 +52,103 @@ 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 */; }; - 5CA450B026E20B690029324E /* context.jsonc in Resources */ = {isa = PBXBuildFile; fileRef = 5CFEDB18269DBB4C00074F66 /* context.jsonc */; }; - 5CA450B126E20B6A0029324E /* context.jsonc in Resources */ = {isa = PBXBuildFile; fileRef = 5CFEDB18269DBB4C00074F66 /* context.jsonc */; }; - 5CA450B226E20B6A0029324E /* context.jsonc in Resources */ = {isa = PBXBuildFile; fileRef = 5CFEDB18269DBB4C00074F66 /* context.jsonc */; }; + 5C556D4426F9DAF7007E2C2E /* TicketingAcceptanceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C556D4026F9DAF7007E2C2E /* TicketingAcceptanceController.swift */; }; + 5C556D4526F9DAF7007E2C2E /* TicketingAcceptanceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C556D4026F9DAF7007E2C2E /* TicketingAcceptanceController.swift */; }; + 5C556D4626F9DAF7007E2C2E /* TicketingAcceptanceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C556D4026F9DAF7007E2C2E /* TicketingAcceptanceController.swift */; }; + 5C556D4926F9F8AC007E2C2E /* CertificateListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C556D4726F9F8AC007E2C2E /* CertificateListController.swift */; }; + 5C556D4A26F9F8AC007E2C2E /* CertificateListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C556D4726F9F8AC007E2C2E /* CertificateListController.swift */; }; + 5C556D4B26F9F8AC007E2C2E /* CertificateListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C556D4726F9F8AC007E2C2E /* CertificateListController.swift */; }; + 5C556D5126F9F8C6007E2C2E /* ServerListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C556D4F26F9F8C6007E2C2E /* ServerListController.swift */; }; + 5C556D5226F9F8C6007E2C2E /* ServerListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C556D4F26F9F8C6007E2C2E /* ServerListController.swift */; }; + 5C556D5326F9F8C6007E2C2E /* ServerListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C556D4F26F9F8C6007E2C2E /* ServerListController.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 */; }; - 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 */; }; + 5CE07C842692E21E00A032F9 /* CertLogicManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C832692E21E00A032F9 /* CertLogicManager.swift */; }; + 5CE07C852692E21E00A032F9 /* CertLogicManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C832692E21E00A032F9 /* CertLogicManager.swift */; }; + 5CE07C862692E21E00A032F9 /* CertLogicManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CE07C832692E21E00A032F9 /* CertLogicManager.swift */; }; + CE13CF0A262DCDDA0070C80E /* CertificateViewerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE13CF09262DCDDA0070C80E /* CertificateViewerController.swift */; }; CE13CF23262DDF810070C80E /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE13CF22262DDF810070C80E /* RoundedButton.swift */; }; - CE1D1EF6263597A2004C8919 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1D1EF5263597A2004C8919 /* LocalData.swift */; }; CE1F155C2639F9E700736D48 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = CE1F155B2639F9E700736D48 /* SwiftyJSON */; }; CE260F56263DD1720083A200 /* WalletCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE260F55263DD1720083A200 /* WalletCell.swift */; }; CE37B643263867D700DEE13D /* SecureBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE37B642263867D700DEE13D /* SecureBackground.swift */; }; - CE4BD254264FF28900689FD6 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE4BD253264FF28900689FD6 /* Settings.swift */; }; + CE4BD254264FF28900689FD6 /* SettingsTableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE4BD253264FF28900689FD6 /* SettingsTableController.swift */; }; CE4BD255264FF39400689FD6 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE4BD257264FF39400689FD6 /* Settings.storyboard */; }; CE4BD258264FF39D00689FD6 /* CertificateViewer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE4BD25A264FF39D00689FD6 /* CertificateViewer.storyboard */; }; - CE56B5052639F48E00FB31B1 /* SwiftDGC in Frameworks */ = {isa = PBXBuildFile; productRef = CE56B5042639F48E00FB31B1 /* SwiftDGC */; }; CE56BC07264068110044FD3F /* GatewayConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE56BC06264068110044FD3F /* GatewayConnection.swift */; }; CE6B330E2651A54800845B8E /* SwiftDGC in Frameworks */ = {isa = PBXBuildFile; productRef = CE6B330D2651A54800845B8E /* SwiftDGC */; }; CE6D4A44264835F100A5D33D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CE6D4A46264835F100A5D33D /* Localizable.strings */; }; CE8096D9263B07BB00A65AD6 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8096D8263B07BB00A65AD6 /* UIColor.swift */; }; CE81533A263FF7EC0030D777 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = CE815339263FF7EC0030D777 /* README.md */; }; CE8912FB2634C6B900CB92AF /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = CE8912FA2634C6B900CB92AF /* Alamofire */; }; - CE891305263581D900CB92AF /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE891304263581D900CB92AF /* Home.swift */; }; - CEA15563262F6DAB0024B7AC /* CertViewerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA15562262F6DAB0024B7AC /* CertViewerDelegate.swift */; }; - CEA1556B262F784E0024B7AC /* SelfSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA1556A262F784E0024B7AC /* SelfSizedTableView.swift */; }; + CE891305263581D900CB92AF /* HomeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE891304263581D900CB92AF /* HomeController.swift */; }; CEA15570262F79DE0024B7AC /* InfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA1556F262F79DE0024B7AC /* InfoCell.swift */; }; CEA6D6EC261F8D2700715333 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6D6EB261F8D2700715333 /* AppDelegate.swift */; }; CEA6D6EE261F8D2700715333 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6D6ED261F8D2700715333 /* SceneDelegate.swift */; }; - CEA6D6F0261F8D2700715333 /* Scan.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6D6EF261F8D2700715333 /* Scan.swift */; }; CEA6D6F3261F8D2700715333 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CEA6D6F1261F8D2700715333 /* Main.storyboard */; }; CEA6D6F5261F8D2900715333 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CEA6D6F4261F8D2900715333 /* Assets.xcassets */; }; 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 */; }; 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 */; }; - CEDABD49263C70EF007A9B97 /* CertPages.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDABD48263C70EF007A9B97 /* CertPages.swift */; }; - CEE9DA55263C7D4000A31532 /* CertCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE9DA54263C7D4000A31532 /* CertCode.swift */; }; + CED949CA263B50CE00883558 /* MainListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED949C9263B50CE00883558 /* MainListController.swift */; }; + CEDABD40263C5FF4007A9B97 /* CertificateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDABD3F263C5FF4007A9B97 /* CertificateController.swift */; }; + CEDABD49263C70EF007A9B97 /* CertPagesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDABD48263C70EF007A9B97 /* CertPagesController.swift */; }; + CEE9DA55263C7D4000A31532 /* CertCodeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE9DA54263C7D4000A31532 /* CertCodeController.swift */; }; CEE9DA5D263C865200A31532 /* Brightness.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE9DA5C263C865200A31532 /* Brightness.swift */; }; CEFAD87F262714C4009AFEF9 /* EHNTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFAD87E262714C4009AFEF9 /* EHNTests.swift */; }; - DA01661C265541C0005B73A1 /* LicenseList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA01661B265541C0005B73A1 /* LicenseList.swift */; }; + D221E48B275E201D00D17049 /* CertificateRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = D221E48A275E201D00D17049 /* CertificateRecord.swift */; }; + D221E48D275E207000D17049 /* TicketingShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = D221E48C275E207000D17049 /* TicketingShared.swift */; }; + D2387C5E2745492600C55DF7 /* TicketingAcceptance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2387C5D2745492600C55DF7 /* TicketingAcceptance.swift */; }; + D2387C5F274558C200C55DF7 /* TicketingAcceptance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2387C5D2745492600C55DF7 /* TicketingAcceptance.swift */; }; + D2387C60274558C300C55DF7 /* TicketingAcceptance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2387C5D2745492600C55DF7 /* TicketingAcceptance.swift */; }; + D2387C622746A7FB00C55DF7 /* KeyChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2387C612746A7FB00C55DF7 /* KeyChain.swift */; }; + D2387C642746EE5500C55DF7 /* DGCLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2387C632746EE5500C55DF7 /* DGCLogger.swift */; }; + D2387C6B274BD5EA00C55DF7 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2387C6D274BD5EA00C55DF7 /* InfoPlist.strings */; }; + D23EF7A6272057F40097D82B /* CertificateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EF7A4272057F40097D82B /* CertificateCell.swift */; }; + D23EF7A7272057F40097D82B /* ServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EF7A5272057F40097D82B /* ServerCell.swift */; }; + D24C8951272353610090398A /* ScanWalletController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24C8950272353610090398A /* ScanWalletController.swift */; }; + D2B605BD2743CA92000D914A /* ValidationResultController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC54BE727201D660048B6BC /* ValidationResultController.swift */; }; + D2B605BE2743CA92000D914A /* ValidationResultController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC54BE727201D660048B6BC /* ValidationResultController.swift */; }; + D2BBDEC02798B25E0043A8A7 /* context.jsonc in Resources */ = {isa = PBXBuildFile; fileRef = 5CFEDB1A269DBB4C00074F66 /* context.jsonc */; }; + D2BBDEC12798B25E0043A8A7 /* context.jsonc in Resources */ = {isa = PBXBuildFile; fileRef = 5CFEDB1A269DBB4C00074F66 /* context.jsonc */; }; + D2BBDEC22798B25F0043A8A7 /* context.jsonc in Resources */ = {isa = PBXBuildFile; fileRef = 5CFEDB1A269DBB4C00074F66 /* context.jsonc */; }; + D2DFDB2427C4F49A00BA15B9 /* DGCBloomFilter in Frameworks */ = {isa = PBXBuildFile; productRef = D2DFDB2327C4F49A00BA15B9 /* DGCBloomFilter */; }; + D2DFDB2C27C4F99000BA15B9 /* DataStuctures.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DFDB2B27C4F99000BA15B9 /* DataStuctures.swift */; }; + D2DFDB2D27C4F99000BA15B9 /* DataStuctures.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DFDB2B27C4F99000BA15B9 /* DataStuctures.swift */; }; + D2DFDB2E27C4F99000BA15B9 /* DataStuctures.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DFDB2B27C4F99000BA15B9 /* DataStuctures.swift */; }; + D2DFDB3C27C5025D00BA15B9 /* SWCompression in Frameworks */ = {isa = PBXBuildFile; productRef = D2DFDB3B27C5025D00BA15B9 /* SWCompression */; }; + D2DFDB3E27C50E5900BA15B9 /* SquareViewFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DFDB3D27C50E5900BA15B9 /* SquareViewFinder.swift */; }; + D2DFDB3F27C50E5900BA15B9 /* SquareViewFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DFDB3D27C50E5900BA15B9 /* SquareViewFinder.swift */; }; + D2DFDB4027C50E5900BA15B9 /* SquareViewFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DFDB3D27C50E5900BA15B9 /* SquareViewFinder.swift */; }; + D2E6CAEF273A93F300E9AF1F /* DataCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CAEE273A93F300E9AF1F /* DataCenter.swift */; }; + D2E6CAF0273A93F300E9AF1F /* DataCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CAEE273A93F300E9AF1F /* DataCenter.swift */; }; + D2E6CAF1273A93F300E9AF1F /* DataCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CAEE273A93F300E9AF1F /* DataCenter.swift */; }; + D2E6CB03273A948600E9AF1F /* SharedConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB02273A948600E9AF1F /* SharedConstants.swift */; }; + D2E6CB04273A948600E9AF1F /* SharedConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB02273A948600E9AF1F /* SharedConstants.swift */; }; + D2E6CB05273A948600E9AF1F /* SharedConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB02273A948600E9AF1F /* SharedConstants.swift */; }; + D2E6CB07273A94EA00E9AF1F /* LocalDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB06273A94EA00E9AF1F /* LocalDataManager.swift */; }; + D2E6CB08273A94EA00E9AF1F /* LocalDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB06273A94EA00E9AF1F /* LocalDataManager.swift */; }; + D2E6CB09273A94EA00E9AF1F /* LocalDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB06273A94EA00E9AF1F /* LocalDataManager.swift */; }; + D2E6CB0B273AA00200E9AF1F /* ImageDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB0A273AA00200E9AF1F /* ImageDataManager.swift */; }; + D2E6CB0C273AA00200E9AF1F /* ImageDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB0A273AA00200E9AF1F /* ImageDataManager.swift */; }; + D2E6CB0D273AA00200E9AF1F /* ImageDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB0A273AA00200E9AF1F /* ImageDataManager.swift */; }; + D2E6CB0F273AA34200E9AF1F /* PdfDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB0E273AA34200E9AF1F /* PdfDataManager.swift */; }; + D2E6CB10273AA34200E9AF1F /* PdfDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB0E273AA34200E9AF1F /* PdfDataManager.swift */; }; + D2E6CB11273AA34200E9AF1F /* PdfDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB0E273AA34200E9AF1F /* PdfDataManager.swift */; }; + D2E6CB13273BE49300E9AF1F /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB12273BE49300E9AF1F /* CertificateValidator.swift */; }; + D2E6CB14273BE49300E9AF1F /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB12273BE49300E9AF1F /* CertificateValidator.swift */; }; + D2E6CB15273BE49300E9AF1F /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E6CB12273BE49300E9AF1F /* CertificateValidator.swift */; }; + DA01661C265541C0005B73A1 /* LicenseTableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA01661B265541C0005B73A1 /* LicenseTableController.swift */; }; DA01661E26554992005B73A1 /* OpenSourceNotices.json in Resources */ = {isa = PBXBuildFile; fileRef = DA01661D26554992005B73A1 /* OpenSourceNotices.json */; }; DA01662026554E02005B73A1 /* LicenseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA01661F26554E02005B73A1 /* LicenseCell.swift */; }; - DA01662226558B2F005B73A1 /* LicenseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA01662126558B2F005B73A1 /* LicenseVC.swift */; }; - DA017DE626552259006E4D49 /* Licenses.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DA017DE526552259006E4D49 /* Licenses.storyboard */; }; + DA01662226558B2F005B73A1 /* LicenseController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA01662126558B2F005B73A1 /* LicenseController.swift */; }; + FE1D1A6B27148CBE00765A9A /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = FE1D1A6A27148CBE00765A9A /* CryptoSwift */; }; + FE6E78882702033300C142A3 /* JWTDecode in Frameworks */ = {isa = PBXBuildFile; productRef = FE6E78872702033300C142A3 /* JWTDecode */; }; + FEAEA96526FBB13600B94FFF /* AccessTokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEAEA96426FBB13600B94FFF /* AccessTokenResponse.swift */; }; + FEBE53B62721A3EA003B61FB /* LimitationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEBE53B42721A3EA003B61FB /* LimitationCell.swift */; }; + FEC54BE4271F26990048B6BC /* IdentityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC54BE3271F26990048B6BC /* IdentityService.swift */; }; + FEC54BE6271F27180048B6BC /* AccessTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC54BE5271F27180048B6BC /* AccessTokenService.swift */; }; + FEC54BE927201D660048B6BC /* ValidationResultController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC54BE727201D660048B6BC /* ValidationResultController.swift */; }; + FEEAD6DB273BBE9600D883EB /* TokenInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEAD6DA273BBE9600D883EB /* TokenInfoCell.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -164,62 +169,47 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 5C00071826CFAED2002E5013 /* UIImage+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+.swift"; sourceTree = ""; }; + 5C00071826CFAED2002E5013 /* UIImage+String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+String.swift"; sourceTree = ""; }; 5C00071B26D38CC3002E5013 /* NFCHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFCHelper.swift; sourceTree = ""; }; 5C00071F26D38D58002E5013 /* DGCAWallet.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = DGCAWallet.entitlements; sourceTree = SOURCE_ROOT; }; 5C00072026D38D6D002E5013 /* DGCAWallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DGCAWallet.entitlements; sourceTree = ""; }; 5C00072126D3A40E002E5013 /* SavedImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedImage.swift; sourceTree = ""; }; - 5C00072526D3A6CD002E5013 /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; - 5C00072926D3A77D002E5013 /* ImageDataStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDataStorage.swift; sourceTree = ""; }; 5C00072D26D3AC11002E5013 /* SavedPDF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedPDF.swift; sourceTree = ""; }; - 5C00073126D3AEDE002E5013 /* PdfDataStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PdfDataStorage.swift; sourceTree = ""; }; - 5C00073526D625D0002E5013 /* PDFTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFTableViewCell.swift; sourceTree = ""; }; - 5C00073926D625DE002E5013 /* ImageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTableViewCell.swift; sourceTree = ""; }; - 5C00073D26D641CC002E5013 /* ImageViewerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewerVC.swift; sourceTree = ""; }; - 5C00073E26D641CC002E5013 /* ImageViewerVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ImageViewerVC.xib; sourceTree = ""; }; - 5C00074526D641E2002E5013 /* PDFViewerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFViewerVC.swift; sourceTree = ""; }; - 5C00074626D641E2002E5013 /* PDFViewerVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PDFViewerVC.xib; sourceTree = ""; }; - 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 = ""; }; + 5C00073526D625D0002E5013 /* PDFTableViewCell.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = PDFTableViewCell.swift; sourceTree = ""; tabWidth = 2; }; + 5C00073926D625DE002E5013 /* ImageTableViewCell.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ImageTableViewCell.swift; sourceTree = ""; tabWidth = 2; }; + 5C00073D26D641CC002E5013 /* ImageViewerController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ImageViewerController.swift; sourceTree = ""; tabWidth = 2; }; + 5C00074526D641E2002E5013 /* PDFViewerController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = PDFViewerController.swift; sourceTree = ""; tabWidth = 2; }; + 5C50CF07269700540015EE29 /* CheckValidityController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = CheckValidityController.swift; sourceTree = ""; tabWidth = 2; }; + 5C50CF0F269700710015EE29 /* RuleValidationResultVC.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = RuleValidationResultVC.swift; sourceTree = ""; tabWidth = 2; }; + 5C50CF192697023B0015EE29 /* RuleErrorCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleErrorCell.swift; 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 = ""; }; + 5C50CF2526972BCA0015EE29 /* SimpleValidityCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleValidityCell.swift; sourceTree = ""; }; + 5C50CF2D26972BFD0015EE29 /* ExtendedValidityCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedValidityCell.swift; 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 = ""; }; + 5C556D4026F9DAF7007E2C2E /* TicketingAcceptanceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = TicketingAcceptanceController.swift; sourceTree = ""; tabWidth = 2; }; + 5C556D4726F9F8AC007E2C2E /* CertificateListController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = CertificateListController.swift; sourceTree = ""; tabWidth = 2; }; + 5C556D4F26F9F8C6007E2C2E /* ServerListController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ServerListController.swift; sourceTree = ""; tabWidth = 2; }; + 5CE07C832692E21E00A032F9 /* CertLogicManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CertLogicManager.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 = ""; }; + CE13CF09262DCDDA0070C80E /* CertificateViewerController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = CertificateViewerController.swift; sourceTree = ""; tabWidth = 2; }; CE13CF22262DDF810070C80E /* RoundedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedButton.swift; sourceTree = ""; }; - CE1D1EF5263597A2004C8919 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = ""; }; CE260F55263DD1720083A200 /* WalletCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletCell.swift; sourceTree = ""; }; CE37B642263867D700DEE13D /* SecureBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackground.swift; sourceTree = ""; }; - CE4BD253264FF28900689FD6 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; + CE4BD253264FF28900689FD6 /* SettingsTableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = SettingsTableController.swift; sourceTree = ""; tabWidth = 2; }; CE4BD256264FF39400689FD6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Settings.storyboard; sourceTree = ""; }; CE4BD259264FF39D00689FD6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/CertificateViewer.storyboard; sourceTree = ""; }; - CE56BC06264068110044FD3F /* GatewayConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GatewayConnection.swift; sourceTree = ""; }; + CE56BC06264068110044FD3F /* GatewayConnection.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = GatewayConnection.swift; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; CE6D4A45264835F100A5D33D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; CE8096D8263B07BB00A65AD6 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; CE815339263FF7EC0030D777 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; - CE891304263581D900CB92AF /* Home.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Home.swift; sourceTree = ""; }; - CEA15562262F6DAB0024B7AC /* CertViewerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertViewerDelegate.swift; sourceTree = ""; }; - CEA1556A262F784E0024B7AC /* SelfSizedTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfSizedTableView.swift; sourceTree = ""; }; + CE891304263581D900CB92AF /* HomeController.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = HomeController.swift; sourceTree = ""; tabWidth = 4; }; CEA1556F262F79DE0024B7AC /* InfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoCell.swift; sourceTree = ""; }; CEA6D6E8261F8D2700715333 /* DGCAWallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DGCAWallet.app; sourceTree = BUILT_PRODUCTS_DIR; }; CEA6D6EB261F8D2700715333 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; CEA6D6ED261F8D2700715333 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - CEA6D6EF261F8D2700715333 /* Scan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scan.swift; sourceTree = ""; }; CEA6D6F2261F8D2700715333 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; CEA6D6F4261F8D2900715333 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CEA6D6F7261F8D2900715333 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -231,18 +221,45 @@ 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 = ""; }; 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 = ""; }; - CEDABD48263C70EF007A9B97 /* CertPages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertPages.swift; sourceTree = ""; }; + CED949C9263B50CE00883558 /* MainListController.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = MainListController.swift; sourceTree = ""; tabWidth = 4; }; + CEDABD3F263C5FF4007A9B97 /* CertificateController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = CertificateController.swift; sourceTree = ""; tabWidth = 2; }; + CEDABD48263C70EF007A9B97 /* CertPagesController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertPagesController.swift; sourceTree = ""; }; CEDCA79B2639E77800FCB83E /* Package.resolved */ = {isa = PBXFileReference; lastKnownFileType = text; name = Package.resolved; path = DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved; sourceTree = SOURCE_ROOT; }; - CEE9DA54263C7D4000A31532 /* CertCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertCode.swift; sourceTree = ""; }; + CEE9DA54263C7D4000A31532 /* CertCodeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertCodeController.swift; sourceTree = ""; }; CEE9DA5C263C865200A31532 /* Brightness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Brightness.swift; sourceTree = ""; }; CEFAD87E262714C4009AFEF9 /* EHNTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EHNTests.swift; sourceTree = ""; }; - DA01661B265541C0005B73A1 /* LicenseList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseList.swift; sourceTree = ""; }; + D221E48A275E201D00D17049 /* CertificateRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateRecord.swift; sourceTree = ""; }; + D221E48C275E207000D17049 /* TicketingShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketingShared.swift; sourceTree = ""; }; + D2387C5D2745492600C55DF7 /* TicketingAcceptance.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = TicketingAcceptance.swift; sourceTree = ""; tabWidth = 2; }; + D2387C612746A7FB00C55DF7 /* KeyChain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyChain.swift; sourceTree = ""; }; + D2387C632746EE5500C55DF7 /* DGCLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DGCLogger.swift; sourceTree = ""; }; + D2387C67274853BC00C55DF7 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + D2387C6C274BD5EA00C55DF7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + D2387C6E274BD5EE00C55DF7 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; + D2387C70274BD84E00C55DF7 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/LaunchScreen.strings; sourceTree = ""; }; + D2387C72274BD85200C55DF7 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LaunchScreen.strings; sourceTree = ""; }; + D23EF7A4272057F40097D82B /* CertificateCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CertificateCell.swift; sourceTree = ""; }; + D23EF7A5272057F40097D82B /* ServerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerCell.swift; sourceTree = ""; }; + D24C8950272353610090398A /* ScanWalletController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ScanWalletController.swift; sourceTree = ""; tabWidth = 2; }; + D2DFDB2B27C4F99000BA15B9 /* DataStuctures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStuctures.swift; sourceTree = ""; }; + D2DFDB3D27C50E5900BA15B9 /* SquareViewFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SquareViewFinder.swift; sourceTree = ""; }; + D2E6CAEE273A93F300E9AF1F /* DataCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = DataCenter.swift; sourceTree = ""; tabWidth = 4; }; + D2E6CB02273A948600E9AF1F /* SharedConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedConstants.swift; sourceTree = ""; }; + D2E6CB06273A94EA00E9AF1F /* LocalDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = LocalDataManager.swift; sourceTree = ""; tabWidth = 4; }; + D2E6CB0A273AA00200E9AF1F /* ImageDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ImageDataManager.swift; sourceTree = ""; tabWidth = 2; }; + D2E6CB0E273AA34200E9AF1F /* PdfDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = PdfDataManager.swift; sourceTree = ""; tabWidth = 2; }; + D2E6CB12273BE49300E9AF1F /* CertificateValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CertificateValidator.swift; sourceTree = ""; }; + DA01661B265541C0005B73A1 /* LicenseTableController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = LicenseTableController.swift; sourceTree = ""; tabWidth = 2; }; DA01661D26554992005B73A1 /* OpenSourceNotices.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = OpenSourceNotices.json; sourceTree = ""; }; DA01661F26554E02005B73A1 /* LicenseCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseCell.swift; sourceTree = ""; }; - DA01662126558B2F005B73A1 /* LicenseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseVC.swift; sourceTree = ""; }; - DA017DE526552259006E4D49 /* Licenses.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Licenses.storyboard; sourceTree = ""; }; + DA01662126558B2F005B73A1 /* LicenseController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = LicenseController.swift; sourceTree = ""; tabWidth = 2; }; + FE6F225327142AB1003AE754 /* dgca-app-core-ios */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "dgca-app-core-ios"; path = "../dgca-app-core-ios"; sourceTree = ""; }; + FEAEA96426FBB13600B94FFF /* AccessTokenResponse.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = AccessTokenResponse.swift; sourceTree = ""; tabWidth = 2; }; + FEBE53B42721A3EA003B61FB /* LimitationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LimitationCell.swift; sourceTree = ""; }; + FEC54BE3271F26990048B6BC /* IdentityService.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = IdentityService.swift; sourceTree = ""; tabWidth = 2; }; + FEC54BE5271F27180048B6BC /* AccessTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessTokenService.swift; sourceTree = ""; }; + FEC54BE727201D660048B6BC /* ValidationResultController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ValidationResultController.swift; sourceTree = ""; tabWidth = 2; }; + FEEAD6DA273BBE9600D883EB /* TokenInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenInfoCell.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -251,11 +268,13 @@ buildActionMask = 2147483647; files = ( CE8912FB2634C6B900CB92AF /* Alamofire in Frameworks */, + D2DFDB3C27C5025D00BA15B9 /* SWCompression in Frameworks */, 5CE07C762692E0D900A032F9 /* CertLogic in Frameworks */, - CE56B5052639F48E00FB31B1 /* SwiftDGC in Frameworks */, + FE6E78882702033300C142A3 /* JWTDecode in Frameworks */, + D2DFDB2427C4F49A00BA15B9 /* DGCBloomFilter in Frameworks */, CE6B330E2651A54800845B8E /* SwiftDGC in Frameworks */, CE1F155C2639F9E700736D48 /* SwiftyJSON in Frameworks */, - CE13CF00262DCC180070C80E /* FloatingPanel in Frameworks */, + FE1D1A6B27148CBE00765A9A /* CryptoSwift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -288,23 +307,12 @@ 5C50CF172697009E0015EE29 /* CheckValidityCells */ = { isa = PBXGroup; children = ( - 5C50CF2526972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift */, - 5C50CF2626972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib */, - 5C50CF2D26972BFD0015EE29 /* CellWithDateAndCountryTVC.swift */, - 5C50CF2E26972BFD0015EE29 /* CellWithDateAndCountryTVC.xib */, + 5C50CF2526972BCA0015EE29 /* SimpleValidityCell.swift */, + 5C50CF2D26972BFD0015EE29 /* ExtendedValidityCell.swift */, ); path = CheckValidityCells; sourceTree = ""; }; - 5C50CF18269700B30015EE29 /* RuleCell */ = { - isa = PBXGroup; - children = ( - 5C50CF192697023B0015EE29 /* RuleErrorTVC.swift */, - 5C50CF1A2697023B0015EE29 /* RuleErrorTVC.xib */, - ); - path = RuleCell; - sourceTree = ""; - }; 5CFEDB16269DBB4C00074F66 /* Context */ = { isa = PBXGroup; children = ( @@ -337,7 +345,6 @@ CEA6D6F1261F8D2700715333 /* Main.storyboard */, CE4BD25A264FF39D00689FD6 /* CertificateViewer.storyboard */, CE4BD257264FF39400689FD6 /* Settings.storyboard */, - DA017DE526552259006E4D49 /* Licenses.storyboard */, ); path = Storyboards; sourceTree = ""; @@ -346,9 +353,10 @@ isa = PBXGroup; children = ( CE37B642263867D700DEE13D /* SecureBackground.swift */, + D2387C612746A7FB00C55DF7 /* KeyChain.swift */, CEE9DA5C263C865200A31532 /* Brightness.swift */, CE56BC06264068110044FD3F /* GatewayConnection.swift */, - 5CE07C832692E21E00A032F9 /* CertLogicEngineManager.swift */, + 5CE07C832692E21E00A032F9 /* CertLogicManager.swift */, ); path = Services; sourceTree = ""; @@ -361,8 +369,7 @@ 5C50CF21269704240015EE29 /* UIViewController+.swift */, 5C50CF3926973D9D0015EE29 /* UserDefaults+.swift */, 5C50CF3D26983E090015EE29 /* UITableView+.swift */, - 5C00071826CFAED2002E5013 /* UIImage+.swift */, - 5C00072526D3A6CD002E5013 /* String+.swift */, + 5C00071826CFAED2002E5013 /* UIImage+String.swift */, ); path = Extensions; sourceTree = ""; @@ -370,24 +377,13 @@ CE13CF1C262DDE600070C80E /* ViewControllers */ = { isa = PBXGroup; children = ( - CE13CF09262DCDDA0070C80E /* CertificateViewer.swift */, - CED949C9263B50CE00883558 /* List.swift */, - CEA6D6EF261F8D2700715333 /* Scan.swift */, - CEE9DA54263C7D4000A31532 /* CertCode.swift */, - CE891304263581D900CB92AF /* Home.swift */, - CEDABD48263C70EF007A9B97 /* CertPages.swift */, - CEDABD3F263C5FF4007A9B97 /* CertTable.swift */, - CE4BD253264FF28900689FD6 /* Settings.swift */, - DA01661B265541C0005B73A1 /* LicenseList.swift */, - DA01662126558B2F005B73A1 /* LicenseVC.swift */, - 5C50CF07269700540015EE29 /* CheckValidityVC.swift */, - 5C50CF08269700540015EE29 /* CheckValidityVC.xib */, - 5C50CF0F269700710015EE29 /* RuleValidationResultVC.swift */, - 5C50CF10269700710015EE29 /* RuleValidationResultVC.xib */, - 5C00073D26D641CC002E5013 /* ImageViewerVC.swift */, - 5C00073E26D641CC002E5013 /* ImageViewerVC.xib */, - 5C00074526D641E2002E5013 /* PDFViewerVC.swift */, - 5C00074626D641E2002E5013 /* PDFViewerVC.xib */, + D24C8950272353610090398A /* ScanWalletController.swift */, + CED949C9263B50CE00883558 /* MainListController.swift */, + CE891304263581D900CB92AF /* HomeController.swift */, + D2B605BA2743C97D000D914A /* CertificateViewer */, + D2B605BB2743C9BB000D914A /* SettingsControllers */, + D2B605BF2743CC25000D914A /* ValidityControllers */, + D2B605BC2743CA18000D914A /* TicketingControllers */, ); path = ViewControllers; sourceTree = ""; @@ -395,16 +391,19 @@ CE13CF1D262DDE730070C80E /* Components */ = { isa = PBXGroup; children = ( - 5C50CF18269700B30015EE29 /* RuleCell */, + D23EF7A4272057F40097D82B /* CertificateCell.swift */, + D23EF7A5272057F40097D82B /* ServerCell.swift */, + 5C50CF192697023B0015EE29 /* RuleErrorCell.swift */, + D2DFDB3D27C50E5900BA15B9 /* SquareViewFinder.swift */, 5C50CF172697009E0015EE29 /* CheckValidityCells */, - CE13CF0E262DD0D80070C80E /* FullFloatingPanelLayout.swift */, CE13CF22262DDF810070C80E /* RoundedButton.swift */, CE260F55263DD1720083A200 /* WalletCell.swift */, - CEA1556A262F784E0024B7AC /* SelfSizedTableView.swift */, CEA1556F262F79DE0024B7AC /* InfoCell.swift */, DA01661F26554E02005B73A1 /* LicenseCell.swift */, 5C00073526D625D0002E5013 /* PDFTableViewCell.swift */, 5C00073926D625DE002E5013 /* ImageTableViewCell.swift */, + FEBE53B42721A3EA003B61FB /* LimitationCell.swift */, + FEEAD6DA273BBE9600D883EB /* TokenInfoCell.swift */, ); path = Components; sourceTree = ""; @@ -419,6 +418,7 @@ CEA6D6ED261F8D2700715333 /* SceneDelegate.swift */, CEA6D6F4261F8D2900715333 /* Assets.xcassets */, CEA6D6F9261F8D2900715333 /* Info.plist */, + D2387C6D274BD5EA00C55DF7 /* InfoPlist.strings */, CE6D4A46264835F100A5D33D /* Localizable.strings */, DA01661D26554992005B73A1 /* OpenSourceNotices.json */, ); @@ -428,15 +428,14 @@ CE157F8B262E24EC00FE4821 /* Models */ = { isa = PBXGroup; children = ( - CE1D1EF5263597A2004C8919 /* LocalData.swift */, - 5CE07C772692E1CC00A032F9 /* CountryDataStorage.swift */, - 5CE07C792692E1CD00A032F9 /* RulesDataStorage.swift */, - 5CE07C782692E1CD00A032F9 /* ValueSetsDataStorage.swift */, + D2E6CB02273A948600E9AF1F /* SharedConstants.swift */, + D2E6CB12273BE49300E9AF1F /* CertificateValidator.swift */, + D2387C632746EE5500C55DF7 /* DGCLogger.swift */, + D2E6CAED273A93B400E9AF1F /* DataStorageManagement */, 5C50CF352697315B0015EE29 /* ValidityCellModel.swift */, 5C00072126D3A40E002E5013 /* SavedImage.swift */, - 5C00072926D3A77D002E5013 /* ImageDataStorage.swift */, 5C00072D26D3AC11002E5013 /* SavedPDF.swift */, - 5C00073126D3AEDE002E5013 /* PdfDataStorage.swift */, + FEAEA96426FBB13600B94FFF /* AccessTokenResponse.swift */, ); path = Models; sourceTree = ""; @@ -448,17 +447,10 @@ name = Frameworks; sourceTree = ""; }; - CEA15561262F6DA10024B7AC /* Protocols */ = { - isa = PBXGroup; - children = ( - CEA15562262F6DAB0024B7AC /* CertViewerDelegate.swift */, - ); - path = Protocols; - sourceTree = ""; - }; CEA6D6DF261F8D2700715333 = { isa = PBXGroup; children = ( + FE6F225327142AB1003AE754 /* dgca-app-core-ios */, CEA6D6EA261F8D2700715333 /* DGCAWallet */, CEA6D701261F8D2900715333 /* DGCAWalletTests */, CEA6D70C261F8D2900715333 /* DGCAWalletUITests */, @@ -481,15 +473,16 @@ isa = PBXGroup; children = ( 5C00072026D38D6D002E5013 /* DGCAWallet.entitlements */, + CE13CF1E262DDE800070C80E /* SupportingFiles */, + FEC54BDF271F24120048B6BC /* Ticketing */, 5C00071A26D38C5C002E5013 /* NFC */, - CEA15561262F6DA10024B7AC /* Protocols */, CE157F8B262E24EC00FE4821 /* Models */, - CE13CF1E262DDE800070C80E /* SupportingFiles */, CE13CF1D262DDE730070C80E /* Components */, CE13CF1C262DDE600070C80E /* ViewControllers */, CE13CF1B262DDE540070C80E /* Extensions */, CE13CF1A262DDE330070C80E /* Services */, CE13CF19262DDE200070C80E /* Storyboards */, + D2DFDB1F27C4F40500BA15B9 /* Resources */, ); path = DGCAWallet; sourceTree = ""; @@ -513,6 +506,96 @@ path = DGCAWalletUITests; sourceTree = ""; }; + D2387C5C274548EC00C55DF7 /* Models */ = { + isa = PBXGroup; + children = ( + D221E48A275E201D00D17049 /* CertificateRecord.swift */, + D2387C5D2745492600C55DF7 /* TicketingAcceptance.swift */, + ); + path = Models; + sourceTree = ""; + }; + D2B605BA2743C97D000D914A /* CertificateViewer */ = { + isa = PBXGroup; + children = ( + CE13CF09262DCDDA0070C80E /* CertificateViewerController.swift */, + 5C00074526D641E2002E5013 /* PDFViewerController.swift */, + 5C00073D26D641CC002E5013 /* ImageViewerController.swift */, + CEDABD48263C70EF007A9B97 /* CertPagesController.swift */, + CEE9DA54263C7D4000A31532 /* CertCodeController.swift */, + CEDABD3F263C5FF4007A9B97 /* CertificateController.swift */, + ); + path = CertificateViewer; + sourceTree = ""; + }; + D2B605BB2743C9BB000D914A /* SettingsControllers */ = { + isa = PBXGroup; + children = ( + CE4BD253264FF28900689FD6 /* SettingsTableController.swift */, + DA01661B265541C0005B73A1 /* LicenseTableController.swift */, + DA01662126558B2F005B73A1 /* LicenseController.swift */, + ); + path = SettingsControllers; + sourceTree = ""; + }; + D2B605BC2743CA18000D914A /* TicketingControllers */ = { + isa = PBXGroup; + children = ( + D221E48C275E207000D17049 /* TicketingShared.swift */, + D2387C5C274548EC00C55DF7 /* Models */, + 5C556D4726F9F8AC007E2C2E /* CertificateListController.swift */, + 5C556D4F26F9F8C6007E2C2E /* ServerListController.swift */, + 5C556D4026F9DAF7007E2C2E /* TicketingAcceptanceController.swift */, + FEC54BE727201D660048B6BC /* ValidationResultController.swift */, + ); + path = TicketingControllers; + sourceTree = ""; + }; + D2B605BF2743CC25000D914A /* ValidityControllers */ = { + isa = PBXGroup; + children = ( + 5C50CF07269700540015EE29 /* CheckValidityController.swift */, + 5C50CF0F269700710015EE29 /* RuleValidationResultVC.swift */, + ); + path = ValidityControllers; + sourceTree = ""; + }; + D2DFDB1F27C4F40500BA15B9 /* Resources */ = { + isa = PBXGroup; + children = ( + ); + path = Resources; + sourceTree = ""; + }; + D2E6CAED273A93B400E9AF1F /* DataStorageManagement */ = { + isa = PBXGroup; + children = ( + D2DFDB2B27C4F99000BA15B9 /* DataStuctures.swift */, + D2E6CAEE273A93F300E9AF1F /* DataCenter.swift */, + D2E6CB06273A94EA00E9AF1F /* LocalDataManager.swift */, + D2E6CB0A273AA00200E9AF1F /* ImageDataManager.swift */, + D2E6CB0E273AA34200E9AF1F /* PdfDataManager.swift */, + ); + path = DataStorageManagement; + sourceTree = ""; + }; + FEC54BDF271F24120048B6BC /* Ticketing */ = { + isa = PBXGroup; + children = ( + FEC54BE2271F24F80048B6BC /* Identity */, + ); + path = Ticketing; + sourceTree = ""; + }; + FEC54BE2271F24F80048B6BC /* Identity */ = { + isa = PBXGroup; + children = ( + FEC54BE3271F26990048B6BC /* IdentityService.swift */, + FEC54BE5271F27180048B6BC /* AccessTokenService.swift */, + ); + path = Identity; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -531,12 +614,14 @@ ); name = DGCAWallet; packageProductDependencies = ( - CE13CEFF262DCC180070C80E /* FloatingPanel */, CE8912FA2634C6B900CB92AF /* Alamofire */, - CE56B5042639F48E00FB31B1 /* SwiftDGC */, CE1F155B2639F9E700736D48 /* SwiftyJSON */, CE6B330D2651A54800845B8E /* SwiftDGC */, 5CE07C752692E0D900A032F9 /* CertLogic */, + FE6E78872702033300C142A3 /* JWTDecode */, + FE1D1A6A27148CBE00765A9A /* CryptoSwift */, + D2DFDB2327C4F49A00BA15B9 /* DGCBloomFilter */, + D2DFDB3B27C5025D00BA15B9 /* SWCompression */, ); productName = DGCAWallet; productReference = CEA6D6E8261F8D2700715333 /* DGCAWallet.app */; @@ -607,14 +692,18 @@ knownRegions = ( en, Base, + de, ); mainGroup = CEA6D6DF261F8D2700715333; packageReferences = ( - CE13CEFE262DCC180070C80E /* XCRemoteSwiftPackageReference "FloatingPanel" */, CE8912F92634C6B900CB92AF /* XCRemoteSwiftPackageReference "Alamofire" */, CE1F155A2639F9E700736D48 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, CE6B330C2651A54800845B8E /* XCRemoteSwiftPackageReference "dgca-app-core-ios" */, 5CE07C742692E0D900A032F9 /* XCRemoteSwiftPackageReference "dgc-certlogic-ios" */, + FE6E78862702033200C142A3 /* XCRemoteSwiftPackageReference "JWTDecode.swift" */, + FE1D1A6927148CBE00765A9A /* XCRemoteSwiftPackageReference "CryptoSwift" */, + D2DFDB2227C4F49A00BA15B9 /* XCRemoteSwiftPackageReference "dgc-bloomfilter-ios" */, + D2DFDB3A27C5025D00BA15B9 /* XCRemoteSwiftPackageReference "SWCompression" */, ); productRefGroup = CEA6D6E9261F8D2700715333 /* Products */; projectDirPath = ""; @@ -632,23 +721,16 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5C00074226D641CC002E5013 /* ImageViewerVC.xib in Resources */, - 5CA450B026E20B690029324E /* context.jsonc in Resources */, 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 */, CE81533A263FF7EC0030D777 /* README.md in Resources */, - 5C50CF1E2697023B0015EE29 /* RuleErrorTVC.xib in Resources */, + D2BBDEC02798B25E0043A8A7 /* context.jsonc in Resources */, DA01661E26554992005B73A1 /* OpenSourceNotices.json in Resources */, CEA6D6F5261F8D2900715333 /* Assets.xcassets in Resources */, - 5C50CF0C269700550015EE29 /* CheckValidityVC.xib in Resources */, CEA6D6F3261F8D2700715333 /* Main.storyboard in Resources */, - 5C00074A26D641E2002E5013 /* PDFViewerVC.xib in Resources */, - 5C50CF14269700710015EE29 /* RuleValidationResultVC.xib in Resources */, - DA017DE626552259006E4D49 /* Licenses.storyboard in Resources */, - 5C50CF2A26972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib in Resources */, + D2387C6B274BD5EA00C55DF7 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -656,14 +738,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5C50CF15269700710015EE29 /* RuleValidationResultVC.xib in Resources */, - 5CA450B126E20B6A0029324E /* context.jsonc in Resources */, - 5C00074326D641CC002E5013 /* ImageViewerVC.xib in Resources */, - 5C50CF2B26972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib in Resources */, - 5C50CF3326972BFD0015EE29 /* CellWithDateAndCountryTVC.xib in Resources */, - 5C00074B26D641E2002E5013 /* PDFViewerVC.xib in Resources */, - 5C50CF0D269700550015EE29 /* CheckValidityVC.xib in Resources */, - 5C50CF1F2697023B0015EE29 /* RuleErrorTVC.xib in Resources */, + D2BBDEC12798B25E0043A8A7 /* context.jsonc in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -671,14 +746,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5C50CF16269700710015EE29 /* RuleValidationResultVC.xib in Resources */, - 5CA450B226E20B6A0029324E /* context.jsonc in Resources */, - 5C00074426D641CC002E5013 /* ImageViewerVC.xib in Resources */, - 5C50CF2C26972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.xib in Resources */, - 5C50CF3426972BFD0015EE29 /* CellWithDateAndCountryTVC.xib in Resources */, - 5C00074C26D641E2002E5013 /* PDFViewerVC.xib in Resources */, - 5C50CF0E269700550015EE29 /* CheckValidityVC.xib in Resources */, - 5C50CF202697023B0015EE29 /* RuleErrorTVC.xib in Resources */, + D2BBDEC22798B25F0043A8A7 /* context.jsonc in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -700,7 +768,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n#if which swiftlint >/dev/null; then\n# swiftlint\n#else\n# echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\n#fi\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -710,54 +778,68 @@ buildActionMask = 2147483647; files = ( CEE9DA5D263C865200A31532 /* Brightness.swift in Sources */, + D2E6CB0B273AA00200E9AF1F /* ImageDataManager.swift in Sources */, 5C50CF362697315B0015EE29 /* ValidityCellModel.swift in Sources */, 5C50CF3A26973D9D0015EE29 /* UserDefaults+.swift in Sources */, - CE13CF0A262DCDDA0070C80E /* CertificateViewer.swift in Sources */, - CEDABD40263C5FF4007A9B97 /* CertTable.swift in Sources */, + CE13CF0A262DCDDA0070C80E /* CertificateViewerController.swift in Sources */, + CEDABD40263C5FF4007A9B97 /* CertificateController.swift in Sources */, CE37B643263867D700DEE13D /* SecureBackground.swift in Sources */, - CE13CF0F262DD0D80070C80E /* FullFloatingPanelLayout.swift in Sources */, - 5C00072A26D3A77D002E5013 /* ImageDataStorage.swift in Sources */, - 5CE07C7D2692E1CD00A032F9 /* ValueSetsDataStorage.swift in Sources */, - 5C00073226D3AEDE002E5013 /* PdfDataStorage.swift in Sources */, - CEA1556B262F784E0024B7AC /* SelfSizedTableView.swift in Sources */, - 5C00071926CFAED2002E5013 /* UIImage+.swift in Sources */, + 5C556D4926F9F8AC007E2C2E /* CertificateListController.swift in Sources */, + FEC54BE6271F27180048B6BC /* AccessTokenService.swift in Sources */, + 5C00071926CFAED2002E5013 /* UIImage+String.swift in Sources */, + FEBE53B62721A3EA003B61FB /* LimitationCell.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 */, + CEDABD49263C70EF007A9B97 /* CertPagesController.swift in Sources */, + CE4BD254264FF28900689FD6 /* SettingsTableController.swift in Sources */, + 5CE07C842692E21E00A032F9 /* CertLogicManager.swift in Sources */, CED2726026398683003D47A9 /* UIFont.swift in Sources */, - CEE9DA55263C7D4000A31532 /* CertCode.swift in Sources */, + FEC54BE927201D660048B6BC /* ValidationResultController.swift in Sources */, + CEE9DA55263C7D4000A31532 /* CertCodeController.swift in Sources */, CE56BC07264068110044FD3F /* GatewayConnection.swift in Sources */, - CEA6D6F0261F8D2700715333 /* Scan.swift in Sources */, + FEAEA96526FBB13600B94FFF /* AccessTokenResponse.swift in Sources */, CE13CF23262DDF810070C80E /* RoundedButton.swift in Sources */, - 5C50CF2726972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift in Sources */, + 5C556D5126F9F8C6007E2C2E /* ServerListController.swift in Sources */, + 5C50CF2726972BCA0015EE29 /* SimpleValidityCell.swift in Sources */, DA01662026554E02005B73A1 /* LicenseCell.swift in Sources */, CEA6D6EC261F8D2700715333 /* AppDelegate.swift in Sources */, - DA01661C265541C0005B73A1 /* LicenseList.swift in Sources */, + D2E6CB07273A94EA00E9AF1F /* LocalDataManager.swift in Sources */, + DA01661C265541C0005B73A1 /* LicenseTableController.swift in Sources */, 5C00073626D625D0002E5013 /* PDFTableViewCell.swift in Sources */, - CE891305263581D900CB92AF /* Home.swift in Sources */, + CE891305263581D900CB92AF /* HomeController.swift in Sources */, + D221E48B275E201D00D17049 /* CertificateRecord.swift in Sources */, + FEEAD6DB273BBE9600D883EB /* TokenInfoCell.swift in Sources */, + D2387C622746A7FB00C55DF7 /* KeyChain.swift in Sources */, 5C00071C26D38CC3002E5013 /* NFCHelper.swift in Sources */, - CEA15563262F6DAB0024B7AC /* CertViewerDelegate.swift in Sources */, + D2DFDB3E27C50E5900BA15B9 /* SquareViewFinder.swift in Sources */, CE260F56263DD1720083A200 /* WalletCell.swift in Sources */, + D23EF7A7272057F40097D82B /* ServerCell.swift in Sources */, CEA15570262F79DE0024B7AC /* InfoCell.swift in Sources */, - 5CE07C7A2692E1CD00A032F9 /* CountryDataStorage.swift in Sources */, - CED949CA263B50CE00883558 /* List.swift in Sources */, + FEC54BE4271F26990048B6BC /* IdentityService.swift in Sources */, + D24C8951272353610090398A /* ScanWalletController.swift in Sources */, + D2E6CB13273BE49300E9AF1F /* CertificateValidator.swift in Sources */, + CED949CA263B50CE00883558 /* MainListController.swift in Sources */, 5C50CF11269700710015EE29 /* RuleValidationResultVC.swift in Sources */, - DA01662226558B2F005B73A1 /* LicenseVC.swift in Sources */, + DA01662226558B2F005B73A1 /* LicenseController.swift in Sources */, + D221E48D275E207000D17049 /* TicketingShared.swift in Sources */, 5C00072E26D3AC11002E5013 /* SavedPDF.swift in Sources */, - 5C50CF1B2697023B0015EE29 /* RuleErrorTVC.swift in Sources */, - 5C50CF09269700550015EE29 /* CheckValidityVC.swift in Sources */, + 5C556D4426F9DAF7007E2C2E /* TicketingAcceptanceController.swift in Sources */, + 5C50CF1B2697023B0015EE29 /* RuleErrorCell.swift in Sources */, + 5C50CF09269700550015EE29 /* CheckValidityController.swift in Sources */, + D2387C642746EE5500C55DF7 /* DGCLogger.swift in Sources */, 5C50CF3E26983E090015EE29 /* UITableView+.swift in Sources */, CEA6D6EE261F8D2700715333 /* SceneDelegate.swift in Sources */, 5C00073A26D625DE002E5013 /* ImageTableViewCell.swift in Sources */, + D2DFDB2C27C4F99000BA15B9 /* DataStuctures.swift in Sources */, 5C50CF22269704240015EE29 /* UIViewController+.swift in Sources */, - 5CE07C802692E1CD00A032F9 /* RulesDataStorage.swift in Sources */, - 5C00074726D641E2002E5013 /* PDFViewerVC.swift in Sources */, - 5C00073F26D641CC002E5013 /* ImageViewerVC.swift in Sources */, + D2E6CB0F273AA34200E9AF1F /* PdfDataManager.swift in Sources */, + D23EF7A6272057F40097D82B /* CertificateCell.swift in Sources */, + 5C00074726D641E2002E5013 /* PDFViewerController.swift in Sources */, + 5C00073F26D641CC002E5013 /* ImageViewerController.swift in Sources */, 5C00072226D3A40E002E5013 /* SavedImage.swift in Sources */, - 5C00072626D3A6CD002E5013 /* String+.swift in Sources */, - 5C50CF2F26972BFD0015EE29 /* CellWithDateAndCountryTVC.swift in Sources */, + D2E6CAEF273A93F300E9AF1F /* DataCenter.swift in Sources */, + D2E6CB03273A948600E9AF1F /* SharedConstants.swift in Sources */, + 5C50CF2F26972BFD0015EE29 /* ExtendedValidityCell.swift in Sources */, + D2387C5E2745492600C55DF7 /* TicketingAcceptance.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -765,31 +847,36 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5C00074826D641E2002E5013 /* PDFViewerVC.swift in Sources */, - 5C50CF2826972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift in Sources */, + 5C50CF2826972BCA0015EE29 /* SimpleValidityCell.swift in Sources */, 5C00071D26D38CC3002E5013 /* NFCHelper.swift in Sources */, 5C00072F26D3AC11002E5013 /* SavedPDF.swift in Sources */, - 5C50CF0A269700550015EE29 /* CheckValidityVC.swift in Sources */, + 5C50CF0A269700550015EE29 /* CheckValidityController.swift in Sources */, + 5C556D4526F9DAF7007E2C2E /* TicketingAcceptanceController.swift in Sources */, 5C50CF3B26973D9D0015EE29 /* UserDefaults+.swift in Sources */, 5C50CF3F26983E090015EE29 /* UITableView+.swift in Sources */, + D2E6CB08273A94EA00E9AF1F /* LocalDataManager.swift in Sources */, 5C00073B26D625DE002E5013 /* ImageTableViewCell.swift in Sources */, - 5C00073326D3AEDE002E5013 /* PdfDataStorage.swift in Sources */, - 5C50CF1C2697023B0015EE29 /* RuleErrorTVC.swift in Sources */, - 5CE07C7E2692E1CD00A032F9 /* ValueSetsDataStorage.swift in Sources */, - 5C00072B26D3A77D002E5013 /* ImageDataStorage.swift in Sources */, + 5C50CF1C2697023B0015EE29 /* RuleErrorCell.swift in Sources */, + D2E6CB0C273AA00200E9AF1F /* ImageDataManager.swift in Sources */, + 5C556D5226F9F8C6007E2C2E /* ServerListController.swift in Sources */, 5C00072326D3A40E002E5013 /* SavedImage.swift in Sources */, + D2DFDB2D27C4F99000BA15B9 /* DataStuctures.swift in Sources */, 5C50CF12269700710015EE29 /* RuleValidationResultVC.swift in Sources */, CEA6D703261F8D2900715333 /* DGCAWalletTests.swift in Sources */, - 5C00072726D3A6CD002E5013 /* String+.swift in Sources */, + D2E6CB10273AA34200E9AF1F /* PdfDataManager.swift in Sources */, 5C00073726D625D0002E5013 /* PDFTableViewCell.swift in Sources */, 5C50CF23269704240015EE29 /* UIViewController+.swift in Sources */, - 5CE07C852692E21E00A032F9 /* CertLogicEngineManager.swift in Sources */, + D2E6CB14273BE49300E9AF1F /* CertificateValidator.swift in Sources */, + D2E6CAF0273A93F300E9AF1F /* DataCenter.swift in Sources */, + D2B605BD2743CA92000D914A /* ValidationResultController.swift in Sources */, + 5CE07C852692E21E00A032F9 /* CertLogicManager.swift in Sources */, 5C50CF372697315B0015EE29 /* ValidityCellModel.swift in Sources */, - 5C50CF3026972BFD0015EE29 /* CellWithDateAndCountryTVC.swift in Sources */, - 5C00074026D641CC002E5013 /* ImageViewerVC.swift in Sources */, - 5CE07C812692E1CD00A032F9 /* RulesDataStorage.swift in Sources */, + 5C50CF3026972BFD0015EE29 /* ExtendedValidityCell.swift in Sources */, + 5C556D4A26F9F8AC007E2C2E /* CertificateListController.swift in Sources */, + D2DFDB3F27C50E5900BA15B9 /* SquareViewFinder.swift in Sources */, + D2E6CB04273A948600E9AF1F /* SharedConstants.swift in Sources */, + D2387C5F274558C200C55DF7 /* TicketingAcceptance.swift in Sources */, CEFAD87F262714C4009AFEF9 /* EHNTests.swift in Sources */, - 5CE07C7B2692E1CD00A032F9 /* CountryDataStorage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -798,29 +885,34 @@ buildActionMask = 2147483647; files = ( 5C00073826D625D0002E5013 /* PDFTableViewCell.swift in Sources */, - 5CE07C7C2692E1CD00A032F9 /* CountryDataStorage.swift in Sources */, + D2E6CB05273A948600E9AF1F /* SharedConstants.swift in Sources */, 5C00072426D3A40E002E5013 /* SavedImage.swift in Sources */, 5C00073C26D625DE002E5013 /* ImageTableViewCell.swift in Sources */, 5C00073026D3AC11002E5013 /* SavedPDF.swift in Sources */, 5C50CF382697315B0015EE29 /* ValidityCellModel.swift in Sources */, - 5C00073426D3AEDE002E5013 /* PdfDataStorage.swift in Sources */, 5C50CF3C26973D9D0015EE29 /* UserDefaults+.swift in Sources */, + D2DFDB4027C50E5900BA15B9 /* SquareViewFinder.swift in Sources */, 5C50CF4026983E090015EE29 /* UITableView+.swift in Sources */, - 5C00074926D641E2002E5013 /* PDFViewerVC.swift in Sources */, + D2E6CB0D273AA00200E9AF1F /* ImageDataManager.swift in Sources */, + 5C556D4626F9DAF7007E2C2E /* TicketingAcceptanceController.swift in Sources */, + D2E6CAF1273A93F300E9AF1F /* DataCenter.swift in Sources */, + 5C556D4B26F9F8AC007E2C2E /* CertificateListController.swift in Sources */, + D2E6CB09273A94EA00E9AF1F /* LocalDataManager.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 */, + 5C50CF3126972BFD0015EE29 /* ExtendedValidityCell.swift in Sources */, + 5C50CF0B269700550015EE29 /* CheckValidityController.swift in Sources */, + D2B605BE2743CA92000D914A /* ValidationResultController.swift in Sources */, + 5C556D5326F9F8C6007E2C2E /* ServerListController.swift in Sources */, + 5C50CF1D2697023B0015EE29 /* RuleErrorCell.swift in Sources */, + 5CE07C862692E21E00A032F9 /* CertLogicManager.swift in Sources */, 5C00071E26D38CC3002E5013 /* NFCHelper.swift in Sources */, 5C50CF13269700710015EE29 /* RuleValidationResultVC.swift in Sources */, 5C50CF24269704240015EE29 /* UIViewController+.swift in Sources */, - 5C00072826D3A6CD002E5013 /* String+.swift in Sources */, - 5CE07C822692E1CD00A032F9 /* RulesDataStorage.swift in Sources */, - 5C00074126D641CC002E5013 /* ImageViewerVC.swift in Sources */, - 5C50CF2926972BCA0015EE29 /* CellWithTitleAndDescriptionTVC.swift in Sources */, - 5C00072C26D3A77D002E5013 /* ImageDataStorage.swift in Sources */, + D2DFDB2E27C4F99000BA15B9 /* DataStuctures.swift in Sources */, + D2E6CB11273AA34200E9AF1F /* PdfDataManager.swift in Sources */, + D2E6CB15273BE49300E9AF1F /* CertificateValidator.swift in Sources */, + 5C50CF2926972BCA0015EE29 /* SimpleValidityCell.swift in Sources */, + D2387C60274558C300C55DF7 /* TicketingAcceptance.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -860,6 +952,7 @@ isa = PBXVariantGroup; children = ( CE6D4A45264835F100A5D33D /* en */, + D2387C67274853BC00C55DF7 /* de */, ); name = Localizable.strings; sourceTree = ""; @@ -876,10 +969,21 @@ isa = PBXVariantGroup; children = ( CEA6D6F7261F8D2900715333 /* Base */, + D2387C70274BD84E00C55DF7 /* en */, + D2387C72274BD85200C55DF7 /* de */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; + D2387C6D274BD5EA00C55DF7 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + D2387C6C274BD5EA00C55DF7 /* en */, + D2387C6E274BD5EE00C55DF7 /* de */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -887,6 +991,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -948,6 +1053,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -1007,15 +1113,15 @@ CODE_SIGN_ENTITLEMENTS = DGCAWallet/DGCAWallet.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 9; DEVELOPMENT_TEAM = 9FH6TNGWF2; INFOPLIST_FILE = DGCAWallet/SupportingFiles/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; + IPHONEOS_DEPLOYMENT_TARGET = 13.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.2.3.1; PRODUCT_BUNDLE_IDENTIFIER = "com.t-systems.pu-ds.dgca.wallet.dev"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Digital Green Certificate Wallet"; @@ -1032,15 +1138,15 @@ CODE_SIGN_ENTITLEMENTS = DGCAWallet/DGCAWallet.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 9; DEVELOPMENT_TEAM = 9FH6TNGWF2; INFOPLIST_FILE = DGCAWallet/SupportingFiles/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; + IPHONEOS_DEPLOYMENT_TARGET = 13.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.2.3.1; PRODUCT_BUNDLE_IDENTIFIER = "com.t-systems.pu-ds.dgca.wallet.dev"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Digital Green Certificate Wallet"; @@ -1179,18 +1285,10 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/eu-digital-green-certificates/dgc-certlogic-ios.git"; requirement = { - branch = main; + branch = "certLogic-main-updated"; kind = branch; }; }; - CE13CEFE262DCC180070C80E /* XCRemoteSwiftPackageReference "FloatingPanel" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SCENEE/FloatingPanel"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.3.0; - }; - }; CE1F155A2639F9E700736D48 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON"; @@ -1203,7 +1301,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/eu-digital-green-certificates/dgca-app-core-ios"; requirement = { - branch = main; + branch = "fix-qrcode-crash"; kind = branch; }; }; @@ -1215,6 +1313,38 @@ minimumVersion = 5.4.3; }; }; + D2DFDB2227C4F49A00BA15B9 /* XCRemoteSwiftPackageReference "dgc-bloomfilter-ios" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/eu-digital-green-certificates/dgc-bloomfilter-ios.git"; + requirement = { + branch = main; + kind = branch; + }; + }; + D2DFDB3A27C5025D00BA15B9 /* XCRemoteSwiftPackageReference "SWCompression" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/tsolomko/SWCompression.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.0.0; + }; + }; + FE1D1A6927148CBE00765A9A /* XCRemoteSwiftPackageReference "CryptoSwift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/krzyzanowskim/CryptoSwift"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; + FE6E78862702033200C142A3 /* XCRemoteSwiftPackageReference "JWTDecode.swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/auth0/JWTDecode.swift.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.0.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1223,20 +1353,11 @@ package = 5CE07C742692E0D900A032F9 /* XCRemoteSwiftPackageReference "dgc-certlogic-ios" */; productName = CertLogic; }; - CE13CEFF262DCC180070C80E /* FloatingPanel */ = { - isa = XCSwiftPackageProductDependency; - package = CE13CEFE262DCC180070C80E /* XCRemoteSwiftPackageReference "FloatingPanel" */; - productName = FloatingPanel; - }; CE1F155B2639F9E700736D48 /* SwiftyJSON */ = { isa = XCSwiftPackageProductDependency; package = CE1F155A2639F9E700736D48 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; productName = SwiftyJSON; }; - CE56B5042639F48E00FB31B1 /* SwiftDGC */ = { - isa = XCSwiftPackageProductDependency; - productName = SwiftDGC; - }; CE6B330D2651A54800845B8E /* SwiftDGC */ = { isa = XCSwiftPackageProductDependency; package = CE6B330C2651A54800845B8E /* XCRemoteSwiftPackageReference "dgca-app-core-ios" */; @@ -1247,6 +1368,26 @@ package = CE8912F92634C6B900CB92AF /* XCRemoteSwiftPackageReference "Alamofire" */; productName = Alamofire; }; + D2DFDB2327C4F49A00BA15B9 /* DGCBloomFilter */ = { + isa = XCSwiftPackageProductDependency; + package = D2DFDB2227C4F49A00BA15B9 /* XCRemoteSwiftPackageReference "dgc-bloomfilter-ios" */; + productName = DGCBloomFilter; + }; + D2DFDB3B27C5025D00BA15B9 /* SWCompression */ = { + isa = XCSwiftPackageProductDependency; + package = D2DFDB3A27C5025D00BA15B9 /* XCRemoteSwiftPackageReference "SWCompression" */; + productName = SWCompression; + }; + FE1D1A6A27148CBE00765A9A /* CryptoSwift */ = { + isa = XCSwiftPackageProductDependency; + package = FE1D1A6927148CBE00765A9A /* XCRemoteSwiftPackageReference "CryptoSwift" */; + productName = CryptoSwift; + }; + FE6E78872702033300C142A3 /* JWTDecode */ = { + isa = XCSwiftPackageProductDependency; + package = FE6E78862702033200C142A3 /* XCRemoteSwiftPackageReference "JWTDecode.swift" */; + productName = JWTDecode; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = CEA6D6E0261F8D2700715333 /* Project object */; diff --git a/DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index bf4ef6e..0000000 --- a/DGCAWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,106 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "Alamofire", - "repositoryURL": "https://github.com/Alamofire/Alamofire", - "state": { - "branch": null, - "revision": "f96b619bcb2383b43d898402283924b80e2c4bae", - "version": "5.4.3" - } - }, - { - "package": "ASN1Decoder", - "repositoryURL": "https://github.com/filom/ASN1Decoder", - "state": { - "branch": null, - "revision": "65953a42a0f039f53c73e48fd88c02809f7db607", - "version": "1.8.0" - } - }, - { - "package": "CertLogic", - "repositoryURL": "https://github.com/eu-digital-green-certificates/dgc-certlogic-ios.git", - "state": { - "branch": "main", - "revision": "80c9ff3697b3e3e0e875e5e0a41b2dbbfa4a9ca6", - "version": null - } - }, - { - "package": "SwiftDGC", - "repositoryURL": "https://github.com/eu-digital-green-certificates/dgca-app-core-ios", - "state": { - "branch": "main", - "revision": "ea7f671dd19260ba93681114ff6518eae9725129", - "version": null - } - }, - { - "package": "FloatingPanel", - "repositoryURL": "https://github.com/SCENEE/FloatingPanel", - "state": { - "branch": null, - "revision": "b8fcb50874f50bb511173700f87b09344c568c6a", - "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", - "state": { - "branch": "master", - "revision": "4809e8a105b3fd2818a08caf9e3cbcdd7d319af2", - "version": null - } - }, - { - "package": "PathKit", - "repositoryURL": "https://github.com/kylef/PathKit.git", - "state": { - "branch": null, - "revision": "73f8e9dca9b7a3078cb79128217dc8f2e585a511", - "version": "1.0.0" - } - }, - { - "package": "Spectre", - "repositoryURL": "https://github.com/kylef/Spectre.git", - "state": { - "branch": null, - "revision": "d02129a9af77729de049d328dd61e530b6f2bb2b", - "version": null - } - }, - { - "package": "SwiftCBOR", - "repositoryURL": "https://github.com/eu-digital-green-certificates/SwiftCBOR", - "state": { - "branch": "master", - "revision": "b76024995f6909e4615f943c7a03268fd29778fc", - "version": null - } - }, - { - "package": "SwiftyJSON", - "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON", - "state": { - "branch": null, - "revision": "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07", - "version": "5.0.1" - } - } - ] - }, - "version": 1 -} diff --git a/DGCAWallet/Components/Cells/CertificateTVC.swift b/DGCAWallet/Components/Cells/CertificateTVC.swift new file mode 100644 index 0000000..93f4ed5 --- /dev/null +++ b/DGCAWallet/Components/Cells/CertificateTVC.swift @@ -0,0 +1,61 @@ +// +/*- + * ---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 + */ +// +// CertificateCell.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 21.09.2021. +// +// Updated by Igor Khomiak on 11.10.2021. + + +import UIKit +import SwiftDGC + +class CertificateCell: UITableViewCell { + + @IBOutlet weak var nameLabel: UILabel! + @IBOutlet weak var descriptionLabel: UILabel! + + private var hCert: HCert? { + didSet { + setupView() + } + } + + private func setupView() { + if let hCert = hCert { + nameLabel.text = hCert.fullName + descriptionLabel.text = hCert.exp.dateString + } else { + nameLabel.text = "" + descriptionLabel.text = "" + } + } + + public func setCertificate(cert: HCert) { + hCert = cert + } + + override func prepareForReuse() { + hCert = nil + } +} diff --git a/DGCAWallet/Components/Cells/ServerTVC.swift b/DGCAWallet/Components/Cells/ServerTVC.swift new file mode 100644 index 0000000..05bbeec --- /dev/null +++ b/DGCAWallet/Components/Cells/ServerTVC.swift @@ -0,0 +1,62 @@ +// +/*- + * ---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 + */ +// +// ServerCell.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 21.09.2021. +// +// Updated by Igor Khomiak on 11.10.2021. + + +import UIKit +import SwiftDGC + +class ServerCell: UITableViewCell { + + @IBOutlet weak var nameLabel: UILabel! + @IBOutlet weak var descriptionLabel: UILabel! + + private var service: ValidationService? { + didSet { + setupView() + } + } + + private func setupView() { + if let service = service { + nameLabel.text = service.name + descriptionLabel.text = service.serviceEndpoint + } else { + nameLabel.text = "" + descriptionLabel.text = "" + } + } + + public func setService(serv: ValidationService) { + service = serv + } + + override func prepareForReuse() { + service = nil + } + +} diff --git a/DGCAWallet/Components/CertificateCell.swift b/DGCAWallet/Components/CertificateCell.swift new file mode 100644 index 0000000..49252b1 --- /dev/null +++ b/DGCAWallet/Components/CertificateCell.swift @@ -0,0 +1,63 @@ +// +/*- + * ---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 + */ +// +// CertificateCell.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 21.09.2021. +// +// Updated by Igor Khomiak on 11.10.2021. + + +import UIKit +import SwiftDGC + +class CertificateCell: UITableViewCell { + + @IBOutlet fileprivate weak var nameLabel: UILabel! + @IBOutlet fileprivate weak var certTypeLabel: UILabel! + @IBOutlet fileprivate weak var descriptionLabel: UILabel! + + private var hCert: HCert? { + didSet { + setupView() + } + } + + private func setupView() { + if let hCert = hCert { + nameLabel.text = hCert.fullName + descriptionLabel.text = hCert.exp.dateString + certTypeLabel.text = hCert.certificateType.rawValue + } else { + nameLabel.text = "" + descriptionLabel.text = "" + } + } + + func setCertificate(cert: HCert) { + hCert = cert + } + + override func prepareForReuse() { + hCert = nil + } +} diff --git a/DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.xib b/DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.xib deleted file mode 100644 index fcae5a3..0000000 --- a/DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.xib +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DGCAWallet/Components/CheckValidityCells/CellWithTitleAndDescriptionTVC.xib b/DGCAWallet/Components/CheckValidityCells/CellWithTitleAndDescriptionTVC.xib deleted file mode 100644 index 3d95d6f..0000000 --- a/DGCAWallet/Components/CheckValidityCells/CellWithTitleAndDescriptionTVC.xib +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.swift b/DGCAWallet/Components/CheckValidityCells/ExtendedValidityCell.swift similarity index 67% rename from DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.swift rename to DGCAWallet/Components/CheckValidityCells/ExtendedValidityCell.swift index a7cbbaf..55a9acf 100644 --- a/DGCAWallet/Components/CheckValidityCells/CellWithDateAndCountryTVC.swift +++ b/DGCAWallet/Components/CheckValidityCells/ExtendedValidityCell.swift @@ -19,7 +19,7 @@ * ---license-end */ // -// CellWithDateAndCountryTVC.swift +// ExtendedValidityCell.swift // DGCAWallet // // Created by Alexandr Chernyy on 08.07.2021. @@ -32,40 +32,43 @@ 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! +class ExtendedValidityCell: UITableViewCell { + private enum Constants { + static let userDefaultsCountryKey = "UDWalletCountryKey" + } + + @IBOutlet fileprivate weak var destinationLabel: UILabel! + @IBOutlet fileprivate weak var countryPicker: UIPickerView! + @IBOutlet fileprivate weak var dateLabel: UILabel! + @IBOutlet fileprivate 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) + try UserDefaults.standard.setObject(newValue, forKey: Constants.userDefaultsCountryKey) } catch { - print(error.localizedDescription) + DGCLogger.logError(error) } } get { - let userDefaults = UserDefaults.standard do { - let selected = try userDefaults.getObject(forKey: Constants.userDefaultsCountryKey, castTo: CountryModel.self) + let selected = try UserDefaults.standard.getObject(forKey: Constants.userDefaultsCountryKey, castTo: CountryModel.self) return selected } catch { - print(error.localizedDescription) + DGCLogger.logError(error) return nil } } } + func setupView() { - destinationLabel.text = l10n("destination_country") - dateLabel.text = l10n("destination_date") - countryPicker.delegate = self - countryPicker.dataSource = self + destinationLabel.text = "Your destination country".localized + dateLabel.text = "Check the date".localized datePicker.minimumDate = Date() if #available(iOS 13.4, *) { datePicker.preferredDatePickerStyle = .wheels @@ -73,43 +76,43 @@ class CellWithDateAndCountryTVC: UITableViewCell { // Fallback on earlier versions } datePicker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged) - GatewayConnection.countryList { countryList in - DispatchQueue.main.async { - self.setListOfRuleCounties(list: countryList) - } - } + setListOfRuleCounties(list: DataCenter.countryCodes) } } -extension CellWithDateAndCountryTVC { +extension ExtendedValidityCell { @objc func dateChanged(_ sender: UIDatePicker) { dataHandler?(sender.date) } } -extension CellWithDateAndCountryTVC: UIPickerViewDataSource, UIPickerViewDelegate { +extension ExtendedValidityCell: 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") } + if countryItems.count == 0 { return "Country codes list empty".localized } 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 { +extension ExtendedValidityCell { 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) @@ -119,14 +122,5 @@ extension CellWithDateAndCountryTVC { 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/CellWithTitleAndDescriptionTVC.swift b/DGCAWallet/Components/CheckValidityCells/SimpleValidityCell.swift similarity index 83% rename from DGCAWallet/Components/CheckValidityCells/CellWithTitleAndDescriptionTVC.swift rename to DGCAWallet/Components/CheckValidityCells/SimpleValidityCell.swift index a2ec47d..99a576f 100644 --- a/DGCAWallet/Components/CheckValidityCells/CellWithTitleAndDescriptionTVC.swift +++ b/DGCAWallet/Components/CheckValidityCells/SimpleValidityCell.swift @@ -19,7 +19,7 @@ * ---license-end */ // -// CellWithTitleAndDescriptionTVC.swift +// SimpleValidityCell.swift // DGCAWallet // // Created by Alexandr Chernyy on 08.07.2021. @@ -28,9 +28,9 @@ import UIKit -class CellWithTitleAndDescriptionTVC: UITableViewCell { - @IBOutlet weak var titleLabel: UILabel! - @IBOutlet weak var descriptionLabel: UILabel! +class SimpleValidityCell: UITableViewCell { + @IBOutlet fileprivate weak var titleLabel: UILabel! + @IBOutlet fileprivate weak var descriptionLabel: UILabel! private weak var cellModel: ValidityCellModel? { didSet { @@ -38,13 +38,7 @@ class CellWithTitleAndDescriptionTVC: UITableViewCell { } } - override func awakeFromNib() { - super.awakeFromNib() - setInitialStrings() - } - override func prepareForReuse() { - super.prepareForReuse() setInitialStrings() } @@ -70,5 +64,4 @@ class CellWithTitleAndDescriptionTVC: UITableViewCell { titleLabel.font = UIFont.boldSystemFont(ofSize: 14) } } - } diff --git a/DGCAWallet/Components/ImageTableViewCell.swift b/DGCAWallet/Components/ImageTableViewCell.swift index 39625c3..ca17b08 100644 --- a/DGCAWallet/Components/ImageTableViewCell.swift +++ b/DGCAWallet/Components/ImageTableViewCell.swift @@ -28,25 +28,18 @@ import UIKit -final class ImageTableViewCell: UITableViewCell { - - @IBOutlet weak var imagePreviewView: UIImageView! - @IBOutlet weak var nameLabel: UILabel! - @IBOutlet weak var timeLabel: UILabel! +class ImageTableViewCell: UITableViewCell { + @IBOutlet fileprivate weak var imagePreviewView: UIImageView! + @IBOutlet fileprivate weak var nameLabel: UILabel! + @IBOutlet fileprivate weak var timeLabel: UILabel! private var savedImage: SavedImage? { didSet { setupView() } } - - override func awakeFromNib() { - super.awakeFromNib() - // Initialization code - setupView() - } - - public func setImage(image: SavedImage) { + + func setImage(image: SavedImage) { savedImage = image } @@ -60,5 +53,8 @@ final class ImageTableViewCell: UITableViewCell { nameLabel.text = savedImage.fileName timeLabel.text = savedImage.dateString } - + + override func prepareForReuse() { + savedImage = nil + } } diff --git a/DGCAWallet/Components/InfoCell.swift b/DGCAWallet/Components/InfoCell.swift index 69c901f..8a824ad 100644 --- a/DGCAWallet/Components/InfoCell.swift +++ b/DGCAWallet/Components/InfoCell.swift @@ -28,10 +28,10 @@ import UIKit import SwiftDGC class InfoCell: UITableViewCell { - @IBOutlet weak var headerLabel: UILabel! - @IBOutlet weak var contentLabel: UILabel! + @IBOutlet fileprivate weak var headerLabel: UILabel! + @IBOutlet fileprivate weak var contentLabel: UILabel! - func draw(_ info: InfoSection) { + func setupCell(_ info: InfoSection) { headerLabel?.text = info.header contentLabel?.text = info.content let fontSize = contentLabel.font.pointSize diff --git a/DGCAWallet/Components/LicenseCell.swift b/DGCAWallet/Components/LicenseCell.swift index e0f7ecc..eab593d 100644 --- a/DGCAWallet/Components/LicenseCell.swift +++ b/DGCAWallet/Components/LicenseCell.swift @@ -26,11 +26,10 @@ // import UIKit -import SwiftDGC import SwiftyJSON class LicenseCell: UITableViewCell { - @IBOutlet weak var cellLabel: UILabel! + @IBOutlet fileprivate weak var cellLabel: UILabel! public var licenseObject: JSON = [] diff --git a/DGCAWallet/ViewControllers/Scan.swift b/DGCAWallet/Components/LimitationCell.swift similarity index 68% rename from DGCAWallet/ViewControllers/Scan.swift rename to DGCAWallet/Components/LimitationCell.swift index dc437e5..04943e5 100644 --- a/DGCAWallet/ViewControllers/Scan.swift +++ b/DGCAWallet/Components/LimitationCell.swift @@ -1,3 +1,4 @@ +// /*- * ---license-start * eu-digital-green-certificates / dgca-wallet-app-ios @@ -17,24 +18,20 @@ * limitations under the License. * ---license-end */ -// -// ViewController.swift +// +// LimitationCell.swift // DGCAWallet -// -// Created by Yannick Spreen on 4/8/21. -// -// https://www.raywenderlich.com/12663654-vision-framework-tutorial-for-ios-scanning-barcodes -// +// +// Created by Illia Vlasov on 21.10.2021. +// + import UIKit -import SwiftDGC -import FloatingPanel - -class ScanVC: SwiftDGC.ScanVC { - override func viewDidLoad() { - super.viewDidLoad() - applicationType = .wallet - createBackButton() - } +class LimitationCell: UITableViewCell { + @IBOutlet weak var issueTextView: UITextView! { + didSet { + issueTextView.text = "" + } + } } diff --git a/DGCAWallet/Components/PDFTableViewCell.swift b/DGCAWallet/Components/PDFTableViewCell.swift index 302fae4..9200711 100644 --- a/DGCAWallet/Components/PDFTableViewCell.swift +++ b/DGCAWallet/Components/PDFTableViewCell.swift @@ -30,25 +30,18 @@ import UIKit import PDFKit class PDFTableViewCell: UITableViewCell { - - @IBOutlet weak var pdfView: UIView! - @IBOutlet weak var nameLabel: UILabel! - @IBOutlet weak var timeLabel: UILabel! + @IBOutlet fileprivate weak var pdfView: UIView! + @IBOutlet fileprivate weak var nameLabel: UILabel! + @IBOutlet fileprivate weak var timeLabel: UILabel! private var savedPDF: SavedPDF? { didSet { setupView() } } - private var pdfViewer: PDFView? - - override func awakeFromNib() { - super.awakeFromNib() - // Initialization code - setupView() - } - public func setPDF(pdf: SavedPDF) { + private var pdfViewer: PDFView? + func setPDF(pdf: SavedPDF) { savedPDF = pdf } @@ -72,4 +65,8 @@ class PDFTableViewCell: UITableViewCell { nameLabel.text = savedPDF.fileName timeLabel.text = savedPDF.dateString } + + override func prepareForReuse() { + savedPDF = nil + } } diff --git a/DGCAWallet/Components/RoundedButton.swift b/DGCAWallet/Components/RoundedButton.swift index 920d2f6..cc7f174 100644 --- a/DGCAWallet/Components/RoundedButton.swift +++ b/DGCAWallet/Components/RoundedButton.swift @@ -24,7 +24,6 @@ // Created by Yannick Spreen on 4/19/21. // -import Foundation import UIKit @IBDesignable diff --git a/DGCAWallet/Components/RuleCell/RuleErrorTVC.xib b/DGCAWallet/Components/RuleCell/RuleErrorTVC.xib deleted file mode 100644 index 55704dd..0000000 --- a/DGCAWallet/Components/RuleCell/RuleErrorTVC.xib +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DGCAWallet/Components/RuleCell/RuleErrorTVC.swift b/DGCAWallet/Components/RuleErrorCell.swift similarity index 53% rename from DGCAWallet/Components/RuleCell/RuleErrorTVC.swift rename to DGCAWallet/Components/RuleErrorCell.swift index 8f4a69c..18eb3e3 100644 --- a/DGCAWallet/Components/RuleCell/RuleErrorTVC.swift +++ b/DGCAWallet/Components/RuleErrorCell.swift @@ -19,7 +19,7 @@ * ---license-end */ // -// RuleErrorTVC.swift +// RuleErrorCell.swift // DGCAWallet // // Created by Alexandr Chernyy on 05.07.2021. @@ -28,73 +28,72 @@ 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! +class RuleErrorCell: UITableViewCell { + @IBOutlet fileprivate weak var ruleLabel: UILabel! + @IBOutlet fileprivate weak var ruleValueLabel: UILabel! + @IBOutlet fileprivate weak var currentLabel: UILabel! + @IBOutlet fileprivate weak var currentValueLabel: UILabel! + @IBOutlet fileprivate weak var resultLabel: UILabel! + @IBOutlet fileprivate weak var resultValueLabel: UILabel! + @IBOutlet fileprivate 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") + ruleLabel.text = "Rule".localized ruleValueLabel.text = "" - currentLabel.text = l10n("current") + currentLabel.text = "Current".localized currentValueLabel.text = "" - resultLabel.text = l10n("result") + resultLabel.text = "Result".localized 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 .failed: + failedLabel.textColor = .walletRed + failedLabel.text = "Failed".localized case .passed: - failedLabel.textColor = .green - failedLabel.text = l10n("passed") + failedLabel.textColor = .walletGreen + failedLabel.text = "Passed".localized case .open: - failedLabel.textColor = .green - failedLabel.text = l10n("open") + failedLabel.textColor = .walletGreen + failedLabel.text = "Open".localized } if let countryName = infoItem.countryName { switch infoItem.ruleValidationResult { - case .error: - resultValueLabel.text = String(format: l10n("failed_for_country"), countryName) + case .failed: + resultValueLabel.text = String(format: "Failed for %@ (see settings)".localized, countryName) case .passed: - resultValueLabel.text = String(format: l10n("passed_for_country"), countryName) + resultValueLabel.text = String(format: "Passed for %@ (see settings)".localized, countryName) case .open: - resultValueLabel.text = String(format: l10n("open_for_country"), countryName) + resultValueLabel.text = String(format: "Open for %@ (see settings)".localized, countryName) } } else { switch infoItem.ruleValidationResult { - case .error: - resultValueLabel.text = l10n("failed") + case .failed: + resultValueLabel.text = "Failed".localized case .passed: - resultValueLabel.text = l10n("passed") + resultValueLabel.text = "Passed".localized case .open: - resultValueLabel.text = l10n("open") + resultValueLabel.text = "Open".localized } } } - public func setupCell(with info: InfoSection) { + + func setupCell(with info: InfoSection) { self.infoItem = info } } diff --git a/DGCAWallet/Components/ServerCell.swift b/DGCAWallet/Components/ServerCell.swift new file mode 100644 index 0000000..e5c6667 --- /dev/null +++ b/DGCAWallet/Components/ServerCell.swift @@ -0,0 +1,61 @@ +// +/*- + * ---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 + */ +// +// ServerCell.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 21.09.2021. +// +// Updated by Igor Khomiak on 11.10.2021. + + +import UIKit +import SwiftDGC + +class ServerCell: UITableViewCell { + + @IBOutlet fileprivate weak var nameLabel: UILabel! + @IBOutlet fileprivate weak var descriptionLabel: UILabel! + + private var service: ValidationService? { + didSet { + setupView() + } + } + + private func setupView() { + if let service = service { + nameLabel.text = service.name + descriptionLabel.text = service.serviceEndpoint + } else { + nameLabel.text = "" + descriptionLabel.text = "" + } + } + + func setService(serv: ValidationService) { + service = serv + } + + override func prepareForReuse() { + service = nil + } +} diff --git a/DGCAWallet/Components/SquareViewFinder.swift b/DGCAWallet/Components/SquareViewFinder.swift new file mode 100644 index 0000000..324b45a --- /dev/null +++ b/DGCAWallet/Components/SquareViewFinder.swift @@ -0,0 +1,77 @@ +/*- + * ---license-start + * eu-digital-green-certificates / dgca-app-core-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 + */ +// +// SquareViewFinder.swift +// +// +// Created by Yannick Spreen on 4/29/21. +// + +import UIKit + +class SquareViewFinder { + public static func newView(from view: UIView? = nil) -> UIView { + let view = view ?? UIView(frame: .zero) + view.frame = .zero + view.translatesAutoresizingMaskIntoConstraints = false + view.backgroundColor = .clear + view.isUserInteractionEnabled = false + return view + } + +static func create(from controller: UIViewController) { + guard let view = controller.view else { return } + + let guide = newView() + let square = newView() + let imgTopRight = newView(from: UIImageView(image: UIImage(named: "cam_top_right"))) + let imgTopLeft = newView(from: UIImageView(image: UIImage(named: "cam_top_left"))) + let imgBottomRight = newView(from: UIImageView(image: UIImage(named: "cam_bottom_right"))) + let imgBottomLeft = newView(from: UIImageView(image: UIImage(named: "cam_bottom_left"))) + let constraints = [ + guide.leadingAnchor.constraint(equalTo: view.leadingAnchor), + guide.trailingAnchor.constraint(equalTo: view.trailingAnchor), + guide.topAnchor.constraint(equalTo: view.topAnchor), + guide.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.53), + square.bottomAnchor.constraint(equalTo: guide.bottomAnchor), + square.centerXAnchor.constraint(equalTo: guide.centerXAnchor), + square.widthAnchor.constraint(equalToConstant: 250), + square.heightAnchor.constraint(equalToConstant: 250), + imgTopRight.topAnchor.constraint(equalTo: square.topAnchor), + imgTopRight.rightAnchor.constraint(equalTo: square.rightAnchor), + imgBottomRight.bottomAnchor.constraint(equalTo: square.bottomAnchor), + imgBottomRight.rightAnchor.constraint(equalTo: square.rightAnchor), + imgBottomLeft.bottomAnchor.constraint(equalTo: square.bottomAnchor), + imgBottomLeft.leftAnchor.constraint(equalTo: square.leftAnchor), + imgTopLeft.topAnchor.constraint(equalTo: square.topAnchor), + imgTopLeft.leftAnchor.constraint(equalTo: square.leftAnchor) + ] + for child in [ + guide, + square, + imgTopRight, + imgTopLeft, + imgBottomRight, + imgBottomLeft] { + view.addSubview(child) + } + NSLayoutConstraint.activate(constraints) + } +} diff --git a/DGCAWallet/Components/FullFloatingPanelLayout.swift b/DGCAWallet/Components/TokenInfoCell.swift similarity index 62% rename from DGCAWallet/Components/FullFloatingPanelLayout.swift rename to DGCAWallet/Components/TokenInfoCell.swift index 235b3a9..4f95073 100644 --- a/DGCAWallet/Components/FullFloatingPanelLayout.swift +++ b/DGCAWallet/Components/TokenInfoCell.swift @@ -1,3 +1,4 @@ +// /*- * ---license-start * eu-digital-green-certificates / dgca-wallet-app-ios @@ -17,24 +18,25 @@ * limitations under the License. * ---license-end */ -// -// FullFloatingPanelLayout.swift +// +// TokenInfoCell.swift // DGCAWallet -// -// Created by Yannick Spreen on 4/19/21. -// - -import FloatingPanel - -class FullFloatingPanelLayout: FloatingPanelLayout { - var position: FloatingPanelPosition = .bottom +// +// Created by Illia Vlasov on 10.11.2021. +// + - var initialState: FloatingPanelState = .full +import UIKit - var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] { - let top = FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea) - return [ - .full: top - ] - } +class TokenInfoCell: UITableViewCell { + + @IBOutlet weak var fieldName: UILabel! + @IBOutlet weak var fieldValue: UILabel! + + var certificateRecord: CertificateRecord? { + didSet { + fieldName.text = certificateRecord?.keyName + fieldValue.text = certificateRecord?.value + } + } } diff --git a/DGCAWallet/Components/WalletCell.swift b/DGCAWallet/Components/WalletCell.swift index 3721df0..4ac9f65 100644 --- a/DGCAWallet/Components/WalletCell.swift +++ b/DGCAWallet/Components/WalletCell.swift @@ -28,17 +28,15 @@ import UIKit import SwiftDGC class WalletCell: UITableViewCell { - @IBOutlet weak var typeLabel: UILabel! - @IBOutlet weak var nameLabel: UILabel! - @IBOutlet weak var dateLabel: UILabel! + @IBOutlet fileprivate weak var typeLabel: UILabel! + @IBOutlet fileprivate weak var nameLabel: UILabel! + @IBOutlet fileprivate weak var dateLabel: UILabel! - func draw(_ dated: DatedCertString) { - guard let cert = dated.cert else { - return - } + func setupCell(_ dated: DatedCertString) { + guard let cert = dated.cert else { return } typeLabel.text = cert.certTypeString nameLabel.text = cert.fullName - dateLabel.text = String(format: l10n("list.cell.scanned-at"), dated.date.localDateString) + dateLabel.text = String(format: "Scanned %@".localized, dated.date.localDateString) } } diff --git a/DGCAWallet/Extensions/UIColor.swift b/DGCAWallet/Extensions/UIColor.swift index 177dbce..d39170d 100644 --- a/DGCAWallet/Extensions/UIColor.swift +++ b/DGCAWallet/Extensions/UIColor.swift @@ -28,10 +28,14 @@ import UIKit extension UIColor { - static var red: UIColor! { UIColor(named: "red") } - static var green: UIColor! { UIColor(named: "green") } - static var blue: UIColor! { UIColor(named: "blue") } - static var black: UIColor! { UIColor(named: "black") } + static var walletRed: UIColor! { UIColor(named: "walletRed") } + static var walletGreen: UIColor! { UIColor(named: "walletGreen") } + static var walletBlue: UIColor! { UIColor(named: "walletBlue") } + static var walletBlack: UIColor! { UIColor(named: "walletBlack") } static var disabledText: UIColor! { UIColor(named: "disabledText") } - static var grey10: UIColor! { UIColor(named: "grey10") } + static var walletGray10: UIColor! { UIColor(named: "walletGray10") } + static var walletYellow: UIColor! { UIColor(named: "walletYellow") } + static var walletLightYellow: UIColor! { UIColor(named: "walletLightYellow") } + static var walletLightGreen: UIColor! { UIColor(named: "walletLightGreen") } + static var walletLightBlue: UIColor! { UIColor(named: "walletLightBlue") } } diff --git a/DGCAWallet/Extensions/UIImage+.swift b/DGCAWallet/Extensions/UIImage+String.swift similarity index 75% rename from DGCAWallet/Extensions/UIImage+.swift rename to DGCAWallet/Extensions/UIImage+String.swift index 081a84f..328285b 100644 --- a/DGCAWallet/Extensions/UIImage+.swift +++ b/DGCAWallet/Extensions/UIImage+String.swift @@ -32,10 +32,9 @@ extension UIImage { func qrCodeString() -> String? { var qrAsString = "" guard let detector = CIDetector(ofType: CIDetectorTypeQRCode, - context: nil, - options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]), - let ciImage = CIImage(image: self), - let features = detector.features(in: ciImage) as? [CIQRCodeFeature] else { + context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]), + let ciImage = CIImage(image: self), + let features = detector.features(in: ciImage) as? [CIQRCodeFeature] else { return qrAsString } for feature in features { @@ -51,3 +50,11 @@ extension UIImage { return self.jpegData(compressionQuality: 1)?.base64EncodedString() ?? "" } } + +extension String { + func convertBase64StringToImage () -> UIImage? { + guard let imageData = Data.init(base64Encoded: self, options: .init(rawValue: 0)) else { return nil } + let image = UIImage(data: imageData) + return image + } +} diff --git a/DGCAWallet/Extensions/UITableView+.swift b/DGCAWallet/Extensions/UITableView+.swift index 2a120e1..a5c6587 100644 --- a/DGCAWallet/Extensions/UITableView+.swift +++ b/DGCAWallet/Extensions/UITableView+.swift @@ -30,7 +30,7 @@ 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.textColor = .walletBlack messageLabel.numberOfLines = 0 messageLabel.textAlignment = .center messageLabel.font = UIFont.systemFont(ofSize: 16) @@ -39,6 +39,7 @@ extension UITableView { 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 index 85b3bf8..890f6c9 100644 --- a/DGCAWallet/Extensions/UIViewController+.swift +++ b/DGCAWallet/Extensions/UIViewController+.swift @@ -29,17 +29,10 @@ 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() - } - @available(iOS 13.0, *) var sceneDelegate: SceneDelegate? { guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let delegate = windowScene.delegate as? SceneDelegate else { return nil } + let delegate = windowScene.delegate as? SceneDelegate else { return nil } return delegate } } diff --git a/DGCAWallet/Extensions/UserDefaults+.swift b/DGCAWallet/Extensions/UserDefaults+.swift index 189cbfe..daffd52 100644 --- a/DGCAWallet/Extensions/UserDefaults+.swift +++ b/DGCAWallet/Extensions/UserDefaults+.swift @@ -38,7 +38,6 @@ enum ObjectSavableError: String, LocalizedError { } } - 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 diff --git a/DGCAWallet/Models/AccessTokenResponse.swift b/DGCAWallet/Models/AccessTokenResponse.swift new file mode 100644 index 0000000..ac3617c --- /dev/null +++ b/DGCAWallet/Models/AccessTokenResponse.swift @@ -0,0 +1,67 @@ +// +/*- + * ---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 + */ +// +// AccessTokenResponse.swift +// DGCAWallet +// +// Created by Illia Vlasov on 22.09.2021. +// + + +import Foundation + +struct AccessTokenResponse : Codable { + let jti : String? + let lss : String? + let iat : Int? + let sub : String? + let aud : String? + let exp : Int? + let t : Int? + let v : String? + let confirmation : String? + let vc : ValidationCertificate? + let result : String? + let results : [LimitationInfo]? +} + +struct LimitationInfo : Codable { + let identifier : String + let result : String + let type : String + let details : String +} + +struct ValidationCertificate : Codable { + let lang : String + let fnt : String + let gnt : String + let dob : String + let coa : String + let cod : String + let roa : String + let rod : String + let type : [String] + let category : [String] + let validationClock : String + let validFrom : String + let validTo : String +} diff --git a/DGCAWallet/Models/CertificateValidator.swift b/DGCAWallet/Models/CertificateValidator.swift new file mode 100644 index 0000000..c1bdac0 --- /dev/null +++ b/DGCAWallet/Models/CertificateValidator.swift @@ -0,0 +1,265 @@ +// +/*- + * ---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 + */ +// +// CertificateValidator..swift +// +// +// Created by Igor Khomiak on 15.10.2021. +// + +import Foundation +import SwiftDGC +import CertLogic + +public typealias ValidityCompletion = (ValidityState) -> Void + +public class CertificateValidator { + + public let certificate: HCert + + init(with cert: HCert) { + self.certificate = cert + } + + func validate(completion: ValidityCompletion) { + let failures = findValidityFailures() + + let technicalValidity: HCertValidity = failures.isEmpty ? .valid : .invalid + let issuerValidity = validateCertLogicForIssuer() + let destinationValidity = validateCertLogicForDestination() + let travalerValidity = validateCertLogicForTraveller() + let (infoRulesSection, allRulesValidity): (InfoSection?, HCertValidity) + if technicalValidity == .valid { + (infoRulesSection, allRulesValidity) = validateCertLogicForAllRules() + } else { + (infoRulesSection, allRulesValidity) = (nil, .invalid) + } + + let validityState = ValidityState( + technicalValidity: technicalValidity, + issuerValidity: issuerValidity, + destinationValidity: destinationValidity, + travalerValidity: travalerValidity, + allRulesValidity: allRulesValidity, + revocationValidity: .valid, + validityFailures: failures, + infoRulesSection: infoRulesSection) + + completion(validityState) + } + + private func findValidityFailures() -> [String] { + var failures = [String]() + if !certificate.cryptographicallyValid { + failures.append("No entries in the certificate.".localized) + } + if certificate.exp < HCert.clock { + failures.append("Certificate past expiration date.".localized) + } + if certificate.iat > HCert.clock { + failures.append("Certificate issuance date is in the future.".localized) + } + if certificate.statement == nil { + failures.append("No entries in the certificate.".localized) + return failures + } + failures.append(contentsOf: certificate.statement.validityFailures) + return failures + } + + // MARK: - private validation methods + private func validateCertLogicForAllRules() -> (InfoSection?, HCertValidity) { + var validity: HCertValidity = .valid + let certType = certificationType(for: certificate.certificateType) + var infoSection: InfoSection? + + if let countryCode = certificate.ruleCountryCode { + let valueSets = DataCenter.localDataManager.getValueSetsForExternalParameters() + let filterParameter = FilterParameter(validationClock: Date(), + countryCode: countryCode, + certificationType: certType) + let externalParameters = ExternalParameter(validationClock: Date(), + valueSets: valueSets, + exp: certificate.exp, + iat: certificate.iat, + issuerCountryCode: certificate.issCode, + kid: certificate.kidStr) + let result = CertLogicManager.shared.validate(filter: filterParameter, + external: externalParameters, payload: certificate.body.description) + let failsAndOpen = result.filter { $0.result != .passed } + + if failsAndOpen.count > 0 { + validity = .ruleInvalid + infoSection = InfoSection(header: "Possible limitation", content: "Country rules validation failed") + var listOfRulesSection: [InfoSection] = [] + result.sorted(by: { $0.result.rawValue < $1.result.rawValue }).forEach { validationResult in + if let error = validationResult.validationErrors?.first { + switch validationResult.result { + case .fail: + listOfRulesSection.append(InfoSection(header: "CirtLogic Engine error", + content: error.localizedDescription, + countryName: certificate.ruleCountryCode, + ruleValidationResult: .failed)) + case .open: + listOfRulesSection.append(InfoSection(header: "CirtLogic Engine error", + content: error.localizedDescription, + countryName: certificate.ruleCountryCode, + ruleValidationResult: .open)) + case .passed: + listOfRulesSection.append(InfoSection(header: "CirtLogic Engine error", + content: error.localizedDescription, + countryName: certificate.ruleCountryCode, + 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 = CertLogicManager.shared.getRuleDetailsError(rule: rule, filter: filterParameter) + dict.keys.forEach({ detailsError += $0 + ": " + (dict[$0] ?? "") + " " }) + } + switch validationResult.result { + case .fail: + listOfRulesSection.append(InfoSection(header: errorString, + content: detailsError, + countryName: certificate.ruleCountryCode, + ruleValidationResult: .failed)) + case .open: + listOfRulesSection.append(InfoSection(header: errorString, + content: detailsError, + countryName: certificate.ruleCountryCode, + ruleValidationResult: .open)) + case .passed: + listOfRulesSection.append(InfoSection(header: errorString, + content: detailsError, + countryName: certificate.ruleCountryCode, + ruleValidationResult: .passed)) + } + } + } + infoSection?.sectionItems = listOfRulesSection + } + } + return (infoSection, validity) + } + + private func validateCertLogicForIssuer() -> HCertValidity { + let certType = certificationType(for: certificate.certificateType) + if let countryCode = certificate.ruleCountryCode { + let valueSets = DataCenter.localDataManager.getValueSetsForExternalParameters() + let filterParameter = FilterParameter(validationClock: Date(), + countryCode: countryCode, + certificationType: certType) + let externalParameters = ExternalParameter(validationClock: Date(), + valueSets: valueSets, + exp: certificate.exp, + iat: certificate.iat, + issuerCountryCode: certificate.issCode, + kid: certificate.kidStr) + let result = CertLogicManager.shared.validateIssuer(filter: filterParameter, + external: externalParameters, payload: certificate.body.description) + let fails = result.filter { $0.result == .fail } + if !fails.isEmpty { + return .invalid + } + let open = result.filter { $0.result == .open } + if !open.isEmpty { + return .ruleInvalid + } + } + return .valid + } + + private func validateCertLogicForDestination() -> HCertValidity { + let certType = certificationType(for: certificate.certificateType) + if let countryCode = certificate.ruleCountryCode { + let valueSets = DataCenter.localDataManager.getValueSetsForExternalParameters() + let filterParameter = FilterParameter(validationClock: Date(), + countryCode: countryCode, + certificationType: certType) + let externalParameters = ExternalParameter(validationClock: Date(), + valueSets: valueSets, + exp: certificate.exp, + iat: certificate.iat, + issuerCountryCode: certificate.issCode, + kid: certificate.kidStr) + let result = CertLogicManager.shared.validateDestination(filter: filterParameter, + external: externalParameters, payload: certificate.body.description) + let fails = result.filter { $0.result == .fail } + if !fails.isEmpty { + return .invalid + } + let open = result.filter { $0.result == .open } + if !open.isEmpty { + return .ruleInvalid + } + } + return .valid + } + + private func validateCertLogicForTraveller() -> HCertValidity { + let certType = certificationType(for: certificate.certificateType) + if let countryCode = certificate.ruleCountryCode { + let valueSets = DataCenter.localDataManager.getValueSetsForExternalParameters() + let filterParameter = FilterParameter(validationClock: Date(), + countryCode: countryCode, + certificationType: certType) + let externalParameters = ExternalParameter(validationClock: Date(), + valueSets: valueSets, + exp: certificate.exp, + iat: certificate.iat, + issuerCountryCode: certificate.issCode, + kid: certificate.kidStr) + let result = CertLogicManager.shared.validateTraveller(filter: filterParameter, + external: externalParameters, payload: certificate.body.description) + + let fails = result.filter { $0.result == .fail } + if !fails.isEmpty { + return .invalid + } + let open = result.filter { $0.result == .open } + if !open.isEmpty { + return .ruleInvalid + } + } + return .valid + } + + private func certificationType(for type: SwiftDGC.HCertType) -> CertificateType { + switch type { + case .recovery: + return .recovery + case .test: + return .test + case .vaccine: + return .vaccination + case .unknown: + return .general + } + } +} diff --git a/DGCAWallet/Models/CountryDataStorage.swift b/DGCAWallet/Models/CountryDataStorage.swift deleted file mode 100644 index 968bb0a..0000000 --- a/DGCAWallet/Models/CountryDataStorage.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -/*- - * ---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/DGCLogger.swift b/DGCAWallet/Models/DGCLogger.swift new file mode 100644 index 0000000..a3b9f8b --- /dev/null +++ b/DGCAWallet/Models/DGCLogger.swift @@ -0,0 +1,61 @@ +/*- + * ---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 + */ +// +// DGCLogger.swift +// DGCAVerifier +// +// Created by Igor Khomiak on 21.10.2021. +// + + +import UIKit + +public enum DGCLogger {} + +// MARK: - Public +extension DGCLogger { + public static func logInfo(_ message: String, file: NSString = #file, function: String = #function, line: Int = #line) { + log(message, tag: .info, file: file, function: function, line: line) + } + + public static func logError(_ message: String, file: NSString = #file, function: String = #function, line: Int = #line) { + log(message, tag: .error, file: file, function: function, line: line) + } + + public static func logError(_ error: Error, file: NSString = #file, function: String = #function, line: Int = #line) { + log(error, tag: .error, file: file, function: function, line: line) + } +} + +// MARK: - Private +private extension DGCLogger { + + enum Tag: String { + case error + case info + } + + static func log(_ value: Any, tag: Tag, file: NSString, function: String, line: Int) { + #if DEBUG + let valueString = "\(value)".padEnd(length: 50) + print(" [ \(tag.rawValue.uppercased()) ] \(valueString) \t: \(function) \(file.lastPathComponent):\(line)") + #endif + } +} diff --git a/DGCAWallet/Models/DataStorageManagement/DataCenter.swift b/DGCAWallet/Models/DataStorageManagement/DataCenter.swift new file mode 100644 index 0000000..9afb83d --- /dev/null +++ b/DGCAWallet/Models/DataStorageManagement/DataCenter.swift @@ -0,0 +1,232 @@ +// +/*- + * ---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 + */ +// +// DataCenter.swift +// DGCAVerifier +// +// Created by Igor Khomiak on 03.11.2021. +// + +import UIKit +import SwiftDGC +import CertLogic + +typealias CompletionHandler = () -> Void + +class DataCenter { + static let shared = DataCenter() + static var appVersion: String { + let versionValue = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "?.?.?" + let buildNumValue = (Bundle.main.infoDictionary?["CFBundleVersion"] as? String) ?? "?.?.?" + return "\(versionValue)(\(buildNumValue))" + } + + static let localDataManager: LocalDataManager = LocalDataManager() + static let imageDataManager: ImageDataManager = ImageDataManager() + static let pdfDataManager: PdfDataManager = PdfDataManager() + + static var downloadedDataHasExpired: Bool { + return lastFetch.timeIntervalSinceNow < -SharedConstants.expiredDataInterval + } + + static var appWasRunWithOlderVersion: Bool { + return localDataManager.localData.lastLaunchedAppVersion != appVersion + } + + // MARK: - public variables + + static var lastFetch: Date { + get { + return localDataManager.localData.lastFetch + } + set { + localDataManager.localData.lastFetch = newValue + } + } + + static var lastLaunchedAppVersion: String { + return DataCenter.localDataManager.localData.lastLaunchedAppVersion + } + + static var certStrings: [DatedCertString] { + get { + return localDataManager.localData.certStrings + } + set { + localDataManager.localData.certStrings = newValue + } + } + + static var resumeToken: String? { + get { + return localDataManager.localData.resumeToken + } + set { + localDataManager.localData.resumeToken = newValue + } + } + + static var images: [SavedImage] { + get { + return imageDataManager.localData.images + } + set { + imageDataManager.localData.images = newValue + } + } + + static var pdfs: [SavedPDF] { + get { + return pdfDataManager.localData.pdfs + } + set { + pdfDataManager.localData.pdfs = newValue + } + } + + static var countryCodes: [CountryModel] { + get { + return localDataManager.localData.countryCodes + } + set { + localDataManager.localData.countryCodes = newValue + } + } + + static var rules: [Rule] { + get { + return localDataManager.localData.rules + } + set { + localDataManager.localData.rules = newValue + } + } + + static var valueSets: [ValueSet] { + get { + return localDataManager.localData.valueSets + } + set { + localDataManager.localData.valueSets = newValue + } + } + + static func saveLocalData() { + localDataManager.save { rez in } + } + + static func addValueSets(_ list: [ValueSet]) { + list.forEach { localDataManager.add(valueSet: $0) } + } + + static func addRules(_ list: [Rule]) { + list.forEach { localDataManager.add(rule: $0) } + } + + static func addCountries(_ list: [CountryModel]) { + localDataManager.localData.countryCodes.removeAll() + list.forEach { localDataManager.add(country: $0) } + } + + // MARK: - Data initialize methods + class func prepareLocalData(completion: @escaping DataCompletionHandler) { + initializeAllStorageData { result in + let shouldDownload = self.downloadedDataHasExpired || self.appWasRunWithOlderVersion + if !shouldDownload { + completion(result) + } else { + reloadStorageData { result in + initializeAllStorageData { result in + completion(result) + } + } + } + } + } + + static func initializeAllStorageData(completion: @escaping DataCompletionHandler) { + let group = DispatchGroup() + + group.enter() + localDataManager.loadLocallyStoredData { result in + CertLogicManager.shared.setRules(ruleList: rules) + + group.enter() + imageDataManager.loadLocallyStoredData { result in + group.leave() + } + + group.enter() + pdfDataManager.loadLocallyStoredData { result in + group.leave() + } + group.leave() + } + + group.notify(queue: .main) { + completion(.success(true)) + } + } + + static func reloadStorageData(completion: @escaping DataCompletionHandler) { + let group = DispatchGroup() + + group.enter() + localDataManager.loadLocallyStoredData { result in + CertLogicManager.shared.setRules(ruleList: rules) + + group.enter() + imageDataManager.loadLocallyStoredData { result in + group.leave() + } + + group.enter() + pdfDataManager.loadLocallyStoredData { result in + group.leave() + } + + group.enter() + GatewayConnection.loadCountryList { list, error in + group.leave() + } + + group.enter() + GatewayConnection.loadValueSetsFromServer { list, error in + group.leave() + } + + group.enter() + GatewayConnection.loadRulesFromServer { listRules, error in + guard error == nil else { completion(.failure(error!)); return } + CertLogicManager.shared.setRules(ruleList: listRules ?? []) + group.leave() + } + + group.leave() + } + + group.notify(queue: .main) { + localDataManager.localData.lastFetch = Date() + localDataManager.localData.lastLaunchedAppVersion = Self.appVersion + localDataManager.save(completion: completion) + } + } +} diff --git a/DGCAWallet/Models/DataStorageManagement/DataStuctures.swift b/DGCAWallet/Models/DataStorageManagement/DataStuctures.swift new file mode 100644 index 0000000..a36398b --- /dev/null +++ b/DGCAWallet/Models/DataStorageManagement/DataStuctures.swift @@ -0,0 +1,78 @@ +// +/*- + * ---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 + */ +// +// DataStuctures.swift +// DGCAVerifier +// +// Created by Igor Khomiak on 11/3/21. +// + + +import Foundation +import SwiftDGC +import CertLogic + +class DatedCertString: Codable { + var isSelected: Bool = false + let date: Date + let certString: String + let storedTAN: String? + var cert: HCert? { + return try? HCert(from: certString) + } + + + init(date: Date, certString: String, storedTAN: String?) { + self.date = date + self.certString = certString + self.storedTAN = storedTAN + } +} + +class LocalData: Codable { + var encodedPublicKeys = [String: [String]]() + var certStrings = [DatedCertString]() + + var countryCodes = [CountryModel]() + var valueSets = [ValueSet]() + var rules = [Rule]() + + var resumeToken: String? + var lastFetchRaw: Date? + var lastFetch: Date { + get { + lastFetchRaw ?? Date.distantPast + } + set { + lastFetchRaw = newValue + } + } + var config = Config.load() + var lastLaunchedAppVersion = "0.0" +} + +class ImageDataStorage: Codable { + var images = [SavedImage]() +} + +class PdfDataStorage: Codable { + var pdfs = [SavedPDF]() +} diff --git a/DGCAWallet/Models/DataStorageManagement/ImageDataManager.swift b/DGCAWallet/Models/DataStorageManagement/ImageDataManager.swift new file mode 100644 index 0000000..02b9646 --- /dev/null +++ b/DGCAWallet/Models/DataStorageManagement/ImageDataManager.swift @@ -0,0 +1,69 @@ +// +/*- + * ---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 + */ +// +// ImageDataManager.swift +// DGCAVerifier +// +// Created by Alexandr Chernyy on 22.06.2021. +// +import Foundation +import SwiftDGC +import SwiftyJSON + +class ImageDataManager { + var localData: ImageDataStorage = ImageDataStorage() + lazy var storage = SecureStorage(fileName: SharedConstants.imageStorageName) + + func add(savedImage: SavedImage, completion: @escaping DataCompletionHandler) { + if !localData.images.contains(where: { $0.identifier == savedImage.identifier }) { + localData.images.append(savedImage) + storage.save(localData, completion: completion) + } else { + completion(.success(true)) + } + } + + func deleteImage(with identifier: String, completion: @escaping DataCompletionHandler) { + let images = localData.images.filter { $0.identifier != identifier } + localData.images = images + storage.save(localData, completion: completion) + } + + func isImageExistWith(identifier: String) -> Bool { + return localData.images.contains(where: { $0.identifier == identifier }) + } + + func save(completion: @escaping DataCompletionHandler) { + storage.save(localData, completion: completion) + } + + func loadLocallyStoredData(completion: @escaping DataCompletionHandler) { + storage.loadStoredData(fallback: localData) { [unowned self] data in + guard let result = data else { + completion(.failure(DataOperationError.noInputData)) + return + } + DGCLogger.logInfo(String(format: "Loaded %d images", result.images.count)) + self.localData = result + completion(.success(true)) + } + } +} diff --git a/DGCAWallet/Models/DataStorageManagement/LocalDataManager.swift b/DGCAWallet/Models/DataStorageManagement/LocalDataManager.swift new file mode 100644 index 0000000..4262c10 --- /dev/null +++ b/DGCAWallet/Models/DataStorageManagement/LocalDataManager.swift @@ -0,0 +1,146 @@ +// +/*- + * ---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 + */ +// +// LocalDataManager.swift +// DGCAVerifier +// +// Created by Yannick Spreen on 4/25/21. +// + +import Foundation +import SwiftDGC +import SwiftyJSON +import CertLogic + +class LocalDataManager { + var localData: LocalData = LocalData() + lazy var storage = SecureStorage(fileName: SharedConstants.dataStorageName) + + // MARK: - Public Keys + func add(encodedPublicKey: String) { + let kid = KID.from(encodedPublicKey) + let kidStr = KID.string(from: kid) + + let list = localData.encodedPublicKeys[kidStr] ?? [] + if !list.contains(encodedPublicKey) { + localData.encodedPublicKeys[kidStr] = list + [encodedPublicKey] + } + } + + func add(_ cert: HCert, with tan: String?, completion: @escaping DataCompletionHandler) { + localData.certStrings.append(DatedCertString(date: Date(), certString: cert.fullPayloadString, storedTAN: tan)) + storage.save(localData, completion: completion) + } + + func remove(withDate date: Date, completion: @escaping DataCompletionHandler) { + if let ind = localData.certStrings.firstIndex(where: { $0.date == date }) { + localData.certStrings.remove(at: ind) + storage.save(localData, completion: completion) + } + } + + // MARK: Data Add and update methods + // MARK: - Countries + func add(country: CountryModel) { + if !localData.countryCodes.contains(where: { $0.code == country.code }) { + localData.countryCodes.append(country) + } + } + + func update(country: CountryModel) { + guard let countryFromDB = localData.countryCodes.filter({ $0.code == country.code }).first else { return } + countryFromDB.debugModeEnabled = country.debugModeEnabled + } + + // MARK: - ValueSets + func add(valueSet: ValueSet) { + if !localData.valueSets.contains(where: { $0.valueSetId == valueSet.valueSetId }) { + localData.valueSets.append(valueSet) + } + } + + func deleteValueSetWithHash(hash: String) { + localData.valueSets = localData.valueSets.filter { $0.hash != hash } + } + + func isValueSetExistWithHash(hash: String) -> Bool { + return localData.valueSets.contains(where: { $0.hash == hash }) + } + + public func getValueSetsForExternalParameters() -> Dictionary { + var returnValue = Dictionary() + localData.valueSets.forEach { valueSet in + let keys = Array(valueSet.valueSetValues.keys) + returnValue[valueSet.valueSetId] = keys + } + return returnValue + } + + // MARK: - Rules + func add(rule: Rule) { + if !localData.rules.contains(where: { $0.identifier == rule.identifier && $0.version == rule.version }) { + localData.rules.append(rule) + } + } + + func deleteRuleWithHash(hash: String) { + localData.rules = localData.rules.filter { $0.hash != hash } + } + + func isRuleExistWithHash(hash: String) -> Bool { + return localData.rules.contains(where: { $0.hash == hash }) + } + + // MARK: - Config + func merge(other: JSON) { + localData.config.merge(other: other) + } + + // MARK: - Service + func save(completion: @escaping DataCompletionHandler) { + storage.save(localData, completion: completion) + } + + func loadLocallyStoredData(completion: @escaping DataCompletionHandler) { + storage.loadStoredData(fallback: localData) { [unowned self] data in + guard let loadedData = data else { + completion(.failure(DataOperationError.noInputData)) + return + } + + DGCLogger.logInfo(String(format: "%d certs loaded.", loadedData.certStrings.count)) + if loadedData.lastLaunchedAppVersion != DataCenter.appVersion { + loadedData.config = self.localData.config + loadedData.lastLaunchedAppVersion = DataCenter.appVersion + } + self.localData = loadedData + self.save(completion: completion) + } + } + + var versionedConfig: JSON { + if localData.config["versions"][DataCenter.appVersion].exists() { + return localData.config["versions"][DataCenter.appVersion] + } else { + return localData.config["versions"]["default"] + } + } +} diff --git a/DGCAWallet/Models/DataStorageManagement/PdfDataManager.swift b/DGCAWallet/Models/DataStorageManagement/PdfDataManager.swift new file mode 100644 index 0000000..a52f2da --- /dev/null +++ b/DGCAWallet/Models/DataStorageManagement/PdfDataManager.swift @@ -0,0 +1,68 @@ +// +/*- + * ---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 + */ +// +// PdfDataManager.swift +// DGCAVerifier +// +// Created by Igor Khomiak on 22.06.2021. +// +import Foundation +import SwiftDGC + +class PdfDataManager { + var localData: PdfDataStorage = PdfDataStorage() + lazy var storage = SecureStorage(fileName: SharedConstants.pdfStorageName) + + func add(savedPdf: SavedPDF, completion: @escaping DataCompletionHandler) { + if !localData.pdfs.contains(where: { $0.identifier == savedPdf.identifier }) { + localData.pdfs.append(savedPdf) + storage.save(localData, completion: completion) + } else { + completion(.success(true)) + } + } + + func deletePDF(with identifier: String, completion: @escaping DataCompletionHandler) { + let pdfs = localData.pdfs.filter { $0.identifier != identifier } + localData.pdfs = pdfs + storage.save(localData, completion: completion) + } + + func isPdfExistWith(identifier: String) -> Bool { + return localData.pdfs.contains(where: { $0.identifier == identifier }) + } + + func save(completion: @escaping DataCompletionHandler) { + storage.save(localData, completion: completion) + } + + func loadLocallyStoredData(completion: @escaping DataCompletionHandler) { + storage.loadStoredData(fallback: localData) { [unowned self] data in + guard let result = data else { + completion(.failure(DataOperationError.noInputData)) + return + } + DGCLogger.logInfo(String(format: "Loaded %d pdf files", result.pdfs.count)) + self.localData = result + completion(.success(true)) + } + } +} diff --git a/DGCAWallet/Models/ImageDataStorage.swift b/DGCAWallet/Models/ImageDataStorage.swift deleted file mode 100644 index 847b62e..0000000 --- a/DGCAWallet/Models/ImageDataStorage.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -/*- - * ---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 - */ -// -// ImageDataStorage.swift -// DGCAWallet -// -// Created by Alexandr Chernyy on 23.08.2021. -// - - -import Foundation -import SwiftDGC - -struct ImageDataStorage: Codable { - static var sharedInstance = ImageDataStorage() - static let storage = SecureStorage(fileName: "images_secure") - - var images = [SavedImage]() - - mutating func add(savedImage: SavedImage) { - let list = images - if list.contains(where: { image in - image.identifier == savedImage.identifier - }) { - return - } - images.append(savedImage) - save() - } - - public func save() { - Self.storage.save(self) - } - - public mutating func deleteImageWith(identifier: String) { - self.images = self.images.filter { $0.identifier != identifier } - save() - } - - public func isImageExistWith(identifier: String) -> Bool { - let list = images - return list.contains(where: { image in - image.identifier == identifier - }) - } - static func initialize(completion: @escaping () -> Void) { - storage.loadOverride(fallback: ImageDataStorage.sharedInstance) { success in - guard let result = success else { - return - } - let format = l10n("log.images") - print(String.localizedStringWithFormat(format, result.images.count)) - ImageDataStorage.sharedInstance = result - completion() - } - } -} diff --git a/DGCAWallet/Models/LocalData.swift b/DGCAWallet/Models/LocalData.swift deleted file mode 100644 index 52fba16..0000000 --- a/DGCAWallet/Models/LocalData.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -/*- - * ---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 - */ -// -// LocalData.swift -// DGCAWallet -// -// Created by Yannick Spreen on 4/25/21. -// - -import Foundation -import SwiftDGC -import SwiftyJSON - -struct DatedCertString: Codable { - var date: Date - var certString: String - var storedTAN: String? - var cert: HCert? { - HCert(from: certString, applicationType: .wallet) - } -} - -struct LocalData: Codable { - private enum Constants { - static let pubKeysStorageFilename = "secure" - static let bundleVersion = "CFBundleShortVersionString" - static let unknown = "?.?.?" - static let versions = "versions" - static let defaultVer = "default" - } - 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() - var lastLaunchedAppVersion = Self.appVersion - - public func save() { - Self.storage.save(self) - } - public static func add(_ cert: HCert, with tan: String?) { - sharedInstance.certStrings.append(.init(date: Date(), certString: cert.fullPayloadString, storedTAN: tan)) - sharedInstance.save() - } - static func initialize(completion: @escaping () -> Void) { - storage.loadOverride(fallback: LocalData.sharedInstance) { success in - guard var result = success else { - return - } - let format = l10n("log.certs-loaded") - print(String.localizedStringWithFormat(format, result.certStrings.count)) - if result.lastLaunchedAppVersion != Self.appVersion { - result.config = LocalData.sharedInstance.config - } - LocalData.sharedInstance = result - completion() - GatewayConnection.fetchContext() - } - } - var versionedConfig: JSON { - if config[Constants.versions][Self.appVersion].exists() { - return config[Constants.versions][Self.appVersion] - } - return config[Constants.versions][Constants.defaultVer] - } -} diff --git a/DGCAWallet/Models/PdfDataStorage.swift b/DGCAWallet/Models/PdfDataStorage.swift deleted file mode 100644 index ec3dcda..0000000 --- a/DGCAWallet/Models/PdfDataStorage.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -/*- - * ---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 - */ -// -// PdfDataStorage.swift -// DGCAWallet -// -// Created by Alexandr Chernyy on 23.08.2021. -// - - -import Foundation - -import SwiftDGC - -struct PdfDataStorage: Codable { - static var sharedInstance = PdfDataStorage() - static let storage = SecureStorage(fileName: "pdfs_secure") - - var pdfs = [SavedPDF]() - - mutating func add(savedPdf: SavedPDF) { - let list = pdfs - if list.contains(where: { pdf in - pdf.identifier == savedPdf.identifier - }) { - return - } - pdfs.append(savedPdf) - save() - } - - public func save() { - Self.storage.save(self) - } - - public mutating func deletePdfWith(identifier: String) { - self.pdfs = self.pdfs.filter { $0.identifier != identifier } - save() - } - - public func isPdfExistWith(identifier: String) -> Bool { - let list = pdfs - return list.contains(where: { pdf in - pdf.identifier == identifier - }) - } - static func initialize(completion: @escaping () -> Void) { - storage.loadOverride(fallback: PdfDataStorage.sharedInstance) { success in - guard let result = success else { - return - } - let format = l10n("log.pdfs") - print(String.localizedStringWithFormat(format, result.pdfs.count)) - PdfDataStorage.sharedInstance = result - completion() - } - } -} diff --git a/DGCAWallet/Models/RulesDataStorage.swift b/DGCAWallet/Models/RulesDataStorage.swift deleted file mode 100644 index 15a3b08..0000000 --- a/DGCAWallet/Models/RulesDataStorage.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -/*- - * ---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/SavedImage.swift b/DGCAWallet/Models/SavedImage.swift index faba98f..451b688 100644 --- a/DGCAWallet/Models/SavedImage.swift +++ b/DGCAWallet/Models/SavedImage.swift @@ -26,7 +26,6 @@ // -import Foundation import UIKit public class SavedImage: Codable { diff --git a/DGCAWallet/Models/SavedPDF.swift b/DGCAWallet/Models/SavedPDF.swift index 4b5413c..9a2eacd 100644 --- a/DGCAWallet/Models/SavedPDF.swift +++ b/DGCAWallet/Models/SavedPDF.swift @@ -26,7 +26,6 @@ // -import Foundation import UIKit import PDFKit diff --git a/DGCAWallet/Models/SharedConstants.swift b/DGCAWallet/Models/SharedConstants.swift new file mode 100644 index 0000000..a77467d --- /dev/null +++ b/DGCAWallet/Models/SharedConstants.swift @@ -0,0 +1,45 @@ +// +/*- + * ---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 + */ +// +// SharedConstants.swift +// DGCAVerifier +// +// Created by Igor Khomiak on 05.11.2021. +// + + +import Foundation + +enum SharedConstants { + static let keyTicketingToken = "DGCAWallet.TicketingToken" + static let keyAccessToken = "DGCAWallet.AccessToken" + static let keyXnonce = "DGCAWallet.xnonce" + + static let expiredDataInterval: TimeInterval = 12.0 * 60 * 60 + static let dataStorageName = "secure_storage" + static let imageStorageName = "images_secure" + static let pdfStorageName = "pdfs_secure" + + static let userDefaultsCountryKey = "UDCountryKey" + + static let linkToOpenGitHubSource = "https://github.com/eu-digital-green-certificates" + static let linkToOopenEuCertDoc = "https://ec.europa.eu/health/ehealth/covid-19_en" +} diff --git a/DGCAWallet/Extensions/String+.swift b/DGCAWallet/Models/TicketingData.swift similarity index 71% rename from DGCAWallet/Extensions/String+.swift rename to DGCAWallet/Models/TicketingData.swift index 905bda7..f0da93f 100644 --- a/DGCAWallet/Extensions/String+.swift +++ b/DGCAWallet/Models/TicketingData.swift @@ -19,20 +19,21 @@ * ---license-end */ // -// String+.swift +// TicketingData.swift // DGCAWallet // -// Created by Alexandr Chernyy on 23.08.2021. +// Created by Illia Vlasov on 20.09.2021. // import Foundation -import UIKit -extension String { - func convertBase64StringToImage () -> UIImage? { - guard let imageData = Data.init(base64Encoded: self, options: .init(rawValue: 0)) else { return nil } - let image = UIImage(data: imageData) - return image - } +struct TicketingQR : Codable { + var protocolName : String + var protocolVersion : String + var serviceIdentity : String + var token : String + var consent : String + var subject : String + var serviceProvider : String } diff --git a/DGCAWallet/Models/ValidityCellModel.swift b/DGCAWallet/Models/ValidityCellModel.swift index 05c347d..a921696 100644 --- a/DGCAWallet/Models/ValidityCellModel.swift +++ b/DGCAWallet/Models/ValidityCellModel.swift @@ -33,13 +33,14 @@ enum ValidityCellModelType: Int { case countryAndTimeSelection } -public final class ValidityCellModel { +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) { + init(cellType: ValidityCellModelType = .titleAndDescription, title: String? = nil, description: String? = nil, + needChangeTitleFont: Bool = false) { self.needChangeTitleFont = needChangeTitleFont self.cellType = cellType self.title = title diff --git a/DGCAWallet/Models/ValueSetsDataStorage.swift b/DGCAWallet/Models/ValueSetsDataStorage.swift deleted file mode 100644 index 78f65dd..0000000 --- a/DGCAWallet/Models/ValueSetsDataStorage.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -/*- - * ---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/Resources/DCCRevocation.xcdatamodeld/DCCRevocation.xcdatamodel/contents b/DGCAWallet/Resources/DCCRevocation.xcdatamodeld/DCCRevocation.xcdatamodel/contents new file mode 100644 index 0000000..2da5c33 --- /dev/null +++ b/DGCAWallet/Resources/DCCRevocation.xcdatamodeld/DCCRevocation.xcdatamodel/contents @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DGCAWallet/Services/Brightness.swift b/DGCAWallet/Services/Brightness.swift index f5608dd..d7d0781 100644 --- a/DGCAWallet/Services/Brightness.swift +++ b/DGCAWallet/Services/Brightness.swift @@ -25,7 +25,6 @@ // Created by Yannick Spreen on 4/30/21. // -import Foundation import UIKit struct Brightness { diff --git a/DGCAWallet/Services/CertLogicEngineManager.swift b/DGCAWallet/Services/CertLogicEngineManager.swift deleted file mode 100644 index 1c51af4..0000000 --- a/DGCAWallet/Services/CertLogicEngineManager.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -/*- - * ---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/CertLogicManager.swift b/DGCAWallet/Services/CertLogicManager.swift new file mode 100644 index 0000000..6a5311d --- /dev/null +++ b/DGCAWallet/Services/CertLogicManager.swift @@ -0,0 +1,58 @@ +// +/*- + * ---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 + */ +// +// CertLogicManager.swift +// DGCAVerifier +// +// Created by Alexandr Chernyy on 23.06.2021. +// +import Foundation +import CertLogic +import SwiftDGC + +class CertLogicManager { + static let shared = CertLogicManager() + + var 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 validateIssuer(filter: FilterParameter, external: ExternalParameter, payload: String) -> [ValidationResult] { + return certLogicEngine.validate(filter: filter, external: external, payload: payload, validationType: .issuer) + } + + func validateDestination(filter: FilterParameter, external: ExternalParameter, payload: String) -> [ValidationResult] { + return certLogicEngine.validate(filter: filter, external: external, payload: payload, validationType: .destination) + } + + func validateTraveller(filter: FilterParameter, external: ExternalParameter, payload: String) -> [ValidationResult] { + return certLogicEngine.validate(filter: filter, external: external, payload: payload, validationType: .traveller) + } + + 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 738ede5..27ecff7 100644 --- a/DGCAWallet/Services/GatewayConnection.swift +++ b/DGCAWallet/Services/GatewayConnection.swift @@ -25,34 +25,61 @@ // Created by Yannick Spreen on 5/3/21. // -import Foundation +import UIKit import Alamofire import SwiftDGC import SwiftyJSON -import UIKit import CertLogic -import CryptoKit +import JWTDecode + +enum GatewayError: Error { + case insufficientData + case encodingError + case signingError + case updatingError + case incorrectDataResponse + case connection(error: Error) + case local(description: String) + case parsingError + case privateKeyError + case tokenError +} + +typealias ValueSetsCompletion = ([ValueSet]?, Error?) -> Void +typealias ValueSetCompletionHandler = (ValueSet?, Error?) -> Void +typealias RulesCompletion = ([Rule]?, Error?) -> Void +typealias RuleCompletionHandler = (Rule?, Error?) -> Void +typealias CountriesCompletion = ([CountryModel]?, Error?) -> Void +typealias TicketingCompletion = (AccessTokenResponse?, Error?) -> Void +typealias ContextCompletion = (Bool, String?, Error?) -> Void -struct GatewayConnection: ContextConnection { - public static func claim(cert: HCert, with tan: String?, completion: ((Bool, String?) -> Void)?) { +class GatewayConnection: ContextConnection { + static func claim(cert: HCert, with tan: String?, completion: @escaping ContextCompletion) { guard var tan = tan, !tan.isEmpty else { + completion(false, nil, GatewayError.insufficientData) return } + // Replace dashes, spaces, etc. and turn into uppercase. let set = CharacterSet(charactersIn: "0123456789").union(.uppercaseLetters) tan = tan.uppercased().components(separatedBy: set.inverted).joined() - + let tanHash = SHA256.stringDigest(input: Data(tan.data(using: .utf8) ?? .init())) let certHash = cert.certHash let pubKey = (X509.derPubKey(for: cert.keyPair) ?? Data()).base64EncodedString() - + let toBeSigned = tanHash + certHash + pubKey let toBeSignedData = Data(toBeSigned.data(using: .utf8) ?? .init()) + Enclave.sign(data: toBeSignedData, with: cert.keyPair, using: .ecdsaSignatureMessageX962SHA256) { sign, err in - guard let sign = sign, err == nil else { + guard err == nil else { + completion(false, nil, GatewayError.local(description: err!)) + return + } + guard let sign = sign else { + completion(false, nil, GatewayError.local(description: "No sign")) return } - let keyParam: [String: Any] = [ "type": "EC", "value": pubKey ] let param: [String: Any] = [ "DGCI": cert.uvci, @@ -62,66 +89,50 @@ struct GatewayConnection: ContextConnection { "signature": sign.base64EncodedString(), "sigAlg": "SHA256withECDSA" ] - request( - ["endpoints", "claim"], - method: .post, - parameters: param, - encoding: JSONEncoding.default - ).response { - guard - case .success(_) = $0.result, - let status = $0.response?.statusCode, - status / 100 == 2 - else { - completion?(false, nil) + request( ["endpoints", "claim"], method: .post, parameters: param, encoding: JSONEncoding.default, + headers: HTTPHeaders([HTTPHeader(name: "content-type", value: "application/json")])).response { + guard case .success(_) = $0.result, let status = $0.response?.statusCode, status / 100 == 2 else { + completion(false, nil, GatewayError.local(description: "Cannot claim certificate")) return } - let response = String(data: $0.data ?? .init(), encoding: .utf8) let json = JSON(parseJSON: response ?? "") let newTAN = json["tan"].string - completion?(true, newTAN) + completion(true, newTAN, nil) } } } - - public static func fetchContext() { - request( - ["context"] - ).response { - guard - let data = $0.data, - let string = String(data: data, encoding: .utf8) - else { - return - } + + static func fetchContext(completion: @escaping CompletionHandler) { + request( ["context"] ).response { + guard let data = $0.data, let string = String(data: data, encoding: .utf8) else { return } + let json = JSON(parseJSONC: string) - LocalData.sharedInstance.config.merge(other: json) - LocalData.sharedInstance.save() - if LocalData.sharedInstance.versionedConfig["outdated"].bool == true { - ( - UIApplication.shared.windows[0].rootViewController as? UINavigationController - )?.popToRootViewController(animated: false) + DataCenter.localDataManager.localData.config.merge(other: json) + DataCenter.localDataManager.save { result in + if DataCenter.localDataManager.versionedConfig["outdated"].bool == true { + let controller = UIApplication.shared.windows.first?.rootViewController as? UINavigationController + controller?.popToRootViewController(animated: false) + } + completion() } } } + static var config: JSON { - LocalData.sharedInstance.versionedConfig + return DataCenter.localDataManager.versionedConfig } } // MARK: Country, Rules, Valuesets extension - extension GatewayConnection { // Country list - public static func getListOfCountry(completion: (([CountryModel]) -> Void)?) { + private static func getListOfCountry(completion: @escaping CountriesCompletion) { request(["endpoints", "countryList"], method: .get).response { - guard - case let .success(result) = $0.result, - let response = result, + guard case let .success(result) = $0.result, let response = result, let responseStr = String(data: response, encoding: .utf8), - let json = JSON(parseJSON: responseStr).array - else { + let json = JSON(parseJSON: responseStr).array else { + completion(nil, GatewayError.parsingError) return } let codes = json.compactMap { $0.string } @@ -129,185 +140,275 @@ extension GatewayConnection { codes.forEach { code in countryList.append(CountryModel(code: code)) } - completion?(countryList) + completion(countryList, nil) } } - 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 - })) + + static func loadCountryList(completion: @escaping CountriesCompletion) { + getListOfCountry { list, error in + guard error == nil else { + completion(nil, GatewayError.connection(error: error!)) + return + } + guard let countryCodeList = list else { + completion(nil, GatewayError.local(description: "No sign")) + return + } + DataCenter.addCountries(countryCodeList) + let countryCodes = DataCenter.countryCodes.sorted(by: { $0.name < $1.name }) + completion(countryCodes, nil) } - } } + // Rules - public static func getListOfRules(completion: (([CertLogic.Rule]) -> Void)?) { + static func getListOfRules(completion: @escaping RulesCompletion) { request(["endpoints", "rules"], method: .get).response { - guard - case let .success(result) = $0.result, - let response = result, + guard case let .success(result) = $0.result, let response = result, let responseStr = String(data: response, encoding: .utf8) else { + completion(nil, GatewayError.parsingError) 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 - }) + DataCenter.rules = DataCenter.rules.filter { rule in + return !ruleHashes.contains(where: { $0.hash == rule.hash}) } + // Downloading new hashes - var rulesItems = [CertLogic.Rule]() - let downloadingGroup = DispatchGroup() + let rulesItems = SyncArray() + let group = 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) + group.enter() + if !DataCenter.localDataManager.isRuleExistWithHash(hash: ruleHash.hash) { + getRules(ruleHash: ruleHash) { rule, error in + guard error == nil else { + completion(nil, GatewayError.connection(error: error!)) + return + } + guard let rule = rule else { + completion(nil, GatewayError.parsingError) + return } - downloadingGroup.leave() + rulesItems.append(rule) + group.leave() } } else { - downloadingGroup.leave() + group.leave() } } - downloadingGroup.notify(queue: .main) { - completion?(rulesItems) - print("Finished all requests.") + group.notify(queue: .main) { + completion(rulesItems.resultArray, nil) + DGCLogger.logInfo("Finished all Rules requests.") } } } - public static func getRules(ruleHash: CertLogic.RuleHash, completion: ((CertLogic.Rule?) -> Void)?) { + + static func getRules(ruleHash: RuleHash, completion: @escaping RuleCompletionHandler) { request(["endpoints", "rules"], externalLink: "/\(ruleHash.country)/\(ruleHash.hash)", method: .get).response { - guard - case let .success(result) = $0.result, + guard case let .success(result) = $0.result, let response = result, let responseStr = String(data: response, encoding: .utf8) else { - completion?(nil) + completion(nil, GatewayError.parsingError) return } - if let rule: Rule = CertLogicEngine.getItem(from: responseStr) { + if var 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) + completion(rule, nil) } else { - completion?(nil) + completion(nil, GatewayError.signingError) } return + } else { + completion(nil, GatewayError.signingError) } - 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) + static func loadRulesFromServer(completion: @escaping RulesCompletion) { + getListOfRules { rulesList, error in + guard error == nil else { + completion(nil, GatewayError.connection(error: error!)) + return + } + guard let rules = rulesList else { + completion(nil, GatewayError.parsingError) + return } - RulesDataStorage.sharedInstance.lastFetch = Date() - RulesDataStorage.sharedInstance.save() - completion?(RulesDataStorage.sharedInstance.rules) + DataCenter.addRules(rules) + completion(DataCenter.rules, nil) } } // ValueSets - public static func getListOfValueSets(completion: (([CertLogic.ValueSet]) -> Void)?) { + static func getListOfValueSets(completion: @escaping ValueSetsCompletion) { request(["endpoints", "valuesets"], method: .get).response { - guard - case let .success(result) = $0.result, - let response = result, + guard case let .success(result) = $0.result, let response = result, let responseStr = String(data: response, encoding: .utf8) else { + completion(nil, GatewayError.parsingError) 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 - }) + DataCenter.valueSets = DataCenter.valueSets.filter { valueSet in + return !valueSetsHashes.contains(where: { $0.hash == valueSet.hash}) } // Downloading new hashes - var valueSetsItems = [CertLogic.ValueSet]() - let downloadingGroup = DispatchGroup() + let valueSetsItems = SyncArray() + let group = DispatchGroup() valueSetsHashes.forEach { valueSetHash in - downloadingGroup.enter() - if !ValueSetsDataStorage.sharedInstance.isValueSetExistWithHash(hash: valueSetHash.hash) { - getValueSets(valueSetHash: valueSetHash) { valueSet in + group.enter() + if !DataCenter.localDataManager.isValueSetExistWithHash(hash: valueSetHash.hash) { + loadValueSet(valueSetHash: valueSetHash) { valueSet, error in + guard error == nil else { + completion(nil, GatewayError.connection(error: error!)) + return + } if let valueSet = valueSet { valueSetsItems.append(valueSet) } - downloadingGroup.leave() + group.leave() } } else { - downloadingGroup.leave() + group.leave() } } - downloadingGroup.notify(queue: .main) { - completion?(valueSetsItems) - print("Finished all requests.") + group.notify(queue: .main) { + completion(valueSetsItems.resultArray, nil) + DGCLogger.logInfo("Finished all ValueSets requests.") } } } - public static func getValueSets(valueSetHash: CertLogic.ValueSetHash, completion: ((CertLogic.ValueSet?) -> Void)?) { + + static private func loadValueSet(valueSetHash: ValueSetHash, completion: @escaping ValueSetCompletionHandler) { request(["endpoints", "valuesets"], externalLink: "/\(valueSetHash.hash)", method: .get).response { - guard - case let .success(result) = $0.result, - let response = result, + guard case let .success(result) = $0.result, let response = result, let responseStr = String(data: response, encoding: .utf8) else { - completion?(nil) + completion(nil, GatewayError.parsingError) 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) - } + guard var valueSet: ValueSet = CertLogicEngine.getItem(from: responseStr) else { + completion(nil, GatewayError.encodingError) return } - completion?(nil) - } - } - static func valueSetsList(completion: (([CertLogic.ValueSet]) -> Void)? = nil) { - ValueSetsDataStorage.initialize { - completion?(ValueSetsDataStorage.sharedInstance.valueSets) + let downloadedValueSetHash = SHA256.digest(input: response as NSData) + if downloadedValueSetHash.hexString == valueSetHash.hash { + valueSet.setHash(hash: valueSetHash.hash) + completion(valueSet, nil) + } else { + completion(nil, GatewayError.signingError) + } } } + + static func loadValueSetsFromServer(completion: @escaping ValueSetsCompletion) { + getListOfValueSets { list, error in + guard error == nil else { + completion(nil, GatewayError.connection(error: error!)) + return + } + guard let valueSetsList = list else { + completion(nil, GatewayError.connection(error: error!)) + return + } - static func loadValueSetsFromServer(completion: (([CertLogic.ValueSet]) -> Void)? = nil){ - getListOfValueSets { valueSetsList in - valueSetsList.forEach { valueSet in - ValueSetsDataStorage.sharedInstance.add(valueSet: valueSet) + DataCenter.addValueSets(valueSetsList) + completion(DataCenter.valueSets, nil) + } + } + + static func loadAccessToken(_ url : URL, servicePath : String, publicKey: String, completion: @escaping TicketingCompletion) { + let json: [String: Any] = ["service": servicePath, "pubKey": publicKey] + + guard let jsonData = try? JSONSerialization.data(withJSONObject: json,options: .prettyPrinted), + let tokenData = KeyChain.load(key: SharedConstants.keyTicketingToken) else { + completion(nil, GatewayError.tokenError) + return + } + let token = String(decoding: tokenData, as: UTF8.self) + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.httpBody = jsonData + request.addValue( "1.0.0", forHTTPHeaderField: "X-Version") + request.addValue( "application/json", forHTTPHeaderField: "content-type") + request.addValue( "Bearer " + token, forHTTPHeaderField: "Authorization") + + let session = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in + guard error == nil else { + completion(nil, GatewayError.connection(error: error!)) + return + } + guard let responseData = data, let tokenJWT = String(data: responseData, encoding: .utf8), responseData.count > 0 else { + completion(nil, GatewayError.incorrectDataResponse) + return } - ValueSetsDataStorage.sharedInstance.lastFetch = Date() - ValueSetsDataStorage.sharedInstance.save() - completion?(ValueSetsDataStorage.sharedInstance.valueSets) + do { + let decodedToken = try decode(jwt: tokenJWT) + let jsonData = try JSONSerialization.data(withJSONObject: decodedToken.body) + let accessTokenResponse = try JSONDecoder().decode(AccessTokenResponse.self, from: jsonData) + + if let tokenData = tokenJWT.data(using: .utf8) { + KeyChain.save(key: SharedConstants.keyAccessToken, data: tokenData) + } + if let httpResponse = response as? HTTPURLResponse, + let xnonceData = (httpResponse.allHeaderFields["x-nonce"] as? String)?.data(using: .utf8) { + KeyChain.save(key: SharedConstants.keyXnonce, data: xnonceData) + } + completion(accessTokenResponse, nil) + + } catch { + completion(nil, GatewayError.encodingError) + DGCLogger.logError(error) + } + }) + session.resume() + } + + static func validateTicketing(url : URL, parameters : [String: String]?, completion : @escaping TicketingCompletion) { + guard let parametersData = try? JSONEncoder().encode(parameters) else { + completion(nil, GatewayError.encodingError) + return } + guard let tokenData = KeyChain.load(key: SharedConstants.keyAccessToken) else { + completion(nil, GatewayError.tokenError) + return + } + let token = String(decoding: tokenData, as: UTF8.self) + var request = URLRequest(url: url) + request.method = .post + request.httpBody = parametersData + + request.addValue( "1.0.0", forHTTPHeaderField: "X-Version") + request.addValue( "application/json", forHTTPHeaderField: "content-type") + request.addValue( "Bearer " + token, forHTTPHeaderField: "Authorization") + + let session = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in + guard error == nil else { + completion(nil,GatewayError.connection(error: error!)) + return + } + guard let responseData = data, let tokenJWT = String(data: responseData, encoding: .utf8) else { + completion(nil, GatewayError.incorrectDataResponse) + return + } + do { + let decodedToken = try decode(jwt: tokenJWT) + let jsonData = try JSONSerialization.data(withJSONObject: decodedToken.body) + let decoder = JSONDecoder() + let accessTokenResponse = try decoder.decode(AccessTokenResponse.self, from: jsonData) + completion(accessTokenResponse, nil) + + } catch { + completion(nil, GatewayError.parsingError) + DGCLogger.logError(error) + } + }) + session.resume() } } - diff --git a/DGCAWallet/Services/KeyChain.swift b/DGCAWallet/Services/KeyChain.swift new file mode 100644 index 0000000..c3d0456 --- /dev/null +++ b/DGCAWallet/Services/KeyChain.swift @@ -0,0 +1,71 @@ +// +/*- + * ---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 + */ +// +// KeyChain.swift +// DGCAWallet +// +// Created by Igor Khomiak on 18.11.2021. +// +// see https://stackoverflow.com/a/37539998/1694526 + + +import Security +import UIKit + +class KeyChain { + @discardableResult + class func save(key: String, data: Data) -> OSStatus { + let query = [ + kSecClass as String : kSecClassGenericPassword as String, + kSecAttrAccount as String : key, + kSecValueData as String : data ] as [String : Any] + + SecItemDelete(query as CFDictionary) + + return SecItemAdd(query as CFDictionary, nil) + } + + class func load(key: String) -> Data? { + let query = [ + kSecClass as String : kSecClassGenericPassword, + kSecAttrAccount as String : key, + kSecReturnData as String : kCFBooleanTrue!, + kSecMatchLimit as String : kSecMatchLimitOne ] as [String : Any] + + var dataTypeRef: AnyObject? = nil + + let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) + + if status == noErr { + return dataTypeRef as! Data? + } else { + return nil + } + } + + class func createUniqueID() -> String { + let uuid: CFUUID = CFUUIDCreate(nil) + let cfStr: CFString = CFUUIDCreateString(nil, uuid) + + let swiftString: String = cfStr as String + return swiftString + } +} diff --git a/DGCAWallet/Services/SecureBackground.swift b/DGCAWallet/Services/SecureBackground.swift index 088814c..a9b90b1 100644 --- a/DGCAWallet/Services/SecureBackground.swift +++ b/DGCAWallet/Services/SecureBackground.swift @@ -25,7 +25,6 @@ // Created by Yannick Spreen on 4/27/21. // -import Foundation import UIKit import LocalAuthentication import SwiftDGC @@ -35,15 +34,12 @@ struct SecureBackground { public static var image: UIImage? public static func enable() { - guard !paused else { - return - } + guard !paused else { return } + disable() - guard let image = image else { - return - } + guard let image = image else { return } let imageView = UIImageView(image: image) - UIApplication.shared.windows[0].addSubview(imageView) + UIApplication.shared.windows.first?.addSubview(imageView) Self.imageView = imageView Self.activation = Date() } @@ -51,9 +47,7 @@ struct SecureBackground { public static func disable() { if imageView != nil { if activation.timeIntervalSinceNow < -1 { - ( - UIApplication.shared.windows[0].rootViewController as? UINavigationController - )?.popToRootViewController(animated: false) + (UIApplication.shared.windows.first?.rootViewController as? UINavigationController)?.popToRootViewController(animated: false) } imageView?.removeFromSuperview() imageView = nil @@ -62,14 +56,14 @@ struct SecureBackground { static var paused = false static var activation = Date() + public static func checkId(from controller: UIViewController? = nil, completion: ((Bool) -> Void)?) { - guard !paused else { - return - } + guard !paused else { return } + paused = true let context = LAContext() - context.localizedCancelTitle = l10n("auth.later") - let reason = l10n("auth.confirm") + context.localizedCancelTitle = "Try Later".localized + let reason = "Could not verify device ownership".localized context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason ) { success, err in if success { paused = false @@ -80,9 +74,11 @@ struct SecureBackground { completion?(false) return } + DispatchQueue.main.async { - controller?.showAlert(title: l10n("auth.confirm"), subtitle: l10n("auth.error")) { _ in - completion?(false) + controller?.showAlert(title: "Could not verify device ownership".localized, + subtitle: "Please try setting a passcode for this device before opening the app.".localized) { _ in + completion?(false) } } } diff --git a/DGCAWallet/Storyboards/Base.lproj/CertificateViewer.storyboard b/DGCAWallet/Storyboards/Base.lproj/CertificateViewer.storyboard index adf2f76..65940f7 100644 --- a/DGCAWallet/Storyboards/Base.lproj/CertificateViewer.storyboard +++ b/DGCAWallet/Storyboards/Base.lproj/CertificateViewer.storyboard @@ -1,81 +1,58 @@ - - + + - + - + - + - + - - + + - + - - + - + - - - - - - - - - + + + + + + + + + + + - - - + + + - - - + + - - + + + + + + - - + - - - + + + + + + + + - - + @@ -157,30 +221,479 @@ - - + + + + + + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -191,7 +704,7 @@ @@ -216,38 +729,37 @@ - + - + - + - - + - - + + - + @@ -261,14 +773,21 @@ - + + + + + @@ -279,6 +798,8 @@ + + @@ -292,31 +813,57 @@ + - + + + + + + + + + + - + + + + + - + - + - + + + + + + + + + + + + + diff --git a/DGCAWallet/Storyboards/Base.lproj/LaunchScreen.storyboard b/DGCAWallet/Storyboards/Base.lproj/LaunchScreen.storyboard index e92744c..4aa6843 100644 --- a/DGCAWallet/Storyboards/Base.lproj/LaunchScreen.storyboard +++ b/DGCAWallet/Storyboards/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + diff --git a/DGCAWallet/Storyboards/Base.lproj/Main.storyboard b/DGCAWallet/Storyboards/Base.lproj/Main.storyboard index e27f4a3..9fb28ad 100644 --- a/DGCAWallet/Storyboards/Base.lproj/Main.storyboard +++ b/DGCAWallet/Storyboards/Base.lproj/Main.storyboard @@ -1,21 +1,21 @@ - - + + - + - + - + - + @@ -23,65 +23,834 @@ - + - - + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - - + + - + - - + + - - - + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + + - + - - - - - - + - - - - - - - + + + + + + + + + - - + + - + - - + - - + + - - + + - - - - - - - - - + + + + + + + + - - + + - + - - + - - - - + + + - + + + + + + + + + + @@ -272,6 +1072,7 @@ + @@ -288,57 +1089,83 @@ + - + + + + + + + + + + + + + - + - + - + - + - + - + - + - + + + - + + + + @@ -347,46 +1174,73 @@ + + - + + + + - + - + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + - - + + diff --git a/DGCAWallet/Storyboards/Base.lproj/Settings.storyboard b/DGCAWallet/Storyboards/Base.lproj/Settings.storyboard index 6143c85..4bf1f8e 100644 --- a/DGCAWallet/Storyboards/Base.lproj/Settings.storyboard +++ b/DGCAWallet/Storyboards/Base.lproj/Settings.storyboard @@ -1,9 +1,9 @@ - + - + @@ -13,7 +13,7 @@ - + @@ -21,8 +21,8 @@ - - + + @@ -31,7 +31,7 @@ @@ -44,8 +44,8 @@ - - + + @@ -57,7 +57,7 @@ - + @@ -69,16 +69,56 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -86,7 +126,7 @@ @@ -99,9 +139,6 @@ - - - @@ -112,72 +149,30 @@ - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - + - + - + - + @@ -195,8 +190,9 @@ - - + + + + + + + + @@ -222,12 +225,12 @@ - + - + - + @@ -242,9 +245,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + diff --git a/DGCAWallet/Storyboards/Licenses.storyboard b/DGCAWallet/Storyboards/Licenses.storyboard deleted file mode 100644 index 8dedba2..0000000 --- a/DGCAWallet/Storyboards/Licenses.storyboard +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DGCAWallet/Storyboards/de.lproj/LaunchScreen.strings b/DGCAWallet/Storyboards/de.lproj/LaunchScreen.strings new file mode 100644 index 0000000..6dbe573 --- /dev/null +++ b/DGCAWallet/Storyboards/de.lproj/LaunchScreen.strings @@ -0,0 +1,3 @@ + +/* Class = "UILabel"; text = "Wallet App"; ObjectID = "JUh-sx-2bS"; */ +"JUh-sx-2bS.text" = "Wallet-App"; diff --git a/DGCAWallet/Storyboards/en.lproj/LaunchScreen.strings b/DGCAWallet/Storyboards/en.lproj/LaunchScreen.strings new file mode 100644 index 0000000..2e6a491 --- /dev/null +++ b/DGCAWallet/Storyboards/en.lproj/LaunchScreen.strings @@ -0,0 +1,3 @@ + +/* Class = "UILabel"; text = "Wallet App"; ObjectID = "JUh-sx-2bS"; */ +"JUh-sx-2bS.text" = "Wallet App"; diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/disabledText.colorset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/disabledText.colorset/Contents.json similarity index 100% rename from DGCAWallet/SupportingFiles/Assets.xcassets/disabledText.colorset/Contents.json rename to DGCAWallet/SupportingFiles/Assets.xcassets/Colors/disabledText.colorset/Contents.json diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/black.colorset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletBlack.colorset/Contents.json similarity index 100% rename from DGCAWallet/SupportingFiles/Assets.xcassets/black.colorset/Contents.json rename to DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletBlack.colorset/Contents.json diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/blue.colorset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletBlue.colorset/Contents.json similarity index 100% rename from DGCAWallet/SupportingFiles/Assets.xcassets/blue.colorset/Contents.json rename to DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletBlue.colorset/Contents.json diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/darkGrey.colorset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletDarkGray.colorset/Contents.json similarity index 100% rename from DGCAWallet/SupportingFiles/Assets.xcassets/darkGrey.colorset/Contents.json rename to DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletDarkGray.colorset/Contents.json diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/grey10.colorset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletGray10.colorset/Contents.json similarity index 100% rename from DGCAWallet/SupportingFiles/Assets.xcassets/grey10.colorset/Contents.json rename to DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletGray10.colorset/Contents.json diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/green.colorset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletGreen.colorset/Contents.json similarity index 100% rename from DGCAWallet/SupportingFiles/Assets.xcassets/green.colorset/Contents.json rename to DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletGreen.colorset/Contents.json diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletLightBlue.colorset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletLightBlue.colorset/Contents.json new file mode 100644 index 0000000..455ad79 --- /dev/null +++ b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletLightBlue.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "230", + "green" : "192", + "red" : "177" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.901", + "green" : "0.754", + "red" : "0.694" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletLightGreen.colorset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletLightGreen.colorset/Contents.json new file mode 100644 index 0000000..5fd0fad --- /dev/null +++ b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletLightGreen.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.639", + "green" : "0.854", + "red" : "0.727" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.639", + "green" : "0.854", + "red" : "0.727" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletLightYellow.colorset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletLightYellow.colorset/Contents.json new file mode 100644 index 0000000..ff6b5c3 --- /dev/null +++ b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletLightYellow.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "179", + "green" : "244", + "red" : "249" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.703", + "green" : "0.957", + "red" : "0.977" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/red.colorset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletRed.colorset/Contents.json similarity index 100% rename from DGCAWallet/SupportingFiles/Assets.xcassets/red.colorset/Contents.json rename to DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletRed.colorset/Contents.json diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/yellow.colorset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletYellow.colorset/Contents.json similarity index 100% rename from DGCAWallet/SupportingFiles/Assets.xcassets/yellow.colorset/Contents.json rename to DGCAWallet/SupportingFiles/Assets.xcassets/Colors/walletYellow.colorset/Contents.json diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_invalid.imageset/Contents.json b/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_invalid.imageset/Contents.json new file mode 100644 index 0000000..527f406 --- /dev/null +++ b/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_invalid.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_large_invalid.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_invalid.imageset/icon_large_invalid.png b/DGCAWallet/SupportingFiles/Assets.xcassets/icon_large_invalid.imageset/icon_large_invalid.png new file mode 100644 index 0000000000000000000000000000000000000000..6b5d7ceed0b543344c856f439cb9d7c0a3c60331 GIT binary patch literal 11002 zcmZvC2T)VZ7cWW|L5iRtAiWbhNS5#=}mt*fpB>O5s8EuJCY_x|!;+`y%k8I`DYREwK; zWL*0^Vw$1XqdZ16)`Y9DA7qP<1`D158)aWa-r2o!g|1|P>xaTI%m^eMIgQ(#4moif#IHuph!4HT|iloBKwN5`G# z>xmk{PeuUxhnZbF*GYkv^)iibu!#0ZO8TLiw@XY3-sYLRj@L3wS`@*rhtv>61PO$| zMi(Ks9@eID&g?aPA+R@$*rf~vm3B8E+SLIvg@y7ZriI=`CH}8F-pHGnx~JBIAVg%e zVOns?cLs2Yfm77HuBx$TtB?yy|F(P%$5zjsnX5PD-%LFz2>z~WVjsCy6p`g_wv+B~ z5>6Df&Q(P`jxO3H6yGP|5ga*$a&p%yF?K1l8O~z1goJ+YJwgS|kUi5AK)@sspl|san2WLY-6vHYuBnXO z{FhnL*U+1_SZE$q0cUVu>KB4iDYgd}DoQ#|G+*%Qc4F`#*uq2@t_i{O(a}^JJ6-{U z4#lw}+62*DU@}@%KE(I!m_M~bT;vnGM~th)P8N9)OV1_^hSR>oFNDws&{)`!>3GY# z&s)EKdN2eK#1B>B`IgZ|$vo5Z-_#R%HUvZOf=0!mF{py?zo_#S+5?Ss#YJI^m9+8RFULq2gYz+6Td^L$sG?n`tTYyZCk%|@P$CB z4YUJANJ#bOAyfxf(A1D}ZY<@dc2OKG@cTH>k zwHOAaCu=1t4v!B+Co&k&%afVls}1+}+Mb%GGPl@p{W$l-SJ8-=dnN()r5ire`n3D$ z2TGM~f|*Clq=QcHYE=nO`JCzr)p2|G(?(psKZ)7g=6lEkTl^i|lSEtBNoV}=%Y*(i z9Nj}Ok4@z?gAQk_kXPwVR{Eb6y||d9RU+N~I8LnA{~zP9mRK5-SH|TZHa`(?Y7*Lb ztCJp%ITk z{Px?9rU7)j{sw*@R!GrgIvxZAVXHfY`Kx7|bAPP_)W z&A1V6S@->@)o;F^fFX(6su|YIQ!Fzb)EC66y0smXf$?m5AKki)^biPZ+LVo_Z1}_ z+I9FfEX&rtSn7L=m{%!<>fqHlnA}Yyu%AA0XUq`$0kpc`<5B5{7?kp;$^IBg+ z6EqSt#l<%)=3XzUF7&ns@;JS{3BuGj<#_(6RP$_A9aKtZeaj5OlmeV}5REy{+%z0I3J$pHrN^HFzRNog;t5 zfDV5&?j8b~d|ci8XEZ*2jz;3@5%AFjV7~ls!F>*%<{M|alDg@{Z-9MhojcZ|AOje8#a!i0X6{gU`GCL*6BdE_$DME8=6?)bEj5 zhWXooxI(h3TITLfpC=4cE&o~5g&GBJxhR6ac;2N1Ru+^Q{aG!Coh2t9sQ8$(j3hA! z1uJc)pHu}RLU?{~56OT2DsKKh0s`lV=*8@M5%*TSX#qwRWKy>MwpDuT5oawLXe(EA zu#UG`@h*xy>c*qGPi*#In(qb~U2gq$rV*{1(f)YRk>U}E@Z^!EbIh5$#}4AUm+?|E z`_I^vxt9U3934bHc7`JA>T9zW4?)L_vy^m{sD!yoO#Q`&0qGEin>S^Gf^Ew!x|#Ty z{c-uv*0JR85clJs34xouwOiN~jhdN(sjb&9bEG$MvyG>yatmK8t<$niazq}9- za5B~oVW7Nvs)f&lG?dXqGf0R=W65DJc}V|c@X>&Qu3N6}U8|TL*Qm3}@SA!sUn^bK z;-x{oWFn7G5uLSS%hGaj zRqC7@ZholvVb3smLO^C5*FjN@XQEQrDkG%7$y%B3! ziyx~U>$pS59IqU#n*j4ETc|m%W`2?NMC9ijvXKroa^t=KvO>MkIYd)P`J@)4QI-8- zYnr&3TR_q-k#tnaB8}Fj2jTVoXJ1&TmICHmeRe{(3~>BDB-2XW9x(-_pPgmK zX^3E=R%=SvpO|C5={E^fW|eHB)7Y~6h;}PVl`oy9VEc}vsn)x4q8Sf~ur2+k+xU~A z0I~G&S$Hr}FfGEizA``I;_(R2yC)Kah0jUG7u(XZ}ok|;-1$ov|om-{YFnEA#KIHGFP0aDIgEJfr`)a4;;gkw* z|0szrM9!JA^o2LE<^*rkXoR!L3Ak3DeGS}pWI+<&K;9pHr(Ozu8M}Z=dGg31l)><$ z(bKOrrl9s;ppYUy)a9@JogB0JDc6+#O=7tC$|nSl9AX_yj%Zk}%%{GnB4RZU+K~RVs41?9K%%#b(B_Xo-Cx&9#-%pHe6GsO z+}q=;CDfCaimt}{&sC_*MO&;V`OFJ$-OR+=<2Rd+d`Johd!BbzgcLW3z1-)v?+gJ^*zK9-qHtLZDPQ9OyoNpQc&wC^|ael)*{ON{XvZ7sAiLfgI7~0 z!I&`qX!*N#_iOts7TDepJ4>x7RMz8eoo((AOv`@VpkB%zujwIBUPB9AN3E9_S}M!l z??z2tZ?7<4xjL0saWes{tM_o|0%<6)8n@pmTVn&ao^gUX)hSiCbkF@V-770pp_jGjCI3+LG*M64tYMev{>S z(e3nP$0pl^a4FMOqBd=HQvk(dF&@;A%dgG0M`$6vpxrkO1Kb@g&i)cdg3M4 zV@(`P?KUH3A>qYqq6dVlVR$p{HsKlWDTP7XM)2+C5#8CX?&t&|0_n>_-j{CATYHFOSW^TnzKgI1&O#BctE`o_Tul0Umm z*0jp-sq8`OC#TyeHCJGj9JN1{JX%?`PM6rWG{jpLWrq7tP#N1uFfvVg4;P zuAM3C1c2 zMC3wJdFqD1Va*FJ&l?RDO>Q<}o%9sISnbTeo>qKoxh}DwzsEz^s$cQ2kMKZ?1iB*U zPnc?N;-)xtjo3hT$~Hjox`#?Z8!38X72!cAt${A840P{Fqz3K!_4n#;5iQTyh*EoN)lL`5s#da3Wte1oS7(D8cFCW~xT%vqOohCg0jXthX~qgiC4 z0yw$0_qSEER6QS$8qT0@XS4(!k`HvXa_G9Lr-i40u^LYZ93A3eBZCC2ekpEU$ z>3 zYMVY<&OcB`22()C;$?TIs*c4xF)%gt_LwKT&gw|m!>Q3!9@3ogD+~b_$m5z9^z*)h zB~2os2Ir=s zaz#r$$q>!jRWtGEntiUEs#e1Se#LlSx4l^20*Lv{ohG}k50o@H1GUYgLgEi7+*M<5 zrM8)tz++qus%|~L_-e7>6V#A@3VeXtP`0S~Te96=K@a&V+Le(DnqEaUKBxGmZbH<< zo~K2eq9W?i*eyfin+iJ)I3rZJ^Kd*#Yc2&I9h*15$R6CQbnkitSu}TuPTcgnYStUJ z%YlM4C?CxMl|ZeE1#O8~tg!M&s)GKSXZw<5QNI`|RPMzqAIC6ksSKG=PqeoUNM57j8)p& zH|+zC5sWsp!(U9XwxGD0WxQg?{uO2Vk3|hf&w4aPoC8f?w(M^2Vh(8gDZkQ>LcL zc&smQ685dc6cqNv&%Z;+pdCq!bimm(44+9K=Oeh+=qSKT95wz{sf7u_rB`V~rntUBE>CmQEY<-9AU>VS-^V@-wp%sNk_itqQ zzKY#?e=>w+v52QX_{BtV3Ho0cI=m+}Gc<7iaK#9+UhY7jbBdXE(70HlfOaQ2%pwlE zj=0^nU!HoUXH9KnU9mqm@$}mc0jL#qor>_^%94Df0z3z9$bD;9&CVBWiS^s|rHu|R zxN;Bi)99A69X67r{Ll!(x7J>$@OrqB5p*dD9ru;p%YzxVf3YaPGV$pT0rz#NPe3p^ z@8PAf_Fd(jcy0;HD(j&1V&nVe^UeYXm0CP_Aa8%gkPe-SS}l>4 zYO$?Xet7n;!OHtFLdlhB@LrbjlQXsPP|N49rro9$3z8d-s1)28%uPOzJ;b({_hhIg zmNjjb0%|lNw@pB+$P`hAfQ;Q*#<&^~)p{|C)>}HaWOct8(4iSZcV2QlXPCR$gEOUo zRy=(AHJM?{gll)xsJ%NdZ+f0HWYI=2(d%cWH=l~#?OQLj^4gAL!>EocP4E0i!Lofa zPRUjG)I)%7mjma~Jiqm3$)e(3mH5<)&=*NQE*bQ+fD2)vz+8J0O!AwFk-*DkSv#TE zQmy5@Wf@s!zv4H(qErlmbcPzj+-cD7wHoQbl`EyXA>kR>277pq+Mk_UFHb!@v!A;6 zv4+qS{>}os-vM}@imN4D1XLF8cX(V-FnE;&SuYIO-bj67=K0SXXEqR=VU_3(_`M2h z&ATofjF{jT2LR6acCU*iQtXb|&h%PU0@bG4_oq5|WI{`SSEA)eG$H&y81dOg`V70a zqEOu@s6zh5bc6E%eZO4-?3}Ar&-y@dRl)-5roh7it<>{uR&s8YyxX}`LDc@ww@#@8 zzLgwp7zaz3s%+Azo-a)H;C@u|-^2r8@AvP<@~wY6>E{WB^|$;kSg5>hrmY_bI}d`q zhIax9%WX=`bmS9FeSMroNAtq|n)Tl&xD=0r2R(rr8SM6W<*)I8_6gLOm?qaJ^ox+b zQETqjv@BTuv{I=k7H?eyvE-c3jGoqq=+mKvx{!oebgFHA&2IsOS9EG}SpIaRNVdg7 zsQ*_HrLFWTGx#PT&YD9CzL5 z>@%(GccW6czk!MeVXgr-IzV?(T*q5N5e))Cg_Q&C5{`V_1bH|_XFbsTsq^yt=muF& zOv6Z|om7ghGbhLZuQJ!x{2-~yF#{<>+dig&M&j514JJ-zKJO+a*XP#1CGKC?;R1a| zBKn%Y914rhQKg#&Du;dFKqkQ0eYkuxT zm6)@s%1O8~dzvE?@uJ>ft^jGmLR=O*>6=HRQ;O;hYvfD#dmCuof`*~EACT_rh}HO=hy2R zpam=pZq|fK`UAEEg|c2;-g|uN?oTn{=e2>FjWpn#Q}d(DD-!^gJKuXOrnnp)2EP*-uc z6U%yV_SMLruvJxv)kMPZ3X!$SuxP@Fg?Pab1 ziF0My--ClCWv8E1m;Ui`I-t;A;jVHNC?{wi=lzgMTV@x*EJKl6gpZYa)n%aleYg!@ zUFhL-kP~Ux*sD*nY2wz78A)NM^?Z1Lk0)Pzd7%wC&!RBD6RS4=sAjK_Bi<3G*)^(1 zfBW!g_x*S#Q&}E3ar5P`5RfEOYYzi8XG6>VErRLIdaP;2GAYp`=)HpM=z;6MQsY_opEX~>bL1$lUPek~+R3UCHEK~g7NmO$Wkf#KeO3RrhU)6k|G>8Y z5B~OlyBbcoti&{#O8{uj#9<`;01pw96yKv8BZo!Cy7}y+p3hu^c#Q<9M5$A0#>0;y z7L(5jD3>aQSb1-(dfpGBq+3usQ^=4Fi}rJ$=T=8FHw9wCk1gF6r*DPmtT_LDXG5=- zRJjoLm{z*(BbVE=t$c?U_o?8SW{xBKHFR{LQPGZ{S^sX-gi&c#29b=%lEq&!TGks& z;E`CK+xJMRa(_0bF`2=~{@ zm$&yud?$lxrIH`&-Jam4;2MPR@$a1ts4Xh|E{7b&W1SeaXrJ$in3oLgH&Yl>AHGD_ zLDp(K&jdrya|%PeOEv9IwH^?$F*BQIv{$3#dwougDi)IkjCI%!Vi zq8;_LsD>+R`{n<$_I1_X| zOFFpmOn-;D(SLeJ{zbxPlgAUfxBXU>R?f8ko(ta*T^1)DQ-U>olCszvhX<7LH^d%! zCI6lX0WZxI=uM5TSKT#KOWT$Ox$3u3jq<`dtTctLckCzo!Y3g|&iwt!(S{wz4G_K! zX4NLuPHrl;6bDP>{f+XytU|T*@+L^J>|UHqvO^wgTlkQ=ak})*dxpQ;@!V@o1?N?A=2DA%l8>P9`Wytw{}fe-$S~+dCjZ2k5hiE(`ZrMR$AU zxdw1#^RlrOaj^Z@aNJmPybmmjvUE>I*#dWXJAku1<60dvb_Lnt$@00A&D=R!?Am^&^W*14#D`(-#GGj+g$n4xgyDQ*1V9h zYfK}-#O!mLWT>{QsM?*Dn}8KgBRj^&lz~}u68OoYuD>`ABXwrh?Z|-7gHrB&D$87o zgzz1Mzmzh9=$-pwcmE8#&u+fkyHL!dLBU&NV`W-$+%4D~VNs#4!u2H0=kqmy0We6m`qF&MSw&(_#k`ucg>zQJ@({UX;nC z75+-GN3xF|ugNkZ=W#m9jHZGP6{D*s($d1(m+Q%ybz!gaA#R(Ab5DF#Wt39xvbi^i z0i2Fxl+mVPP9iS8woQ1EgfyT)ow9c?Da)zCz(GS4Oa-V>JgfrV4(~Fq^|%#Lc9l}S_#AY9 zO-Cx(*=h44-^>0h8@r$VNUWVbC6s#klT$bao_1wHKTfX1IAW@+IOsfF6B`&9WIyiH5=Q)7eDrQcVcB^V^nm;Dk-mN_zp8P&-`Y(P#tffRg1eez7NjXXx@df> zh_-ZR%C=J3EAf`hD`O+XPfT?YyA9sHnJ2o4%@$RoiQ}by#tDLh90w4AHVMTQi=nR< zy;nIogpNC;+^yn-#5E$eO9b3|OwpH1H@KzBAWgu%l8)myA04X)O7Q z6)Rg?g}A=v;$S{g`G+UJIi8n6d7 zPbrX02($u@8fieCSX5sJA--r1AWtXM5CKLJbwh(u*oyv~qI2sOXu@}zRpM2*9)qo* z_HWQC8K@7Zm?Z-}tp+#5^AS#{Iyn^F%z$?snDZou2+lvs}C(c$HqwY2BYIqL3; zJ+VEqJwu)9t zZkH;+2jxIRSorwrP8me%Pa=bSMV3Tqy+$z9e#MtD_b($kHpLW;%E6SU0--&bZbdb zByZQzWIYNi+>5JR>ZQ%*NcsH4x^DN{$RM^Ne0)3OoQh2jP?S&}l6&$_Kz}a!Pr=z2 zV(2Jc1rq<|-G_eqM1uY@4N4Xc{`x`&m){5)!5w8eAnL&1qdU$5y6O&uLitJZrR^87 z=s%2(u+SkEHzauf-3K`OP%%`H)C^No6nhPAYXKbM*zFUd5x6kK(pR;{vs2fO7Cia} z^w(r3yZ+m?Tc9l_-Cqd5d8O7Pr=?TxL(_3x{-WCoUU?`_vjqABWggj8Trmc@Y@pHx z+FDx2U?&@KGE#`6lq>e1aeT8ul=}F!w}x9wx-BW6TDPe}MM(0xrA0~=_BbI=v)EgG zoXjtdf6s-0@UQR9ZgduR-aNhodcM>u#CaHs|IC+=9Qs9grQ?gwpLV%WF_Qc%W2wSf zVd9gRw_k`#C1XZ^RtxW-jx2@;ouCy!g~SeP_<}q*c2i#IabM{t%MV$Qt?7ToSBsr2 zW3Vrp^|p$U96DU!&Ea`ocUI99K3!~7u(&T&Pk9*+6P~J`cI$|T&zfsq58H9$BD4X% zc%!3Wy#+g|IS|z0^{C#Xh0^521UsuGj0rM`cuxVC-hJFF26Squ4?J6_^c`@|4Wac< zB1lc?dne@VQ21Z@NdfZPJt#Fs{OM?quIiKy-R?B3pmPGMMp1)&Kt;O7v5<E)GG#)ZUiS(D>vyARV*>VL-fzJ=?wTN z*T>y629)Kz24zoXD(UZhwdTv1`rgjqcDZ9-H=K8aQiz1~a(|F!`^WVkbTAOTuvmo=!%FrN^Dee>4obp`Y^nn0zH{X=-8z;S#7kv}(b032`HXv&Z z3#^8TZa~2@++VNaPTUURW*M-RLdXuRZP|oSHsz&!o9jRIK(T{*iL%P&X3f8<<1eLU zDC$4B1psbE$)T(HgyY4wNJW$7a&=0KP{#N!fTcI{aZcsmA`hRa?7e}y=9YQBbhF{# z$fQI6jb+7HMSdW{GD@auA*dp#{gvJnYaPUNJi18Rr28&Va;(+Xii!|U8jiFtJm?Zf z2?QYYH*ezqwVM72ZF3vfaYf0vKk%f!H+!Gr+IQOpbE0oZV5C$X{*fB!G;z%%ejzqt zR}|bZNOL2HzF%r18H~ryUhX|G-Rfh##AvMBhc{^Muy0ztfB--JC5QVCUw5?#Rsb%D z3L>?vkj@TB7qC-+3-BP5m6nx}l#!N{mNApL2bNa=%iI%}mIF&mKTmT#`hN(#eVyH1 nL;pu0tsp5QXC|WvR!{(b|0A$EF8=rODw(d9p=RBK$I<@>V-{@; literal 0 HcmV?d00001 diff --git a/DGCAWallet/SupportingFiles/Info.plist b/DGCAWallet/SupportingFiles/Info.plist index a8d53f4..cd95413 100644 --- a/DGCAWallet/SupportingFiles/Info.plist +++ b/DGCAWallet/SupportingFiles/Info.plist @@ -59,6 +59,8 @@ armv7 + UIStatusBarStyle + UIStatusBarStyleLightContent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -67,8 +69,6 @@ UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight UIUserInterfaceStyle Light diff --git a/DGCAWallet/SupportingFiles/OpenSourceNotices.json b/DGCAWallet/SupportingFiles/OpenSourceNotices.json index a551754..fec9884 100644 --- a/DGCAWallet/SupportingFiles/OpenSourceNotices.json +++ b/DGCAWallet/SupportingFiles/OpenSourceNotices.json @@ -4,10 +4,6 @@ "name": "Alamofire", "licenseUrl": "https://raw.githubusercontent.com/Alamofire/Alamofire/master/LICENSE" }, - { - "name": "Floating Panel", - "licenseUrl": "https://raw.githubusercontent.com/scenee/FloatingPanel/master/LICENSE" - }, { "name": "JSON Schema", "licenseUrl": "https://raw.githubusercontent.com/eu-digital-green-certificates/JSONSchema.swift/master/LICENSE" diff --git a/DGCAWallet/Components/SelfSizedTableView.swift b/DGCAWallet/SupportingFiles/de.lproj/InfoPlist.strings similarity index 54% rename from DGCAWallet/Components/SelfSizedTableView.swift rename to DGCAWallet/SupportingFiles/de.lproj/InfoPlist.strings index 6cf62cd..d39af2a 100644 --- a/DGCAWallet/Components/SelfSizedTableView.swift +++ b/DGCAWallet/SupportingFiles/de.lproj/InfoPlist.strings @@ -16,29 +16,21 @@ * See the License for the specific language governing permissions and * limitations under the License. * ---license-end - */ -// -// SelfSizedTableView.swift -// DGCAWallet -// -// Created by Yannick Spreen on 4/20/21. -// -// https://dushyant37.medium.com/swift-4-recipe-self-sizing-table-view-2635ac3df8ab -// -import UIKit + InfoPlist.strings + DGCAWallet -class SelfSizedTableView: UITableView { - var maxHeight: CGFloat = UIScreen.main.bounds.size.height + Created by Igor Khomiak on 22.11.2021. + +*/ - override func reloadData() { - super.reloadData() - self.invalidateIntrinsicContentSize() - self.layoutIfNeeded() - } - - override var intrinsicContentSize: CGSize { - let height = min(contentSize.height, maxHeight) - return CGSize(width: contentSize.width, height: height) - } -} +//Bundle name +"CFBundleDisplayName" = "Wallet-App"; +//Privacy - NFC Scan Usage Description +"NFCReaderUsageDescription" = "Sie können Zertifikate per NFC importieren."; +//Privacy - Camera Usage Description +"NSCameraUsageDescription" = "Sie können Barcodes und Bilder mit Ihrer Kamera scannen."; +//Privacy - Face ID Usage Description +"NSFaceIDUsageDescription" = "Sie verwenden FaceID, um sensible Daten zu entsperren."; +//Privacy - Photo Library Usage Description +"NSPhotoLibraryUsageDescription" = "Sie können Fotos aus der Fotobibliothek importieren."; diff --git a/DGCAWallet/SupportingFiles/de.lproj/Localizable.strings b/DGCAWallet/SupportingFiles/de.lproj/Localizable.strings new file mode 100644 index 0000000..b0879a9 --- /dev/null +++ b/DGCAWallet/SupportingFiles/de.lproj/Localizable.strings @@ -0,0 +1,166 @@ +/*- + * ---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 + + Localizable.strings + DGCAWallet + + Created by Yannick Spreen on 5/6/21. + +*/ + +"Wallet App" = "Wallet-App"; +"Scanned %@" = "Gescannt %@"; +"Save" = "Speichern"; +"Done" = "Fertig"; +"Edit" = "Bearbeiten"; +"Confirm" = "Bestätigen"; +"Confirm TAN" = "TAN bestätigen"; +"Please enter the TAN that was provided together with your certificate:" = "Bitte geben Sie die TAN ein, die Sie zusammen mit Ihrem Zertifikat erhalten haben:"; + +"Certificate saved successfully" = "Zertifikat erfolgreich gespeichert"; +"Now it is available in the wallet" = "Jetzt in der App verfügbar"; +"Cannot save the Certificate" = "Zertifikat konnte nicht gespeichert werden"; +"TAN: %@" = "TAN: %@"; +"Could not verify device ownership" = "Eigentümerschaft des Geräts konnte nicht verifiziert werden."; +"Try Later" = "Versuchen Sie es später."; +"Please try setting a passcode for this device before opening the app." = "Bitte versuchen Sie, einen Passcode für dieses Gerät festzulegen, bevor Sie die App öffnen."; +"Delete Certificate" = "Zertifikat löschen"; +"cert.delete.body" = "Sind Sie sicher, dass Sie dieses Zertifikat löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden."; +"tap to reveal" = "Zum Anzeigen tippen"; + +"Check Validity" = "Gültigkeit prüfen"; +"I Agree, check validity" = "Ich stimme zu, Gültigkeit prüfen"; +"Failed for %@ (see settings)" = "Fehler bei %@ (siehe Einstellungen)"; +"Passed for %@ (see settings)" = "Genehmigt für %@ (siehe Einstellungen)"; +"Open for %@ (see settings)" = "Offen für %@ (siehe Einstellungen)"; + +"Failed" = "Fehlgeschlagen"; +"Passed" = "Genehmigt"; +"Open" = "Offen"; +"Rule" = "Regel"; +"Current" = "Aktuell"; +"Result" = "Ergebnis"; + +"disclaimer_text" = "Diese Prüfung gibt Aufschluss über die Reiseberechtigung auf der Grundlage Ihrer Bescheinigung. Einschränkungen der Freizügigkeit als Reaktion auf die Coronavirus-Pandemie können sich ändern und basieren möglicherweise auf zusätzlichen Informationen, die in diesem Antrag nicht verfügbar sind. Weitere Informationen finden Sie in den betreffenden nationalen Quellen und auf der Website von Re-open EU: https://reopen.europa.eu/. Ihre Daten werden lokal auf diesem Gerät verarbeitet."; + +"Disclaimer" = "Haftungsausschluss"; +"Your destination country" = "Ihr Zielland"; +"Check the date" = "Überprüfen Sie das Datum"; +"Valid certificate" = "Gültiges Zertifikat"; +"Your certificate is valid and confirms..." = "Ihr Zertifikat ist gültig und entspricht den bereitgestellten Länderregeln. Möglicherweise gelten zusätzliche Zulassungsbedingungen, die Sie auf der Website von Re-open EU nachlesen können: https://reopen.europa.eu/"; + +"Invalid certificate" = "Ungültiges Zertifikat"; +"Your certificate did not allows you to enter the chosen country" = "Ihr Zertifikat erlaubt Ihnen nicht die Einreise in das gewählte Land."; +"Certificate has limitation" = "Das Zertifikat hat Einschränkungen."; +"Your certificate is valid but has the following restrictions:" = "Ihr Zertifikat ist gültig, unterliegt jedoch den folgenden Einschränkungen:"; +"Validating certificate with country rules" = "Zertifikat mit Länderregeln validieren"; + +"This check gives an indication on eligibility..." = "Diese Prüfung gibt einen Hinweis auf die Reiseberechtigung anhand Ihres Zertifikates. Einschränkungen der Reisefreiheit in Bezug auf die Coronavirus-Pandemie können sich ändern und basieren möglicherweise auf zusätzlichen Informationen, die in dieser Prüfung nicht verfügbar sind."; +"Issuer Country" = "Ausstellerland"; +"Type of Test" = "Art des Tests"; +"Name" = "Name"; +"Save image" = "Bild speichern"; +"Please enter the image name" = "Bitte geben Sie den Namen ein"; +"filename" = "Dateiname"; + +"Save PDF file" = "PDF-Datei speichern"; +"Please enter the pdf file name" = "Bitte geben Sie den Namen der pdf-Datei ein"; +"Share QR Code?" = "QR-Code teilen?"; +"Do you want to share DCC certificate via image or PDF file?" = "Möchten Sie das DCC-Zertifikat als Bild- oder PDF-Datei teilen?"; +"Export as Image" = "Als Bild exportieren"; +"Export as PDF" = "PDF Export"; +"Cancel" = "Abbrechen"; +"Add new?" = "Neu hinzufügen?"; +"Scan certificate" = "Zertifikat scannen"; +"Image import" = "Bildimport"; +"PDF Import" = "PDF-Import"; +"NFC Import" = "NFC-Import"; +"Cannot read NFC" = "NFC nicht kann nicht gelesen werden"; +"An error occurred while reading NFC" = "Beim Lesen von NFC ist ein Fehler aufgetreten"; +"Retry" = "Wiederholen"; +"OK" = "OK"; +"Images" = "Bilder"; +"PDF files" = "PDF-Dateien"; +"Get Image from" = "Bild laden von"; +"Next" = "Weiter"; + +"Camera" = "Kamera"; +"Gallery" = "Galerie"; +"Cannot scan" = "Scannen nicht möglich."; +"You don't have a camera." = "Sie haben keine Kamera."; +"Services" = "Dienstleistungen"; +"Certificates" = "Zertifikate"; +"Please select the required service" = "Bitte wählen Sie den gewünschten Service aus"; +"Each service is located on a separate server and is designed for specific activities." = "Jeder Dienst befindet sich auf einem separaten Server und ist für bestimmte Aktivitäten konzipiert."; + +"Please select a certificate" = "Bitte wählen Sie ein Zertifikat aus"; +"Here are all the appropriate certificates." = "Hier sind alle passenden Zertifikate."; + +"Close" = "Schließen"; +"Last Updated: %@" = "Letzte Aktualisierung: %@"; +"Consent" = "Zustimmen"; +"Do you agree to share the %@ certificate with %@?" = "Sind Sie damit einverstanden, das %@-Zertifikat mit %@ zu teilen?"; + +"Your certificate is not valid. Please refer to the Re-open EU website:" = "Ihre Bescheinigung ist nicht gültig. Bitte besuchen Sie die Re-open EU Website: https://reopen.europa.eu/"; + +"Unable to verify certificate" = "Das Zertifikat kann nicht validiert werden."; +"Make sure you select the desired service..." = "Vergewissern Sie sich, dass Sie den gewünschten Dienst ausgewählt haben und versuchen Sie es erneut. Wenn das Problem erneut auftritt, besuchen Sie bitte die Re-open EU Website."; + +"Validation error" = "Validierungsfehler"; +"Please refer to the Re-open EU website." = "Bitte besuchen Sie die Re-open EU Website."; + +"Failed to process ticketing request" = "Ein interner Fehler ist aufgetreten"; +"Please quit the application and restart again." = "Bitte beenden Sie die Anwendung und starten Sie sie erneut."; + +"The specified service cannot be used" = "Der angegebene Dienst kann nicht verwendet werden"; +"This certificate is not supported" = "Dieses Zertifikat wird nicht unterstützt"; + +"An internet connection error has occurred" = "Ein Internetverbindungsfehler ist aufgetreten"; +"Make sure your device is connected to the internet and try again..." = "Stellen Sie sicher, dass Ihr Gerät mit dem Internet verbunden ist, und versuchen Sie es erneut. Wenn das Problem erneut auftritt, wenden Sie sich bitte an den Support."; + +"Possible limitation" = "Mögliche Einschränkung"; +"Country rules validation failed" = "Validierung der Länderregeln fehlgeschlagen."; + +"Certificate logic engine error" = "Fehler in der Zertifikatlogik-Engine"; + +"Departure" = "Abreise"; +"Arrival" = "Ankunft"; +"Accepted certificate type" = "Akzeptierter Zertifikatstyp"; +"Category" = "Kategorie"; +"Validation Time" = "Validierungszeit"; +"Valid from" = "Gültig ab"; +"Valid to" = "Gültig bis"; + +"Sorry! \n no rules for this country" = "Entschuldigung! \n Keine Regeln für dieses Land vorhanden."; +"Certificate Wallet" = "Zertifikat Wallet"; +"Add New" = "Hinzufügen"; +"Share" = "Teilen"; +"Licenses" = "Lizenzen"; +"Reload" = "Neu laden"; +"Privacy Information" = "Datenschutzinformationen:"; +"Settings" = "Einstellungen"; +"Expired date: %@" = "Abgelaufenes Datum: %@"; +"Certificate type: %@" = "Art des Zertifikats: %@"; + +"COVID-19 vaccination verification data" = "Daten zur Verifizierung der COVID-19-Impfung"; +"Ticketing information" = "Ticketing-Informationen"; +"Available Certificates" = "Verfügbare Zertifikate"; + +"Failed to confirm ticketing request" = "Ticketanfrage konnte nicht bestätigt werden"; +"Please create new ticketing request and try again..." = "Bitte erstellen Sie eine neue Ticketanfrage und versuchen Sie es erneut. Sollte dies erneut passieren, besuchen Sie bitte die EU Website."; diff --git a/DGCAWallet/SupportingFiles/en.lproj/InfoPlist.strings b/DGCAWallet/SupportingFiles/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..e1f3ad3 --- /dev/null +++ b/DGCAWallet/SupportingFiles/en.lproj/InfoPlist.strings @@ -0,0 +1,39 @@ +/*- + * ---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 + + + InfoPlist.strings + DGCAWallet + + Created by Igor Khomiak on 22.11.2021. + +*/ + +//Bundle name +"CFBundleDisplayName" = "Wallet App"; +//Privacy - NFC Scan Usage Description +"NFCReaderUsageDescription" = "You will be abble to import certificate by NFC."; +//Privacy - Camera Usage Description +"NSCameraUsageDescription" = "You will be abble to scan barcodes and images with your camera."; +//Privacy - Face ID Usage Description +"NSFaceIDUsageDescription" = "You will use FaceID to unlock sensitive data."; +//Privacy - Photo Library Usage Description +"NSPhotoLibraryUsageDescription" = "You will be abble to import photos from the photo library."; + + diff --git a/DGCAWallet/SupportingFiles/en.lproj/Localizable.strings b/DGCAWallet/SupportingFiles/en.lproj/Localizable.strings index 558d2c4..5638b98 100644 --- a/DGCAWallet/SupportingFiles/en.lproj/Localizable.strings +++ b/DGCAWallet/SupportingFiles/en.lproj/Localizable.strings @@ -16,8 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * ---license-end - */ -/* + Localizable.strings DGCAWallet @@ -25,81 +24,145 @@ */ -"log.certs-loaded" = "%d certs loaded."; -"list.cell.scanned-at" = "Scanned %@"; -"btn.okay" = "Okay"; -"btn.cancel" = "Cancel"; -"btn.save" = "Save"; -"btn.confirm" = "Confirm"; -"tan.confirm.title" = "Confirm TAN"; -"tan.confirm.text" = "Please enter the TAN that was provided together with your certificate:"; -"tan.confirm.placeholder" = "XYZ12345"; -"tan.confirm.success.title" = "Success"; -"tan.confirm.success.text" = "Certificate was saved to wallet!"; -"tan.confirm.fail.title" = "Failure"; -"tan.confirm.fail.text" = "Check the TAN and try again."; -"tan.label" = "TAN: %@"; -"auth.confirm" = "Confirm Identity"; -"auth.later" = "Try Later"; -"auth.error" = "Could not verify device ownership. Please try setting a passcode for this device before opening the app."; -"cert.delete.title" = "Delete Certificate"; +"Wallet App" = "Wallet App"; +"Scanned %@" = "Scanned %@"; +"Save" = "Save"; +"Done" = "Done"; +"Edit" = "Edit"; +"Confirm" = "Confirm"; +"Confirm TAN" = "Confirm TAN"; +"Please enter the TAN that was provided together with your certificate:" = "Please enter the TAN that was provided together with your certificate:"; + +"Certificate saved successfully" = "Certificate saved successfully"; +"Now it is available in the wallet" = "Now it is available in the wallet"; +"Cannot save the Certificate" = "Cannot save the Certificate"; +"TAN: %@" = "TAN: %@"; +"Could not verify device ownership" = "Could not verify device ownership"; +"Try Later" = "Try Later"; +"Please try setting a passcode for this device before opening the app." = "Please try setting a passcode for this device before opening the app."; +"Delete Certificate" = "Delete Certificate"; "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"; +"tap to reveal" = "tap to reveal"; + +"Check Validity" = "Check Validity"; +"I Agree, check validity" = "I Agree, check validity"; +"Failed for %@ (see settings)" = "Failed for %@ (see settings)"; +"Passed for %@ (see settings)" = "Passed for %@ (see settings)"; +"Open for %@ (see settings)" = "Open for %@ (see settings)"; + +"Failed" = "Failed"; +"Passed" = "Passed"; +"Open" = "Open"; +"Rule" = "Rule"; +"Current" = "Current"; +"Result" = "Result"; + "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 is valid and confirms to the provided country rules. Additional entry requirements might apply, please refer to the Re-open EU website: https://reopen.europa.eu/"; -"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" = "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."; -"issuer.country" = "Issuer Country"; -"test.type" = "Type of Test"; -"section.name" = "Name"; -"image.confirm.title" = "Save image"; -"image.confirm.text" = "Please enter the image name"; -"image.confirm.placeholder" = "filename"; - -"pdf.confirm.title" = "Save pdf"; -"pdf.confirm.text" = "Please enter the pdf file name"; -"pdf.confirm.placeholder" = "filename"; -"log.images" = "%d images loaded."; -"log.pdfs" = "%d pdf files loaded."; -"share.qr.code" = "Share QR Code?"; -"want.share" = "Do you want share DCC certificate via image or PDF file?"; -"image.export" = "Image export"; -"pdf.export" = "PDF Export"; -"cancel" = "Cancel"; -"add.new" = "Add new?"; -"want.add" = "Do you want to add new certificate, image or PDF file?"; -"scan.certificate" = "Scan certificate"; -"image.import" = "Image import"; -"pdf.import" = "PDF Import"; -"nfc.import" = "NFC Import"; -"error" = "Error"; -"read.dcc.from.nfc" = "Reading DCC from NFC"; -"retry" = "Retry"; -"ok" = "OK"; -"section.certificates" = "Certificates"; -"section.images" = "Images"; -"section.pdf" = "PDF's files"; -"get.image.from" = "Get Image From"; -"camera" = "Camera"; -"galery" = "Gallery"; -"dont.have.camera" = "You don't have camera"; + +"Disclaimer" = "Disclaimer"; +"Your destination country" = "Your destination country"; +"Check the date" = "Check the date"; +"Valid certificate" = "Valid certificate"; +"Your certificate is valid and confirms..." = "Your certificate is valid and confirms to the provided country rules. Additional entry requirements might apply, please refer to the Re-open EU website: https://reopen.europa.eu/"; + +"Invalid certificate" = "Invalid certificate"; +"Your certificate did not allows you to enter the chosen country" = "Your certificate did not allows you to enter the chosen country"; +"Certificate has limitation" = "Certificate has limitation"; +"Your certificate is valid but has the following restrictions:" = "Your certificate is valid but has the following restrictions:"; +"Validating certificate with country rules" = "Validating certificate with country rules"; + +"This check gives an indication on eligibility..." = "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."; +"Issuer Country" = "Issuer Country"; +"Type of Test" = "Type of Test"; +"Name" = "Name"; +"Save image" = "Save image"; +"Please enter the image name" = "Please enter the image name"; +"filename" = "filename"; + +"Save PDF file" = "Save PDF file"; +"Please enter the pdf file name" = "Please enter the pdf file name"; +"Share QR Code?" = "Share QR Code?"; +"Do you want to share DCC certificate via image or PDF file?" = "Do you want to share DCC certificate via image or PDF file?"; +"Export as Image" = "Export as Image"; +"Export as PDF" = "Export as PDF"; +"Cancel" = "Cancel"; +"Add new?" = "Add new?"; +"Scan certificate" = "Scan certificate"; +"Image import" = "Image import"; +"PDF Import" = "PDF Import"; +"NFC Import" = "NFC Import"; +"Cannot read NFC" = "Cannot read NFC"; +"An error occurred while reading NFC" = "An error occurred while reading NFC"; +"Retry" = "Retry"; +"OK" = "OK"; +"Images" = "Images"; +"PDF files" = "PDF files"; +"Get Image from" = "Get Image from"; +"Next" = "Next"; + +"Camera" = "Camera"; +"Gallery" = "Gallery"; +"Cannot scan" = "Cannot scan"; +"You don't have a camera." = "You don't have a camera."; +"Services" = "Services"; +"Certificates" = "Certificates"; +"Please select the required service" = "Please select the required service"; +"Each service is located on a separate server and is designed for specific activities." = "Each service is located on a separate server and is designed for specific activities"; + +"Please select a certificate" = "Please select a certificate"; +"Here are all the appropriate certificates." = "Here are all the appropriate certificates."; + +"Close" = "Close"; +"Last Updated: %@" = "Last Updated: %@"; +"Consent" = "Consent"; +"Do you agree to share the %@ certificate with %@?" = "Do you agree to share the %@ certificate with %@?"; + +"Your certificate is not valid. Please refer to the Re-open EU website:" = "Your certificate is not valid. Please refer to the Re-open EU website:"; + +"Your certificate is valid but has the following restrictions:" = "Your certificate is valid but has the following restrictions:"; + +"Unable to verify certificate" = "Unable to verify certificate"; +"Make sure you select the desired service..." = "Make sure you select the desired service and try again. If it happens again, please refer to the Re-open EU website."; + +"Validation error" = "Validation error"; +"Please refer to the Re-open EU website." = "Please refer to the Re-open EU website."; + +"Failed to process ticketing request" = "Failed to process ticketing request"; +"Please quit the application and restart again." = "Please quit the application and restart again."; + +"The specified service cannot be used" = "The specified service cannot be used"; +"This certificate is not supported" = "This certificate is not supported"; + +"An internet connection error has occurred" = "An internet connection error has occurred"; +"Make sure your device is connected to the internet and try again..." = "Make sure your device is connected to the internet and try again. If it happens again, please refer to application support."; + +"Possible limitation" = "Possible limitation"; +"Country rules validation failed" = "Country rules validation failed"; + +"Certificate logic engine error" = "Certificate logic engine error"; + +"Departure" = "Departure"; +"Arrival" = "Arrival"; +"Accepted certificate type" = "Accepted certificate type"; +"Category" = "Category"; +"Validation Time" = "Validation Time"; +"Valid from" = "Valid from"; +"Valid to" = "Valid to"; + +"Sorry! \n no rules for this country" = "Sorry! \n no rules for this country"; +"Certificate Wallet" = "Certificate Wallet"; +"Add New" = "Add New"; +"Share" = "Share"; +"Licenses" = "Licenses"; +"Reload" = "Reload"; +"Privacy Information" = "Privacy Information:"; +"Settings" = "Settings"; +"Expired date: %@" = "Expired date: %@"; +"Certificate type: %@" = "Certificate type: %@"; + +"COVID-19 vaccination verification data" = "COVID-19 vaccination verification data"; +"Ticketing information" = "Ticketing information"; +"Available Certificates" = "Available Certificates"; + +"Failed to confirm ticketing request" = "Failed to confirm ticketing request"; +"Please create new ticketing request and try again..." = "Please create new ticketing request and try again. If it happens again, please refer to the Re-open EU website."; diff --git a/DGCAWallet/Ticketing/Identity/AccessTokenService.swift b/DGCAWallet/Ticketing/Identity/AccessTokenService.swift new file mode 100644 index 0000000..03e5d3c --- /dev/null +++ b/DGCAWallet/Ticketing/Identity/AccessTokenService.swift @@ -0,0 +1,29 @@ +// +/*- + * ---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 + */ +// +// AccessTokenService.swift +// DGCAWallet +// +// Created by Illia Vlasov on 19.10.2021. +// + + +import Foundation diff --git a/DGCAWallet/Ticketing/Identity/IdentityService.swift b/DGCAWallet/Ticketing/Identity/IdentityService.swift new file mode 100644 index 0000000..958f584 --- /dev/null +++ b/DGCAWallet/Ticketing/Identity/IdentityService.swift @@ -0,0 +1,93 @@ +// +/*- + * ---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 + */ +// +// IdentityService.swift +// DGCAWallet +// +// Created by Illia Vlasov on 19.10.2021. +// +import Foundation +import SwiftDGC + +enum IdentityError: Error { + case wrongData + case connection(error: Error) + case parsingError +} + +typealias IdentityCompletion = (ServerListResponse?, Error?) -> Void + +class IdentityService { + static func requestListOfServices(ticketingInfo : CheckInQR, completion : @escaping IdentityCompletion) { + if let tokenData = ticketingInfo.token.data(using: .utf8) { + KeyChain.save(key: SharedConstants.keyTicketingToken, data: tokenData) + } + let url = URL(string: ticketingInfo.serviceIdentity)! + var request = URLRequest(url: url) + request.addValue( "1.0.0", forHTTPHeaderField: "X-Version") + request.addValue( "application/json", forHTTPHeaderField: "content-type") + + let session = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in + guard error == nil else { + completion(nil, IdentityError.connection(error: error!)) + return + } + + guard let responseData = data else { + completion(nil, IdentityError.wrongData) + return + } + + do { + let responseModel = try JSONDecoder().decode(ServerListResponse.self, from: responseData) + completion(responseModel, nil) + } catch { + completion(nil, IdentityError.parsingError) + } + }) + session.resume() + } + + static func getServiceInfo(url : URL, completion: @escaping IdentityCompletion) { + var request = URLRequest(url: url) + request.addValue( "1.0.0", forHTTPHeaderField: "X-Version") + request.addValue( "application/json", forHTTPHeaderField: "content-type") + + let session = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in + guard error == nil else { + completion(nil, IdentityError.connection(error: error!)) + return + } + + guard let data = data else { + completion(nil, IdentityError.wrongData) + return + } + do { + let responseModel = try JSONDecoder().decode(ServerListResponse.self, from: data) + completion(responseModel, nil) + } catch { + completion(nil, IdentityError.parsingError) + } + }) + session.resume() + } +} diff --git a/DGCAWallet/ViewControllers/CertTable.swift b/DGCAWallet/ViewControllers/CertTable.swift deleted file mode 100644 index 48959d4..0000000 --- a/DGCAWallet/ViewControllers/CertTable.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -/*- - * ---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 - */ -// -// CertTable.swift -// DGCAWallet -// -// Created by Yannick Spreen on 4/30/21. -// - -import SwiftDGC -import UIKit - -class CertTableVC: UIViewController { - @IBOutlet weak var table: UITableView! - - var hCert: HCert! { - (parent as? CertPagesVC)?.embeddingVC.hCert - } - - override func viewDidLoad() { - super.viewDidLoad() - - table.dataSource = self - table.contentInset = .init(top: 0, left: 0, bottom: 32, right: 0) - table.reloadData() - } -} - -extension CertTableVC: UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return hCert.info.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let base = tableView.dequeueReusableCell(withIdentifier: "infoCell", for: indexPath) - guard let cell = base as? InfoCell else { - return base - } - cell.draw(hCert.info[indexPath.row]) - return cell - } -} diff --git a/DGCAWallet/ViewControllers/CertificateViewer.swift b/DGCAWallet/ViewControllers/CertificateViewer.swift deleted file mode 100644 index 9fe8e15..0000000 --- a/DGCAWallet/ViewControllers/CertificateViewer.swift +++ /dev/null @@ -1,195 +0,0 @@ -/*- - * ---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 - */ -// -// CertificateViewer.swift -// DGCAWallet -// -// Created by Yannick Spreen on 4/19/21. -// - -import Foundation -import UIKit -import FloatingPanel -import SwiftDGC -import PDFKit - -class CertificateViewerVC: UIViewController { - @IBOutlet weak var headerBackground: UIView! - @IBOutlet weak var nameLabel: UILabel! - @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? - public var isSaved = true - - func draw() { - guard let hCert = hCert else { - return - } - 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() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - if #available(iOS 13.0, *) { - draw() - } else { - DispatchQueue.main.async { [weak self] in - self?.draw() - } - } - } - - var newCertAdded = false - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - - Brightness.reset() - childDismissedDelegate?.childDismissed(newCertAdded) - } - - @IBAction - func closeButtonClick() { - if isSaved { - return dismiss(animated: true, completion: nil) - } - saveCert() - } - - @IBAction - func cancelButtonClick() { - 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") - ) { [weak self] in - guard let cert = self?.hCert else { - return - } - GatewayConnection.claim(cert: cert, with: $0) { success, newTan in - if success { - guard let cert = self?.hCert else { - return - } - LocalData.add(cert, with: newTan) - self?.newCertAdded = true - self?.showAlert( - title: l10n("tan.confirm.success.title"), - subtitle: l10n("tan.confirm.success.text") - ) { _ in - self?.dismiss(animated: true, completion: nil) - } - } else { - self?.showAlert( - title: l10n("tan.confirm.fail.title"), - subtitle: l10n("tan.confirm.fail.text") - ) - } - } - } - } - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if let child = segue.destination as? CertPagesVC { - child.embeddingVC = self - } - } - - @IBAction func shareAction(_ sender: Any) { - let menuActionSheet = UIAlertController(title: l10n("share.qr.code"), - message: l10n("want.share"), - preferredStyle: UIAlertController.Style.actionSheet) - menuActionSheet.addAction(UIAlertAction(title: l10n("image.export"), - style: UIAlertAction.Style.default, - handler: { [weak self] _ in - self?.shareQRCodeLikeImage() - })) - menuActionSheet.addAction(UIAlertAction(title: l10n("pdf.export"), - style: UIAlertAction.Style.default, - handler: { [weak self] _ in - self?.shareQrCodeLikePDF() - })) - menuActionSheet.addAction(UIAlertAction(title: l10n("cancel"), - style: UIAlertAction.Style.destructive, - handler: nil)) - present(menuActionSheet, animated: true, completion: nil) - } -} - -extension CertificateViewerVC { - private func shareQRCodeLikeImage() { - guard let hCert = hCert else { - return - } - guard let savedImage = hCert.qrCode else { - return - } - let imageToShare = [ savedImage ] - let activityViewController = UIActivityViewController(activityItems: imageToShare as [Any], - applicationActivities: nil) - activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash - self.present(activityViewController, animated: true, completion: nil) - } - private func shareQrCodeLikePDF() { - guard let hCert = hCert else { - return - } - guard let savedImage = hCert.qrCode else { - return - } - let pdfDocument = PDFDocument() - let pdfPage = PDFPage(image: savedImage) - pdfDocument.insert(pdfPage!, at: 0) - let data = pdfDocument.dataRepresentation() - let pdfToShare = [ data ] - let activityViewController = UIActivityViewController(activityItems: pdfToShare as [Any], - applicationActivities: nil) - activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash - self.present(activityViewController, animated: true, completion: nil) - - } -} diff --git a/DGCAWallet/ViewControllers/CertCode.swift b/DGCAWallet/ViewControllers/CertificateViewer/CertCodeController.swift similarity index 68% rename from DGCAWallet/ViewControllers/CertCode.swift rename to DGCAWallet/ViewControllers/CertificateViewer/CertCodeController.swift index 59fdd0c..d9bb6c6 100644 --- a/DGCAWallet/ViewControllers/CertCode.swift +++ b/DGCAWallet/ViewControllers/CertificateViewer/CertCodeController.swift @@ -19,7 +19,7 @@ * ---license-end */ // -// CertTable.swift +// CertCodeController.swift // DGCAWallet // // Created by Yannick Spreen on 4/30/21. @@ -28,20 +28,24 @@ import SwiftDGC import UIKit -class CertCodeVC: UIViewController { - @IBOutlet weak var imageView: UIImageView! - @IBOutlet weak var tanLabel: UILabel! +class CertCodeController: UIViewController { + @IBOutlet fileprivate weak var imageView: UIImageView! + @IBOutlet fileprivate weak var tanLabel: UILabel! - var hCert: HCert! { (parent as? CertPagesVC)?.embeddingVC.hCert } - var tan: String? { (parent as? CertPagesVC)?.embeddingVC.tan } + var hCert: HCert? { + (parent as? CertPagesController)?.embeddingVC?.hCert + } + var tan: String? { + (parent as? CertPagesController)?.embeddingVC?.tan + } override func viewDidLoad() { super.viewDidLoad() - imageView.image = hCert.qrCode + imageView.image = hCert?.qrCode tanLabel.text = "" if tan != nil { - tanLabel.text = String(format: l10n("tan.label"), l10n("tap-to-reveal")) + tanLabel.text = String(format: "TAN: %@".localized, "tap to reveal".localized) tanLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapToReveal))) tanLabel.isUserInteractionEnabled = true } @@ -49,7 +53,7 @@ class CertCodeVC: UIViewController { @IBAction func tapToReveal() { if let tan = tan { - tanLabel.text = String(format: l10n("tan.label"), tan) + tanLabel.text = String(format: "TAN: %@".localized, tan) } } } diff --git a/DGCAWallet/ViewControllers/CertPages.swift b/DGCAWallet/ViewControllers/CertificateViewer/CertPagesController.swift similarity index 69% rename from DGCAWallet/ViewControllers/CertPages.swift rename to DGCAWallet/ViewControllers/CertificateViewer/CertPagesController.swift index 1c3fb8f..10eb44d 100644 --- a/DGCAWallet/ViewControllers/CertPages.swift +++ b/DGCAWallet/ViewControllers/CertificateViewer/CertPagesController.swift @@ -19,7 +19,7 @@ * ---license-end */ // -// CertPages.swift +// CertPagesController.swift // DGCAWallet // // Created by Yannick Spreen on 4/30/21. @@ -27,8 +27,8 @@ import UIKit -class CertPagesVC: UIPageViewController { - weak var embeddingVC: CertificateViewerVC! +class CertPagesController: UIPageViewController { + weak var embeddingVC: CertificateViewerController? var index = 0 let vcs: [UIViewController] = [ @@ -38,14 +38,16 @@ class CertPagesVC: UIPageViewController { override func viewDidLoad() { super.viewDidLoad() - + guard let embeddingVC = embeddingVC else { return } + self.dataSource = self self.delegate = self + index = embeddingVC.isSaved ? 0 : 1 setViewControllers([vcs[index]], direction: .forward, animated: false) let appearance = UIPageControl.appearance(whenContainedInInstancesOf: [UIPageViewController.self]) appearance.pageIndicatorTintColor = UIColor.disabledText - appearance.currentPageIndicatorTintColor = UIColor.black + appearance.currentPageIndicatorTintColor = UIColor.walletBlack } override func viewDidAppear(_ animated: Bool) { @@ -55,53 +57,39 @@ class CertPagesVC: UIPageViewController { } func setBrightness() { - if index == 0 { + index == 0 ? Brightness.forceFull() : Brightness.reset() Brightness.forceFull() - } else { - Brightness.reset() - } } } -extension CertPagesVC: UIPageViewControllerDataSource { - func pageViewController( - _ pageViewController: UIPageViewController, - viewControllerBefore viewController: UIViewController - ) -> UIViewController? { +extension CertPagesController: UIPageViewControllerDataSource { + func pageViewController(_ pageViewController: UIPageViewController, + viewControllerBefore viewController: UIViewController) -> UIViewController? { let index = vcs.firstIndex(of: viewController) ?? 0 return index == 0 ? nil : vcs[index - 1] } - func pageViewController( - _ pageViewController: UIPageViewController, - viewControllerAfter viewController: UIViewController - ) -> UIViewController? { + func pageViewController( _ pageViewController: UIPageViewController, + viewControllerAfter viewController: UIViewController) -> UIViewController? { let index = vcs.firstIndex(of: viewController) ?? vcs.count - 1 return index == vcs.count - 1 ? nil : vcs[index + 1] } func presentationCount(for pageViewController: UIPageViewController) -> Int { - vcs.count + return vcs.count } func presentationIndex(for pageViewController: UIPageViewController) -> Int { - index + return index } } -extension CertPagesVC: UIPageViewControllerDelegate { - func pageViewController( - _ pageViewController: UIPageViewController, +extension CertPagesController: UIPageViewControllerDelegate { + func pageViewController( _ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], - transitionCompleted completed: Bool - ) { - guard - completed, - let controller = pageViewController.viewControllers?.first - else { - return - } + transitionCompleted completed: Bool) { + guard completed, let controller = pageViewController.viewControllers?.first else { return } index = vcs.firstIndex(of: controller) ?? 0 setBrightness() } diff --git a/DGCAWallet/ViewControllers/CertificateViewer/CertificateController.swift b/DGCAWallet/ViewControllers/CertificateViewer/CertificateController.swift new file mode 100644 index 0000000..1fdbcb0 --- /dev/null +++ b/DGCAWallet/ViewControllers/CertificateViewer/CertificateController.swift @@ -0,0 +1,84 @@ +// +/*- + * ---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 + */ +// +// CertificateController.swift +// DGCAWallet +// +// Created by Yannick Spreen on 4/30/21. +// + +import SwiftDGC +import UIKit + +class CertificateController: UIViewController { + @IBOutlet fileprivate weak var table: UITableView! + @IBOutlet fileprivate weak var activityIndicator: UIActivityIndicatorView! + + var hCert: HCert? { + (parent as? CertPagesController)?.embeddingVC?.hCert + } + private var validityState: ValidityState = .invalidState + private var sectionBuilder: SectionBuilder? + + override func viewDidLoad() { + super.viewDidLoad() + validateAndSetupInterface() + table.contentInset = .init(top: 0, left: 0, bottom: 32, right: 0) + } + + private func validateAndSetupInterface() { + guard let hCert = hCert else { return } + + activityIndicator.startAnimating() + let validator = CertificateValidator(with: hCert) + DispatchQueue.global(qos: .userInitiated).async { + validator.validate {[weak self] (validityState) in + self?.validityState = validityState + + let builder = SectionBuilder(with: hCert, validity: validityState) + builder.makeSections(for: .wallet) + if let section = validityState.infoRulesSection { + builder.makeSectionForRuleError(ruleSection: section, for: .wallet) + } + self?.sectionBuilder = builder + DispatchQueue.main.async { + self?.activityIndicator.stopAnimating() + self?.table.reloadData() + } + } + } + } +} + +extension CertificateController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.sectionBuilder?.infoSection.count ?? 0 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "infoCell", for: indexPath) as? InfoCell + else { return UITableViewCell() } + if let infoSection = self.sectionBuilder?.infoSection[indexPath.row] { + cell.setupCell(infoSection) + } + return cell + } +} diff --git a/DGCAWallet/ViewControllers/CertificateViewer/CertificateViewerController.swift b/DGCAWallet/ViewControllers/CertificateViewer/CertificateViewerController.swift new file mode 100644 index 0000000..2c093e0 --- /dev/null +++ b/DGCAWallet/ViewControllers/CertificateViewer/CertificateViewerController.swift @@ -0,0 +1,258 @@ +/*- + * ---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 + */ +// +// CertificateViewerController.swift +// DGCAWallet +// +// Created by Yannick Spreen on 4/19/21. +// + +import UIKit +import SwiftDGC +import PDFKit + +protocol CertificateManaging: AnyObject { + func certificateViewer(_ controller: CertificateViewerController, didDeleteCertificate cert: HCert) + func certificateViewer(_ controller: CertificateViewerController, didAddCeCertificate cert: HCert) +} + + +class CertificateViewerController: UIViewController { + private enum Constants { + static let showValidityController = "showValidityController" + static let embedCertPagesController = "embedCertPagesController" + } + + @IBOutlet fileprivate weak var headerBackground: UIView! + @IBOutlet fileprivate weak var nameLabel: UILabel! + @IBOutlet fileprivate weak var dismissButton: UIButton! + @IBOutlet fileprivate weak var cancelButton: UIButton! + @IBOutlet fileprivate weak var shareButton: UIButton! + @IBOutlet fileprivate weak var editButton: UIButton! + @IBOutlet fileprivate weak var deleteButton: UIButton! + @IBOutlet fileprivate weak var checkValidityButton: UIButton! + @IBOutlet fileprivate weak var activityIndicator: UIActivityIndicatorView! + + var hCert: HCert? + var certDate: Date? + var tan: String? + + weak var delegate: CertificateManaging? + + public var isSaved = true + private var isEditMode = false + + deinit { + let center = NotificationCenter.default + center.removeObserver(self) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + setupInterface() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + Brightness.reset() + } + + func setupInterface() { + guard let hCert = hCert else { return } + + nameLabel.text = hCert.fullName + shareButton.setTitle("Share".localized, for: .normal) + deleteButton.setTitle("Delete Certificate".localized, for: .normal) + checkValidityButton.setTitle("Check Validity".localized, for: .normal) + + if !isSaved { + dismissButton.setTitle("Save".localized, for: .normal) + editButton.isHidden = true + cancelButton.isHidden = false + + deleteButton.isHidden = true + checkValidityButton.isHidden = true + shareButton.isHidden = false + dismissButton.isHidden = false + nameLabel.textColor = .walletBlack + headerBackground.backgroundColor = .walletGray10 + } else { + editButton.isHidden = false + cancelButton.isHidden = true + dismissButton.setTitle("Done".localized, for: .normal) + if isEditMode { + editButton.setTitle("Done".localized, for: .normal) + deleteButton.isHidden = false + checkValidityButton.isHidden = true + dismissButton.isHidden = true + shareButton.isHidden = true + } else { + editButton.setTitle("Edit".localized, for: .normal) + deleteButton.isHidden = true + checkValidityButton.isHidden = false + dismissButton.isHidden = false + shareButton.isHidden = false + } + nameLabel.textColor = .white + headerBackground.backgroundColor = .walletBlue + } + + view.layoutIfNeeded() + } + + @IBAction func closeButtonClick() { + if isSaved { + dismiss(animated: true, completion: nil) + } else { + activityIndicator.startAnimating() + saveCert {[weak self] in + DispatchQueue.main.async { + self?.activityIndicator.stopAnimating() + } + } + } + } + + @IBAction func cancelButtonClick() { + dismiss(animated: true, completion: nil) + } + + @IBAction func checkValidityAction(_ sender: Any) { + self.performSegue(withIdentifier: Constants.showValidityController, sender: nil) + } + + @IBAction func editAction() { + isEditMode = !isEditMode + setupInterface() + } + + @IBAction func deleteCertificateAction() { + guard let certDate = certDate else { return } + guard let cert = self.hCert else { return } + + showAlert( title: "Delete Certificate".localized, subtitle: "cert.delete.body".localized, + actionTitle: "Confirm".localized, cancelTitle: "Cancel".localized) { [unowned self] in + if $0 { + DataCenter.localDataManager.remove(withDate: certDate) { _ in + self.delegate?.certificateViewer(self, didDeleteCertificate: cert) + DispatchQueue.main.async { + self.dismiss(animated: true, completion: nil) + } + } // LocalData + } + } + } + + private func saveCert(completion: @escaping CompletionHandler) { + showInputDialog(title: "Confirm TAN".localized, + subtitle: "Please enter the TAN that was provided together with your certificate:".localized, + actionTitle: "Confirm".localized, inputPlaceholder: "XYZ12345" ) { [unowned self] in + guard let certificate = self.hCert else { + DGCLogger.logInfo("Certificate error") + completion() + return + } + + GatewayConnection.claim(cert: certificate, with: $0) { success, newTan, error in + guard error == nil else { + completion() + DispatchQueue.main.async { + self.showAlert(title:"Cannot save the Certificate".localized, subtitle: "Check the TAN and try again.".localized) + } + + DGCLogger.logError(error!) + return + } + + if success { + DataCenter.localDataManager.add(certificate, with: newTan) { _ in + completion() + DispatchQueue.main.async { + self.showAlert(title: "Certificate saved successfully".localized, subtitle: "Now it is available in the wallet".localized) { _ in + self.dismiss(animated: true) { + self.delegate?.certificateViewer(self, didAddCeCertificate: certificate) + } + } + } + } + } else { + completion() + DispatchQueue.main.async { + self.showAlert(title:"Cannot save the Certificate".localized, subtitle: "Check the TAN and try again.".localized) + } + } + } + } + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + switch segue.identifier { + case Constants.showValidityController: + guard let checkController = segue.destination as? CheckValidityController else { return } + checkController.setupCheckValidity(with: hCert) + + case Constants.embedCertPagesController: + guard let childController = segue.destination as? CertPagesController else { return } + childController.embeddingVC = self + + default: + break + } + } + + @IBAction func shareAction(_ sender: Any) { + let menuActionSheet = UIAlertController(title: "Share QR Code?".localized, + message: "Do you want to share DCC certificate via image or PDF file?".localized, preferredStyle: .actionSheet) + menuActionSheet.addAction(UIAlertAction(title: "Export as Image".localized, style: .default, handler: { [weak self] _ in + self?.shareQRCodeLikeImage() + })) + menuActionSheet.addAction(UIAlertAction(title: "Export as PDF".localized, style: .default, handler: { [weak self] _ in + self?.shareQrCodeLikePDF() + })) + menuActionSheet.addAction(UIAlertAction(title: "Cancel".localized, style: .cancel, handler: nil)) + present(menuActionSheet, animated: true, completion: nil) + } +} + +extension CertificateViewerController { + private func shareQRCodeLikeImage() { + guard let hCert = hCert, let savedImage = hCert.qrCode else { return } + + let imageToShare = [ savedImage ] + let activityViewController = UIActivityViewController(activityItems: imageToShare as [Any], + applicationActivities: nil) + activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash + self.present(activityViewController, animated: true, completion: nil) + } + + private func shareQrCodeLikePDF() { + guard let hCert = hCert, let savedImage = hCert.qrCode else { return } + + let pdfDocument = PDFDocument() + let pdfPage = PDFPage(image: savedImage) + pdfDocument.insert(pdfPage!, at: 0) + let data = pdfDocument.dataRepresentation() + let pdfToShare = [ data ] + let activityViewController = UIActivityViewController(activityItems: pdfToShare as [Any], + applicationActivities: nil) + activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash + self.present(activityViewController, animated: true, completion: nil) + } +} diff --git a/DGCAWallet/ViewControllers/ImageViewerVC.swift b/DGCAWallet/ViewControllers/CertificateViewer/ImageViewerController.swift similarity index 70% rename from DGCAWallet/ViewControllers/ImageViewerVC.swift rename to DGCAWallet/ViewControllers/CertificateViewer/ImageViewerController.swift index 76ebb7f..4a8ffc4 100644 --- a/DGCAWallet/ViewControllers/ImageViewerVC.swift +++ b/DGCAWallet/ViewControllers/CertificateViewer/ImageViewerController.swift @@ -19,7 +19,7 @@ * ---license-end */ // -// ImageViewerVC.swift +// ImageViewerController.swift // DGCAWallet // // Created by Alexandr Chernyy on 25.08.2021. @@ -27,33 +27,32 @@ import UIKit +import SwiftDGC -class ImageViewerVC: UIViewController { +class ImageViewerController: UIViewController { - @IBOutlet weak var closeButton: UIButton! - @IBOutlet weak var backButton: UIButton! - @IBOutlet weak var shareButton: UIButton! - @IBOutlet weak var imageView: UIImageView! - @IBOutlet weak var scrollView: UIScrollView! - var savedImage: SavedImage? { - didSet { - setupView() - } - } + @IBOutlet fileprivate weak var closeButton: UIButton! + @IBOutlet fileprivate weak var shareButton: UIButton! + @IBOutlet fileprivate weak var imageView: UIImageView! + @IBOutlet fileprivate weak var scrollView: UIScrollView! + + var savedImage: SavedImage? override func viewDidLoad() { super.viewDidLoad() + self.closeButton.setTitle("Done".localized, for: .normal) + shareButton.setTitle("Share".localized, for: .normal) + self.title = "PDF files".localized setupView() } - func setImage( image: SavedImage? = nil) { + func setImage(image: SavedImage? = nil) { savedImage = image } private func setupView() { - guard let savedImage = savedImage, let scrollView = scrollView, let imageView = imageView else { - return - } + guard let savedImage = savedImage, let scrollView = scrollView, let imageView = imageView else { return } + scrollView.backgroundColor = .lightGray imageView.image = savedImage.image scrollView.delegate = self @@ -64,28 +63,22 @@ class ImageViewerVC: UIViewController { } @IBAction func shareAction(_ sender: Any) { - guard let savedImage = savedImage else { - return - } + guard let savedImage = savedImage else { return } + let imageToShare = [ savedImage.image ] let activityViewController = UIActivityViewController(activityItems: imageToShare as [Any], - applicationActivities: nil) + applicationActivities: nil) activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash self.present(activityViewController, animated: true, completion: nil) - - } - - @IBAction func backAction(_ sender: Any) { - self.dismiss(animated: true) } - + @IBAction func closeAction(_ sender: Any) { self.dismiss(animated: true) } } -extension ImageViewerVC: UIScrollViewDelegate { +extension ImageViewerController: UIScrollViewDelegate { func viewForZooming(in scrollView: UIScrollView) -> UIView? { - imageView + return imageView } } diff --git a/DGCAWallet/ViewControllers/PDFViewerVC.swift b/DGCAWallet/ViewControllers/CertificateViewer/PDFViewerController.swift similarity index 73% rename from DGCAWallet/ViewControllers/PDFViewerVC.swift rename to DGCAWallet/ViewControllers/CertificateViewer/PDFViewerController.swift index 2b2f608..5483e2e 100644 --- a/DGCAWallet/ViewControllers/PDFViewerVC.swift +++ b/DGCAWallet/ViewControllers/CertificateViewer/PDFViewerController.swift @@ -19,24 +19,25 @@ * ---license-end */ // -// PDFViewerVC.swift +// PDFViewerController.swift // DGCAWallet // // Created by Alexandr Chernyy on 25.08.2021. // - + import UIKit import PDFKit +import SwiftDGC -class PDFViewerVC: UIViewController { +class PDFViewerController: UIViewController { - @IBOutlet weak var closeButton: UIButton! - @IBOutlet weak var backButton: UIButton! - @IBOutlet weak var shareButton: UIButton! - @IBOutlet weak var pdfView: UIView! - var pdfViewer: PDFView? + @IBOutlet fileprivate weak var closeButton: UIButton! + @IBOutlet fileprivate weak var shareButton: UIButton! + @IBOutlet fileprivate weak var pdfView: UIView! + var pdfViewer: PDFView? + var savedPDF: SavedPDF? { didSet { setupView() @@ -49,36 +50,31 @@ class PDFViewerVC: UIViewController { } private func setupView() { - guard let savedPDF = savedPDF, let pdfView = pdfView else { - return - } + guard let savedPDF = savedPDF, let pdfView = pdfView else { return } + if pdfViewer == nil { pdfViewer = PDFView(frame: pdfView.bounds) } pdfViewer?.autoScales = true pdfView.addSubview(pdfViewer!) pdfViewer?.document = savedPDF.pdf - self.navigationItem.title = savedPDF.fileName + closeButton.setTitle("Done".localized, for: .normal) + shareButton.setTitle("Share".localized, for: .normal) + navigationItem.title = savedPDF.fileName } - public func setPDF(pdf: SavedPDF) { + func setPDF(pdf: SavedPDF) { savedPDF = pdf } @IBAction func shareAction(_ sender: Any) { - guard let savedPDF = savedPDF else { - return - } + guard let savedPDF = savedPDF else { return } + let pdfToShare = [ savedPDF.pdfData ] let activityViewController = UIActivityViewController(activityItems: pdfToShare as [Any], - applicationActivities: nil) + applicationActivities: nil) activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash self.present(activityViewController, animated: true, completion: nil) - - } - - @IBAction func backAction(_ sender: Any) { - self.dismiss(animated: true) } @IBAction func closeAction(_ sender: Any) { diff --git a/DGCAWallet/ViewControllers/CheckValidityVC.xib b/DGCAWallet/ViewControllers/CheckValidityVC.xib deleted file mode 100644 index b8d9785..0000000 --- a/DGCAWallet/ViewControllers/CheckValidityVC.xib +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DGCAWallet/ViewControllers/Home.swift b/DGCAWallet/ViewControllers/Home.swift deleted file mode 100644 index e083566..0000000 --- a/DGCAWallet/ViewControllers/Home.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -/*- - * ---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 - */ -// -// Home.swift -// DGCAWallet -// -// Created by Yannick Spreen on 4/25/21. -// - -import Foundation -import UIKit -import SwiftDGC - -class HomeVC: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - - 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 { - return - } - let renderer = UIGraphicsImageRenderer(size: self.view.bounds.size) - SecureBackground.image = renderer.image { rendererContext in - self.view.layer.render(in: rendererContext.cgContext) - } - self.loadComplete() - } - } - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if loaded { - checkId() - } - } - - var loaded = false - func loadComplete() { - loaded = true - checkId() - } - - func checkId() { - if LocalData.sharedInstance.versionedConfig["outdated"].bool == true { - showAlert(title: l10n("info.outdated"), subtitle: l10n("info.outdated.body")) - return - } - SecureBackground.checkId(from: self) { success in - DispatchQueue.main.async { [weak self] in - if success { - self?.performSegue(withIdentifier: "list", sender: self) - } else { - self?.checkId() - } - } - } - } -} diff --git a/DGCAWallet/ViewControllers/HomeController.swift b/DGCAWallet/ViewControllers/HomeController.swift new file mode 100644 index 0000000..6accaea --- /dev/null +++ b/DGCAWallet/ViewControllers/HomeController.swift @@ -0,0 +1,69 @@ +// +/*- + * ---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 + */ +// +// HomeController.swift +// DGCAWallet +// +// Created by Yannick Spreen on 4/25/21. +// + +import UIKit +import SwiftDGC + +class HomeController: UIViewController { + private enum Constants { + static let scannerSegueID = "showMainList" + } + + @IBOutlet fileprivate weak var activityIndicator: UIActivityIndicatorView! + @IBOutlet fileprivate weak var appNameLabel: UILabel! + @IBOutlet fileprivate weak var messageLabel: UILabel! + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + + override func viewDidLoad() { + super.viewDidLoad() + CoreManager.shared.config = HCertConfig(prefetchAllCodes: true, checkSignatures: false, debugPrintJsonErrors: true) + appNameLabel.text = "Wallet App".localized + self.activityIndicator.startAnimating() + messageLabel.text = "Loading data".localized + DataCenter.initializeAllStorageData {[unowned self] result in + DispatchQueue.main.async { + self.activityIndicator.stopAnimating() + self.loadComplete() + } + } + } + + private func loadComplete() { + let renderer = UIGraphicsImageRenderer(size: self.view.bounds.size) + SecureBackground.image = renderer.image { rendererContext in + self.view.layer.render(in: rendererContext.cgContext) + } + if DataCenter.localDataManager.versionedConfig["outdated"].bool == true { + showAlert(title: "Update Available".localized, subtitle: "This version of the app is out of date.".localized) + } else { + performSegue(withIdentifier: Constants.scannerSegueID, sender: nil) + } + } +} diff --git a/DGCAWallet/ViewControllers/ImageViewerVC.xib b/DGCAWallet/ViewControllers/ImageViewerVC.xib deleted file mode 100644 index 4ef34dd..0000000 --- a/DGCAWallet/ViewControllers/ImageViewerVC.xib +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DGCAWallet/ViewControllers/List.swift b/DGCAWallet/ViewControllers/List.swift deleted file mode 100644 index b74d6bd..0000000 --- a/DGCAWallet/ViewControllers/List.swift +++ /dev/null @@ -1,623 +0,0 @@ -// -/*- - * ---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 - */ -// -// Home.swift -// DGCAWallet -// -// Created by Yannick Spreen on 4/25/21. -// - -// swiftlint:disable file_length - -import Foundation -import UIKit -import SwiftDGC -import FloatingPanel -import SwiftCBOR -import CoreImage -import PDFKit -import UniformTypeIdentifiers -import MobileCoreServices -import CoreServices - -class ListVC: UIViewController { - - private enum TableSection: Int { - case certificates = 0 - case images - case pdfs - static var count: Int { return 3} - } - - @IBOutlet weak var addButton: RoundedButton! - - var picker = UIImagePickerController() - var alert: UIAlertController? - var viewController: UIViewController? - var pickImageCallback: ((UIImage) -> Void)? - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if let cert = newHCertScanned { - newHCertScanned = nil - presentViewer(for: cert, isSaved: false) - } - } - - override var preferredStatusBarStyle: UIStatusBarStyle { - return .lightContent - } - - override func viewDidLoad() { - super.viewDidLoad() - - ImageDataStorage.initialize { - PdfDataStorage.initialize { - DispatchQueue.main.async { [weak self] in - self?.table.dataSource = self - self?.table.delegate = self - self?.reloadTable() - } - } - } - } - - @IBAction - func addNew() { - let menuActionSheet = UIAlertController(title: l10n("add.new"), - message: l10n("want.add"), - preferredStyle: UIAlertController.Style.actionSheet) - menuActionSheet.addAction(UIAlertAction(title: l10n("scan.certificate"), - style: UIAlertAction.Style.default, - handler: {[weak self] _ in - self?.scanNewCertificate() - })) - menuActionSheet.addAction(UIAlertAction(title: l10n("image.import"), - style: UIAlertAction.Style.default, - handler: { [weak self] _ in - self?.addImage() - })) - menuActionSheet.addAction(UIAlertAction(title: l10n("pdf.import"), - style: UIAlertAction.Style.default, - handler: { [weak self] _ in - self?.addPdf() - })) - menuActionSheet.addAction(UIAlertAction(title: l10n("nfc.import"), - style: UIAlertAction.Style.default, - handler: { [weak self] _ in - self?.scanNFC() - })) - - menuActionSheet.addAction(UIAlertAction(title: l10n("cancel"), - style: UIAlertAction.Style.destructive, - handler: nil)) - present(menuActionSheet, animated: true, completion: nil) - } - - private func scanNewCertificate() { - performSegue(withIdentifier: "scanner", sender: self) - } - - private func addImage() { - getImageFrom() - } - - private func addPdf() { - let pdfPicker: UIDocumentPickerViewController? - if #available(iOS 14.0, *) { - let supportedTypes: [UTType] = [UTType.pdf] - pdfPicker = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true) - } else { - pdfPicker = UIDocumentPickerViewController(documentTypes: [kUTTypePDF as String], in: .open) - } - guard let pdfPicker = pdfPicker else { - return - } - pdfPicker.delegate = self - present(pdfPicker, animated: true, completion: nil) - } - - private func scanNFC() { - let appDelegate = UIApplication.shared.delegate as? AppDelegate - appDelegate?.isNFCFunctionality = true - if #available(iOS 13.0, *) { - let scene = self.sceneDelegate - scene?.isNFCFunctionality = true - } - let helper = NFCHelper() - helper.onNFCResult = onNFCResult(success:msg:) - helper.restartSession() - } - - func onNFCResult(success: Bool, msg: String) { - DispatchQueue.main.async { [weak self] in - print("\(msg)") - let appDelegate = UIApplication.shared.delegate as? AppDelegate - appDelegate?.isNFCFunctionality = false - if #available(iOS 13.0, *) { - let scene = self?.sceneDelegate - scene?.isNFCFunctionality = false - } - if success, let hCert = HCert(from: msg, applicationType: .wallet) { - self?.saveQrCode(cert: hCert) - } else { - let alertController: UIAlertController = { - let controller = UIAlertController(title: l10n("error"), - message: l10n("read.dcc.from.nfc"), - preferredStyle: .alert) - let actionRetry = UIAlertAction(title: l10n("retry"), style: .default) { _ in - self?.scanNFC() - } - controller.addAction(actionRetry) - let actionOk = UIAlertAction(title: l10n("ok"), style: .default) - controller.addAction(actionOk) - return controller - }() - self?.viewController?.present(alertController, animated: true) - } - } - } - - @IBAction func settingsTapped(_ sender: UIButton) { - guard let settingsVC = UIStoryboard(name: "Settings", bundle: nil).instantiateInitialViewController(), - let viewer = settingsVC as? SettingsVC else { - return - } - viewer.childDismissedDelegate = self - showFloatingPanel(for: viewer) - } - - func showFloatingPanel(for controller: UIViewController) { - let fpc = FloatingPanelController() - fpc.set(contentViewController: controller) - fpc.isRemovalInteractionEnabled = true - fpc.layout = FullFloatingPanelLayout() - fpc.surfaceView.layer.cornerRadius = 24.0 - fpc.surfaceView.clipsToBounds = true - fpc.delegate = self - presentingViewer = controller - - present(fpc, animated: true, completion: nil) - } - - @IBOutlet weak var table: UITableView! - @IBOutlet weak var emptyView: UIView! - - func reloadTable() { - emptyView.alpha = listCertElements.isEmpty - && listImageElements.isEmpty - && listPdfElements.isEmpty ? 1 : 0 - table.reloadData() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - presentingViewer?.dismiss(animated: true, completion: nil) - } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - let appDelegate = UIApplication.shared.delegate as? AppDelegate - appDelegate?.isNFCFunctionality = false - if #available(iOS 13.0, *) { - let scene = self.sceneDelegate - scene?.isNFCFunctionality = false - } - } - - var presentingViewer: UIViewController? - var newHCertScanned: HCert? - - func presentViewer(for certificate: HCert, with tan: String? = nil, isSaved: Bool = true) { - guard - presentingViewer == nil, - let contentVC = UIStoryboard(name: "CertificateViewer", bundle: nil) - .instantiateInitialViewController(), - let viewer = contentVC as? CertificateViewerVC - else { - return - } - - viewer.isSaved = isSaved - viewer.hCert = certificate - viewer.tan = tan - viewer.childDismissedDelegate = self - let fpc = FloatingPanelController() - fpc.set(contentViewController: viewer) - fpc.isRemovalInteractionEnabled = true // Let it removable by a swipe-down - fpc.layout = FullFloatingPanelLayout() - fpc.surfaceView.layer.cornerRadius = 24.0 - fpc.surfaceView.clipsToBounds = true - fpc.delegate = self - presentingViewer = viewer - - present(fpc, animated: true, completion: nil) - UIImpactFeedbackGenerator(style: .medium).impactOccurred() - } - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - super.prepare(for: segue, sender: sender) - - if let scan = segue.destination as? ScanVC { - scan.delegate = self - return - } - } -} - -extension ListVC: CertViewerDelegate { - func childDismissed(_ newCertAdded: Bool) { - if newCertAdded { - reloadTable() - } - presentingViewer = nil - } -} - -extension ListVC: ScanVCDelegate { - func disableBackgroundDetection() { - SecureBackground.paused = true - } - - func enableBackgroundDetection() { - SecureBackground.paused = false - } - - func hCertScanned(_ cert: HCert) { - newHCertScanned = cert - DispatchQueue.main.async { [weak self] in - self?.navigationController?.popViewController(animated: true) - } - } -} - -extension ListVC: UITableViewDataSource { - var listCertElements: [DatedCertString] { - LocalData.sharedInstance.certStrings.reversed() - } - - var listImageElements: [SavedImage] { - ImageDataStorage.sharedInstance.images - } - - var listPdfElements: [SavedPDF] { - PdfDataStorage.sharedInstance.pdfs - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if section == TableSection.certificates.rawValue { - return listCertElements.count - } - if section == TableSection.images.rawValue { - return listImageElements.count - } - if section == TableSection.pdfs.rawValue { - return listPdfElements.count - } - return .zero - } - - func numberOfSections(in tableView: UITableView) -> Int { - TableSection.count - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - if section == TableSection.certificates.rawValue { - return l10n("section.certificates") - } - if section == TableSection.images.rawValue { - return l10n("section.images") - } - if section == TableSection.pdfs.rawValue { - return l10n("section.pdf") - } - return ":" - - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if indexPath.section == TableSection.certificates.rawValue { - let cell = table.dequeueReusableCell(withIdentifier: "walletCell", for: indexPath) - guard let walletCell = cell as? WalletCell else { - return cell - } - walletCell.draw(listCertElements[indexPath.row]) - return walletCell - } - if indexPath.section == TableSection.images.rawValue { - let cell = table.dequeueReusableCell(withIdentifier: "ImageTableViewCell", for: indexPath) - guard let imageCell = cell as? ImageTableViewCell else { - return cell - } - imageCell.setImage(image: listImageElements[indexPath.row]) - return imageCell - } - if indexPath.section == TableSection.pdfs.rawValue { - let cell = table.dequeueReusableCell(withIdentifier: "PDFTableViewCell", for: indexPath) - guard let imageCell = cell as? PDFTableViewCell else { - return cell - } - imageCell.setPDF(pdf: listPdfElements[indexPath.row]) - return imageCell - } - return UITableViewCell() - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - if indexPath.section == TableSection.certificates.rawValue { - return UITableView.automaticDimension - } else { - return 140 - } - } -} - -extension ListVC: UITableViewDelegate { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - table.deselectRow(at: indexPath, animated: true) - if indexPath.section == TableSection.certificates.rawValue { - guard - let cert = listCertElements[indexPath.row].cert - else { - return - } - presentViewer(for: cert, with: listCertElements[indexPath.row].storedTAN) - } - if indexPath.section == TableSection.images.rawValue { - showImage(image: listImageElements[indexPath.row]) - } - if indexPath.section == TableSection.pdfs.rawValue { - showPdfFile(pdf: listPdfElements[indexPath.row]) - } - return - } - - func tableView( - _ tableView: UITableView, - commit editingStyle: UITableViewCell.EditingStyle, - forRowAt indexPath: IndexPath - ) { - let cert = listCertElements[indexPath.row] - showAlert( - title: l10n("cert.delete.title"), - subtitle: l10n("cert.delete.body"), - actionTitle: l10n("btn.confirm"), - cancelTitle: l10n("btn.cancel") - ) { [weak self] in - if $0 { - LocalData.sharedInstance.certStrings.removeAll { - $0.date == cert.date - } - LocalData.sharedInstance.save() - self?.reloadTable() - } - } - } -} - -extension ListVC: FloatingPanelControllerDelegate { - func floatingPanel( - _ fpc: FloatingPanelController, - shouldRemoveAt location: CGPoint, - with velocity: CGVector - ) -> Bool { - let pos = location.y / view.bounds.height - if pos >= 0.33 { - return true - } - let threshold: CGFloat = 5.0 - switch fpc.layout.position { - case .top: - return (velocity.dy <= -threshold) - case .left: - return (velocity.dx <= -threshold) - case .bottom: - return (velocity.dy >= threshold) - case .right: - return (velocity.dx >= threshold) - } - } -} - -extension ListVC: UIImagePickerControllerDelegate, UINavigationControllerDelegate { - - private func getImageFrom() { - alert = UIAlertController(title: l10n("get.image.from"), message: nil, preferredStyle: .actionSheet) - let cameraAction = UIAlertAction(title: l10n("camera"), style: .default) {[weak self] _ in - self?.openCamera() - } - let galleryAction = UIAlertAction(title: l10n("galery"), style: .default) {[weak self] _ in - self?.openGallery() - } - let cancelAction = UIAlertAction(title: l10n("cancel"), style: .cancel) - - // Add the actions - picker.delegate = self - alert?.addAction(cameraAction) - alert?.addAction(galleryAction) - alert?.addAction(cancelAction) - guard let alert = alert else { return } - present(alert, animated: true, completion: nil) - } - - func pickImage(_ viewController: UIViewController, _ callback: @escaping ((UIImage) -> Void)) { - pickImageCallback = callback - self.viewController = viewController -// alert.popoverPresentationController?.sourceView = self.viewController!.view - } - func openCamera() { - alert?.dismiss(animated: true, completion: nil) - if UIImagePickerController.isSourceTypeAvailable(.camera) { - picker.sourceType = .camera - present(picker, animated: true, completion: nil) - } else { - let alertController: UIAlertController = { - let controller = UIAlertController(title: l10n("error"), - message: l10n("dont.have.camera"), - preferredStyle: .alert) - let action = UIAlertAction(title: l10n("ok"), style: .default) - controller.addAction(action) - return controller - }() - viewController?.present(alertController, animated: true) - } - } - func openGallery() { - alert?.dismiss(animated: true, completion: nil) - picker.sourceType = .photoLibrary - present(picker, animated: true, completion: nil) - } - - func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { - picker.dismiss(animated: true, completion: nil) - } - - func imagePickerController(_ picker: UIImagePickerController, - didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { - picker.dismiss(animated: true, completion: nil) - guard let image = info[.originalImage] as? UIImage else { - fatalError("Expected a dictionary containing an image, but was provided the following: \(info)") - } - tryFoundQRCodeIn(image: image) - } - - @objc func imagePickerController(_ picker: UIImagePickerController, pickedImage: UIImage?) { - guard let pickedImage = pickedImage else { return } - tryFoundQRCodeIn(image: pickedImage) - } - -} - -extension ListVC { - private func tryFoundQRCodeIn(image: UIImage) { - if let qrString = image.qrCodeString(), let hCert = HCert(from: qrString, applicationType: .wallet) { - saveQrCode(cert: hCert) - return - } - self.saveImage(image: image) - } - - private func saveQrCode(cert: HCert) { - presentViewer(for: cert, with: nil, isSaved: false) - } - - private func saveImage(image: UIImage) { - showInputDialog( - title: l10n("image.confirm.title"), - subtitle: l10n("image.confirm.text"), - inputPlaceholder: l10n("image.confirm.placeholder") - ) { fileName in - ImageDataStorage.sharedInstance.add(savedImage: SavedImage(fileName: fileName ?? UUID().uuidString, image: image)) - DispatchQueue.main.async { [weak self] in - self?.reloadTable() - } - } - } -} - -extension ListVC: UIDocumentPickerDelegate { - func convertPDF(at sourceURL: URL, dpi: CGFloat = 200) throws -> [UIImage] { - let pdfDocument = CGPDFDocument(sourceURL as CFURL)! - let colorSpace = CGColorSpaceCreateDeviceRGB() - let bitmapInfo = CGImageAlphaInfo.noneSkipLast.rawValue - var images = [UIImage]() - DispatchQueue.concurrentPerform(iterations: pdfDocument.numberOfPages) { index in - // Page number starts at 1, not 0 - let pdfPage = pdfDocument.page(at: index + 1)! - let mediaBoxRect = pdfPage.getBoxRect(.mediaBox) - let scale = dpi / 72.0 - let width = Int(mediaBoxRect.width * scale) - let height = Int(mediaBoxRect.height * scale) - let context = CGContext(data: nil, - width: width, - height: height, - bitsPerComponent: 8, - bytesPerRow: 0, - space: colorSpace, - bitmapInfo: bitmapInfo)! - context.interpolationQuality = .high - context.setFillColor(UIColor.white.cgColor) - context.fill(CGRect(x: 0, y: 0, width: width, height: height)) - context.scaleBy(x: scale, y: scale) - context.drawPDFPage(pdfPage) - let image = context.makeImage()! - images.append(UIImage(cgImage: image)) - } - return images - } - - func checkQRCodesInPDFFile(url: NSURL) { - let images = try? convertPDF(at: url as URL) - if images != nil && !(images?.isEmpty ?? true) { - for image in images! { - if let qrString = image.qrCodeString(), let hCert = HCert(from: qrString, applicationType: .wallet) { - saveQrCode(cert: hCert) - return - } - } - savePDFFile(url: url) - } else { - savePDFFile(url: url) - } - } - - func savePDFFile(url: NSURL) { - showInputDialog( - title: l10n("pdf.confirm.title"), - subtitle: l10n("pdf.confirm.text"), - inputPlaceholder: l10n("pdf.confirm.placeholder") - ) { fileName in - PdfDataStorage.sharedInstance.add(savedPdf: SavedPDF(fileName: fileName ?? UUID().uuidString, pdfUrl: url as URL)) - DispatchQueue.main.async { [weak self] in - self?.reloadTable() - } - - } - } - - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - if controller.documentPickerMode == UIDocumentPickerMode.import { - guard let url = urls.first else { - return - } - checkQRCodesInPDFFile(url: url as NSURL) - } - } - private func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL) { - if controller.documentPickerMode == UIDocumentPickerMode.import { - checkQRCodesInPDFFile(url: url) - } - } -} - -extension ListVC { - func showImage(image: SavedImage) { - let imageVC = ImageViewerVC.loadFromNib() - imageVC.setImage(image: image) - present(imageVC, animated: true) - } - func showPdfFile(pdf: SavedPDF) { - let pdfVC = PDFViewerVC.loadFromNib() - pdfVC.setPDF(pdf: pdf) - present(pdfVC, animated: true) - } -} diff --git a/DGCAWallet/ViewControllers/MainListController.swift b/DGCAWallet/ViewControllers/MainListController.swift new file mode 100644 index 0000000..56c931c --- /dev/null +++ b/DGCAWallet/ViewControllers/MainListController.swift @@ -0,0 +1,701 @@ +// +/*- + * ---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 + */ +// +// MainListController.swift +// DGCAWallet +// +// Created by Yannick Spreen on 4/25/21. +// + +// swiftlint:disable file_length + +import UIKit +import SwiftDGC +import UniformTypeIdentifiers +import MobileCoreServices + + +class MainListController: UIViewController { + fileprivate enum SegueIdentifiers { + static let showScannerSegue = "showScannerSegue" + static let showServicesList = "showServicesList" + static let showSettingsController = "showSettingsController" + static let showCertificateViewer = "showCertificateViewer" + static let showPDFViewer = "showPDFViewer" + static let showImageViewer = "showImageViewer" + } + + private enum TableSection: Int, CaseIterable { + case certificates, images, pdfs + } + + @IBOutlet fileprivate weak var addButton: RoundedButton! + @IBOutlet fileprivate weak var table: UITableView! + @IBOutlet fileprivate weak var emptyView: UIView! + @IBOutlet fileprivate weak var activityIndicator: UIActivityIndicatorView! + @IBOutlet fileprivate weak var titleLabel: UILabel! + + lazy var indicator: UIActivityIndicatorView = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.medium) + + lazy var activityAlert: UIAlertController = { + let controller = UIAlertController(title: "Loading data", message: "\n\n\n", preferredStyle: .alert) + + controller.view.addSubview(indicator) + return controller + }() + + var downloadedDataHasExpired: Bool { + return DataCenter.lastFetch.timeIntervalSinceNow < -SharedConstants.expiredDataInterval + } + + private var expireDataTimer: Timer? + private var scannedToken: String = "" + private var loading = false + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + + override func viewDidLoad() { + super.viewDidLoad() + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + self.titleLabel.text = "Certificate Wallet".localized + self.addButton.setTitle("Add New".localized, for: .normal) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + let appDelegate = UIApplication.shared.delegate as? AppDelegate + appDelegate?.isNFCFunctionality = false + if #available(iOS 13.0, *) { + let scene = self.sceneDelegate + scene?.isNFCFunctionality = false + } + expireDataTimer = Timer.scheduledTimer(timeInterval: 1800, target: self, selector: #selector(reloadExpiredData), + userInfo: nil, repeats: true) + + self.reloadTable() + } + + // MARK: - Actions + @objc func reloadExpiredData() { + if downloadedDataHasExpired { + showAlertReloadDatabase() + } + } + + func showAlertReloadDatabase() { + let alert = UIAlertController(title: "Reload data?".localized, message: "The update may take some time.".localized, preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: "Later".localized, style: .default, handler: { _ in })) + + alert.addAction(UIAlertAction(title: "Reload".localized, style: .default, handler: { (_: UIAlertAction!) in + DataCenter.reloadStorageData(completion: { _ in }) + })) + self.present(alert, animated: true, completion: nil) + } + + // MARK: - Private UI methods + private func reloadAllComponents(completion: @escaping DataCompletionHandler) { + DataCenter.initializeAllStorageData { result in + completion(.success(true)) + } + } + + private func startActivity() { + loading = true + activityIndicator.startAnimating() + addButton.isEnabled = false + addButton.backgroundColor = UIColor.walletLightBlue + } + + private func stopActivity() { + loading = false + activityIndicator.stopAnimating() + addButton.isEnabled = true + addButton.backgroundColor = UIColor.walletBlue + } + + private func reloadTable() { + emptyView.alpha = listCertElements.isEmpty && listImageElements.isEmpty && listPdfElements.isEmpty ? 1 : 0 + table.reloadData() + } + + // MARK: Actions + @IBAction func addNew() { + guard loading == false else { return } + + let menuActionSheet = UIAlertController(title: "Add new?".localized, message: "Do you want to add new certificate, image or PDF file?".localized, + preferredStyle: UIAlertController.Style.actionSheet) + + menuActionSheet.addAction(UIAlertAction(title: "Scan certificate".localized, style: UIAlertAction.Style.default, + handler: {[weak self] _ in + self?.scanNewCertificate() + }) + ) + menuActionSheet.addAction(UIAlertAction(title: "Image import".localized, style: UIAlertAction.Style.default, + handler: { [weak self] _ in + self?.addImageActivity() + }) + ) + menuActionSheet.addAction(UIAlertAction(title: "PDF Import".localized, style: UIAlertAction.Style.default, + handler: { [weak self] _ in + self?.addPdf() + }) + ) + menuActionSheet.addAction(UIAlertAction(title: "NFC Import".localized, style: UIAlertAction.Style.default, + handler: { [weak self] _ in + self?.scanNFC() + }) + ) + menuActionSheet.addAction(UIAlertAction(title: "Cancel".localized, style: UIAlertAction.Style.cancel, handler: nil)) + present(menuActionSheet, animated: true, completion: nil) + } + + func onNFCResult(success: Bool, message: String) { + let barcodeString = message + guard success, !barcodeString.isEmpty else { return } + + DispatchQueue.main.async { [weak self] in + DGCLogger.logInfo("\(message)") + let appDelegate = UIApplication.shared.delegate as? AppDelegate + appDelegate?.isNFCFunctionality = false + if #available(iOS 13.0, *) { + let scene = self?.sceneDelegate + scene?.isNFCFunctionality = false + } + + do { + let hCert = try HCert(from: barcodeString) + self?.saveQrCode(cert: hCert) + + } catch { + let alertController: UIAlertController = { + let controller = UIAlertController(title: "Cannot read NFC".localized, + message: "An error occurred while reading NFC".localized, preferredStyle: .alert) + + let actionRetry = UIAlertAction(title: "Retry".localized, style: .default) { _ in + self?.scanNFC() + } + controller.addAction(actionRetry) + + let actionOk = UIAlertAction(title: "OK".localized, style: .default) + controller.addAction(actionOk) + return controller + }() + self?.present(alertController, animated: true) + } + } + } + + @IBAction func settingsTapped(_ sender: UIButton) { + self.performSegue(withIdentifier: SegueIdentifiers.showSettingsController, sender: nil) + } + + // MARK: Tasks + private func scanNewCertificate() { + performSegue(withIdentifier: SegueIdentifiers.showScannerSegue, sender: nil) + } + + private func addPdf() { + let pdfPicker: UIDocumentPickerViewController + if #available(iOS 14.0, *) { + let supportedTypes: [UTType] = [UTType.pdf] + pdfPicker = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true) + } else { + pdfPicker = UIDocumentPickerViewController(documentTypes: [kUTTypePDF as String], in: .open) + } + pdfPicker.delegate = self + present(pdfPicker, animated: true, completion: nil) + } + + private func scanNFC() { + let appDelegate = UIApplication.shared.delegate as? AppDelegate + appDelegate?.isNFCFunctionality = true + if #available(iOS 13.0, *) { + let scene = self.sceneDelegate + scene?.isNFCFunctionality = true + } + let helper = NFCHelper() + helper.onNFCResult = onNFCResult(success:message:) + helper.restartSession() + } + + // MARK: Navifation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + switch segue.identifier { + case SegueIdentifiers.showScannerSegue: + guard let scanController = segue.destination as? ScanWalletController else { return } + scanController.modalPresentationStyle = .fullScreen + scanController.delegate = self + + case SegueIdentifiers.showSettingsController: + guard let navController = segue.destination as? UINavigationController, + let _ = navController.viewControllers.first as? SettingsTableController else { return } + + case SegueIdentifiers.showServicesList: + guard let serviceController = segue.destination as? ServerListController else { return } + guard let listOfServices = sender as? ServerListResponse else { return } + serviceController.serverListInfo = listOfServices + + + case SegueIdentifiers.showPDFViewer: + guard let serviceController = segue.destination as? PDFViewerController else { return } + guard let pdf = sender as? SavedPDF else { return } + serviceController.setPDF(pdf: pdf) + + case SegueIdentifiers.showImageViewer: + guard let serviceController = segue.destination as? ImageViewerController else { return } + guard let savedImage = sender as? SavedImage else { return } + serviceController.setImage(image: savedImage) + + + case SegueIdentifiers.showCertificateViewer: + guard let serviceController = segue.destination as? CertificateViewerController else { return } + if let savedCertificate = sender as? DatedCertString { + serviceController.hCert = savedCertificate.cert + serviceController.isSaved = true + serviceController.certDate = savedCertificate.date + serviceController.tan = savedCertificate.storedTAN + } else if let certificate = sender as? HCert { + serviceController.hCert = certificate + serviceController.isSaved = false + } + + serviceController.delegate = self + + default: + break + } + } +} + +// MARK: - ScanWalletDelegate +extension MainListController: ScanWalletDelegate { + func walletController(_ controller: ScanWalletController, didFailWithError error: CertificateParsingError) { + DispatchQueue.main.async { + self.showInfoAlert(withTitle: "Barcode reading Error".localized, message: "Something went wrong.".localized) + } + } + + func disableBackgroundDetection() { + SecureBackground.paused = true + } + + func enableBackgroundDetection() { + SecureBackground.paused = false + } + + func walletController(_ controller: ScanWalletController, didScanCertificate certificate: HCert) { + DispatchQueue.main.async { [weak self] in + self?.dismiss(animated: true, completion: { + self?.performSegue(withIdentifier: SegueIdentifiers.showCertificateViewer, sender: certificate) + }) + } + } + + func walletController(_ controller: ScanWalletController, didScanInfo ticketing: SwiftDGC.CheckInQR) { + if scannedToken == ticketing.token { + return + } + scannedToken = ticketing.token + startActivity() + IdentityService.requestListOfServices(ticketingInfo: ticketing) { [weak self] services, error in + guard error == nil else { + self?.showInfoAlert(withTitle: "This certificate is not supported".localized, message: "") + return + } + DispatchQueue.main.async { + self?.stopActivity() + self?.dismiss(animated: true, completion: { + self?.scannedToken = "" + self?.performSegue(withIdentifier: SegueIdentifiers.showServicesList, sender: services) + }) + } + } + } +} + +// MARK: CertificateManaging +extension MainListController: CertificateManaging { + func certificateViewer(_ controller: CertificateViewerController, didDeleteCertificate cert: HCert) { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(150)) { + self.reloadTable() + } + } + + func certificateViewer(_ controller: CertificateViewerController, didAddCeCertificate cert: HCert) { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(150)) { + self.reloadTable() + } + } +} + +// MARK: UITable delegate +extension MainListController: UITableViewDelegate, UITableViewDataSource { + var listCertElements: [DatedCertString] { + return DataCenter.certStrings.reversed() + } + + var listImageElements: [SavedImage] { + return DataCenter.images + } + + var listPdfElements: [SavedPDF] { + return DataCenter.pdfs + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + switch section { + case TableSection.certificates.rawValue: + return listCertElements.count + case TableSection.images.rawValue: + return listImageElements.count + case TableSection.pdfs.rawValue: + return listPdfElements.count + default: + return .zero + } + } + + func numberOfSections(in tableView: UITableView) -> Int { + return TableSection.allCases.count + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + switch section { + case TableSection.certificates.rawValue: + return !listCertElements.isEmpty ? "Certificates".localized : nil + case TableSection.images.rawValue: + return !listImageElements.isEmpty ? "Images".localized : nil + case TableSection.pdfs.rawValue: + return !listPdfElements.isEmpty ? "PDF files".localized : nil + default: + return nil + } + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + switch indexPath.section { + case TableSection.certificates.rawValue: + guard let walletCell = table.dequeueReusableCell(withIdentifier: "walletCell", for: indexPath) as? WalletCell + else { return UITableViewCell() } + + walletCell.setupCell(listCertElements[indexPath.row]) + return walletCell + + case TableSection.images.rawValue: + guard let imageCell = table.dequeueReusableCell(withIdentifier: "ImageTableViewCell", for: indexPath) as? ImageTableViewCell + else { return UITableViewCell() } + + imageCell.setImage(image: listImageElements[indexPath.row]) + return imageCell + + case TableSection.pdfs.rawValue: + guard let imageCell = table.dequeueReusableCell(withIdentifier: "PDFTableViewCell", for: indexPath) as? PDFTableViewCell + else { return UITableViewCell() } + + imageCell.setPDF(pdf: listPdfElements[indexPath.row]) + return imageCell + + default: + return UITableViewCell() + } + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard loading == false else { return } + + table.deselectRow(at: indexPath, animated: true) + switch indexPath.section { + case TableSection.certificates.rawValue: + self.performSegue(withIdentifier: SegueIdentifiers.showCertificateViewer, sender: listCertElements[indexPath.row]) + + case TableSection.images.rawValue: + self.performSegue(withIdentifier: SegueIdentifiers.showImageViewer, sender: listImageElements[indexPath.row]) + + case TableSection.pdfs.rawValue: + self.performSegue(withIdentifier: SegueIdentifiers.showPDFViewer, sender: listPdfElements[indexPath.row]) + + default: + break + } + } + + func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + switch indexPath.section { + case TableSection.certificates.rawValue: + let savedCert = listCertElements[indexPath.row] + showAlert( title: "Delete Certificate".localized, subtitle: "cert.delete.body".localized, + actionTitle: "Confirm".localized, cancelTitle: "Cancel".localized) { [weak self] in + if $0 { + self?.startActivity() + DataCenter.localDataManager.remove(withDate: savedCert.date) { _ in + DispatchQueue.main.async { + self?.stopActivity() + self?.reloadTable() + } + } // LocalData + } + } + + case TableSection.images.rawValue: + let savedImage = listImageElements[indexPath.row] + showAlert( title: "Delete Certificate".localized, subtitle: "cert.delete.body".localized, + actionTitle: "Confirm".localized, cancelTitle:"Cancel".localized) { [weak self] in + if $0 { + self?.startActivity() + DataCenter.imageDataManager.deleteImage(with: savedImage.identifier) { _ in + DispatchQueue.main.async { + self?.stopActivity() + self?.reloadTable() + } + } + } + } + case TableSection.pdfs.rawValue: + let savedPDF = listPdfElements[indexPath.row] + showAlert( title: "Delete Certificate".localized, subtitle: "cert.delete.body".localized, + actionTitle: "Confirm".localized, cancelTitle: "Cancel".localized) { [weak self] in + if $0 { + self?.startActivity() + DataCenter.pdfDataManager.deletePDF(with: savedPDF.identifier) { _ in + DispatchQueue.main.async { + self?.stopActivity() + self?.reloadTable() + } + } + } + } + default: + break + } + } +} + +// MARK: UIImagePicker delegate +extension MainListController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { + private func addImageActivity() { + let alert = UIAlertController(title: "Get Image from".localized, message: nil, preferredStyle: .actionSheet) + let cameraAction = UIAlertAction(title: "Camera".localized, style: .default) {[weak self] _ in + alert.dismiss(animated: true, completion: nil) + self?.openCamera() + } + let galleryAction = UIAlertAction(title: "Gallery".localized, style: .default) {[weak self] _ in + alert.dismiss(animated: true, completion: nil) + self?.openGallery() + } + let cancelAction = UIAlertAction(title: "Cancel".localized, style: .cancel) + + // Add the actions + alert.addAction(cameraAction) + alert.addAction(galleryAction) + alert.addAction(cancelAction) + present(alert, animated: true, completion: nil) + } + + private func openCamera() { + if UIImagePickerController.isSourceTypeAvailable(.camera) { + let picker = UIImagePickerController() + picker.delegate = self + picker.sourceType = .camera + present(picker, animated: true, completion: nil) + } else { + let alertController: UIAlertController = { + let controller = UIAlertController(title:"Cannot scan".localized, message: "You don't have a camera.".localized, + preferredStyle: .alert) + let action = UIAlertAction(title: "OK".localized, style: .default) + controller.addAction(action) + return controller + }() + self.present(alertController, animated: true) + } + } + + func openGallery() { + let picker = UIImagePickerController() + picker.delegate = self + picker.sourceType = .photoLibrary + present(picker, animated: true, completion: nil) + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + picker.dismiss(animated: true, completion: nil) + } + + func imagePickerController(_ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { + picker.dismiss(animated: true, completion: nil) + guard let image = info[.originalImage] as? UIImage else { + fatalError("Expected a dictionary containing an image, but was provided the following: \(info)") + } + tryFoundQRCodeIn(image: image) + } + + @objc func imagePickerController(_ picker: UIImagePickerController, pickedImage: UIImage?) { + guard let pickedImage = pickedImage else { return } + tryFoundQRCodeIn(image: pickedImage) + } +} + +// MARK: QR Code, PDF. Image sources +extension MainListController { + private func tryFoundQRCodeIn(image: UIImage) { + if let qrString = image.qrCodeString() { + do { + let hCert = try HCert(from: qrString) + self.saveQrCode(cert: hCert) + } catch { + } + + } else { + self.saveImage(image: image) + } + } + + private func saveQrCode(cert: HCert) { + self.performSegue(withIdentifier: SegueIdentifiers.showCertificateViewer, sender: cert) + } + + private func saveImage(image: UIImage) { + showInputDialog(title: "Save image".localized, subtitle: "Please enter the image name".localized, + inputPlaceholder: "filename".localized) { [weak self] fileName in + let savedImg = SavedImage(fileName: fileName ?? UUID().uuidString, image: image) + + self?.startActivity() + DataCenter.imageDataManager.add(savedImage: savedImg) { _ in + DispatchQueue.main.async { + self?.stopActivity() + self?.table.reloadData() + let rowCount = DataCenter.images.count + if rowCount > 0 { + let scrollToNum = rowCount - 1 + let path = IndexPath(row: scrollToNum, section: TableSection.images.rawValue) + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(150)) { + self?.table.scrollToRow(at: path, at: .bottom, animated: true) + self?.table.selectRow(at: path, animated: true, scrollPosition: .bottom) + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(350)) { + self?.table.deselectRow(at: path, animated: true) + } + } + } + } + } // end add + } + } +} + +// MARK: PDF Document Delegate +extension MainListController: UIDocumentPickerDelegate { + private func convertPDF(at sourceURL: URL, dpi: CGFloat = 200) throws -> [UIImage] { + let pdfDocument = CGPDFDocument(sourceURL as CFURL)! + let colorSpace = CGColorSpaceCreateDeviceRGB() + let bitmapInfo = CGImageAlphaInfo.noneSkipLast.rawValue + var images = [UIImage]() + DispatchQueue.concurrentPerform(iterations: pdfDocument.numberOfPages) { index in + // Page number starts at 1, not 0 + let pdfPage = pdfDocument.page(at: index + 1)! + let mediaBoxRect = pdfPage.getBoxRect(.mediaBox) + let scale = dpi / 72.0 + let width = Int(mediaBoxRect.width * scale) + let height = Int(mediaBoxRect.height * scale) + let context = CGContext(data: nil, + width: width, + height: height, + bitsPerComponent: 8, + bytesPerRow: 0, + space: colorSpace, + bitmapInfo: bitmapInfo)! + context.interpolationQuality = .high + context.setFillColor(UIColor.white.cgColor) + context.fill(CGRect(x: 0, y: 0, width: width, height: height)) + context.scaleBy(x: scale, y: scale) + context.drawPDFPage(pdfPage) + let image = context.makeImage()! + images.append(UIImage(cgImage: image)) + } + return images + } + + private func checkQRCodesInPDFFile(url: NSURL) { + guard let images = try? convertPDF(at: url as URL), !images.isEmpty else { + savePDFFile(url: url) + return + } + for image in images { + if let qrString = image.qrCodeString() { + do { + let hCert = try HCert(from: qrString) + self.saveQrCode(cert: hCert) + } catch { + savePDFFile(url: url) + } + return + } + } + savePDFFile(url: url) + } + + private func savePDFFile(url: NSURL) { + showInputDialog(title: "Save PDF file".localized, subtitle: "Please enter the pdf file name".localized, + inputPlaceholder: "filename".localized) { [weak self] fileName in + let pdf = SavedPDF(fileName: fileName ?? UUID().uuidString, pdfUrl: url as URL) + + self?.startActivity() + DataCenter.pdfDataManager.add(savedPdf: pdf) { _ in + DispatchQueue.main.async { + self?.stopActivity() + self?.table.reloadData() + let rowsCount = DataCenter.pdfs.count + if rowsCount > 0 { + let scrollToNum = rowsCount-1 + let path = IndexPath(row: scrollToNum, section: TableSection.pdfs.rawValue) + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(150)) { + self?.table.scrollToRow(at: path, at: .bottom, animated: true) + self?.table.selectRow(at: path, animated: true, scrollPosition: .bottom) + // let's add time for app to scroll down (0.35 sec) + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(350)) { + self?.table.deselectRow(at: path, animated: true) + } + } + } + } + } // end add + } // end alert action + } + + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + guard let url = urls.first else { return } + + if controller.documentPickerMode == .import { + checkQRCodesInPDFFile(url: url as NSURL) + } + } + + private func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL) { + if controller.documentPickerMode == .import { + checkQRCodesInPDFFile(url: url) + } + } +} diff --git a/DGCAWallet/ViewControllers/PDFViewerVC.xib b/DGCAWallet/ViewControllers/PDFViewerVC.xib deleted file mode 100644 index dc7ac71..0000000 --- a/DGCAWallet/ViewControllers/PDFViewerVC.xib +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DGCAWallet/ViewControllers/RuleValidationResultVC.swift b/DGCAWallet/ViewControllers/RuleValidationResultVC.swift deleted file mode 100644 index 84e9b80..0000000 --- a/DGCAWallet/ViewControllers/RuleValidationResultVC.swift +++ /dev/null @@ -1,235 +0,0 @@ -// -/*- - * ---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 - resultDescriptionLabel.sizeToFit() - noWarrantyLabel.sizeToFit() - } -} - -extension RuleValidationResultVC: UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - // Please uncomment this if you need to show message about no rules for selected country -// 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 deleted file mode 100644 index fe37e5f..0000000 --- a/DGCAWallet/ViewControllers/RuleValidationResultVC.xib +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DGCAWallet/ViewControllers/ScanWalletController.swift b/DGCAWallet/ViewControllers/ScanWalletController.swift new file mode 100644 index 0000000..2fe9708 --- /dev/null +++ b/DGCAWallet/ViewControllers/ScanWalletController.swift @@ -0,0 +1,235 @@ +// +// File.swift +// +// +// Created by Igor Khomiak on 11.10.2021. +// + +import UIKit +import SwiftDGC +import Vision +import AVFoundation + + protocol ScanWalletDelegate: AnyObject { + func walletController(_ controller: ScanWalletController, didScanCertificate certificate: HCert) + func walletController(_ controller: ScanWalletController, didScanInfo info: CheckInQR) + func walletController(_ controller: ScanWalletController, didFailWithError error: CertificateParsingError) + func disableBackgroundDetection() + func enableBackgroundDetection() +} + +class ScanWalletController: UIViewController { + + private var captureSession: AVCaptureSession? + weak var delegate: ScanWalletDelegate? + + lazy var detectBarcodeRequest = VNDetectBarcodesRequest { request, error in + guard error == nil else { + self.showAlert(withTitle: "Barcode reading Error".localized, + message: error?.localizedDescription ?? "Something went wrong.".localized) + return + } + self.processClassification(request) + } + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return .portrait + } + + private var camView: UIView! + + override func viewDidLoad() { + super.viewDidLoad() + camView = UIView(frame: .zero) + camView.translatesAutoresizingMaskIntoConstraints = false + camView.isUserInteractionEnabled = false + view.addSubview(camView) + NSLayoutConstraint.activate([ + camView.topAnchor.constraint(equalTo: view.topAnchor), + camView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + camView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + camView.trailingAnchor.constraint(equalTo: view.trailingAnchor) + ]) + + view.backgroundColor = .init(white: 0, alpha: 1) +#if targetEnvironment(simulator) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + // swiftlint:disable:next line_length + self.observationHandler(payloadString: """ +{ + "protocol": "DCCVALIDATION", + "protocolVersion": "1.0.0", + "serviceIdentity": "https://dgca-booking-demo-eu-test.cfapps.eu10.hana.ondemand.com/api/identity", + "privacyUrl": "https://validation-decorator.example", + "token": "eyJ0eXAiOiJKV1QiLCJraWQiOiJiUzhEMi9XejV0WT0iLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJodHRwczovL2RnY2EtYm9va2luZy1kZW1vLWV1LXRlc3QuY2ZhcHBzLmV1MTAuaGFuYS5vbmRlbWFuZC5jb20vYXBpL2lkZW50aXR5IiwiZXhwIjoxNjM2NjIzMTA0LCJzdWIiOiI0NmM3N2YwOS1kMGI1LTQ1NjUtYTY5NC01ZDkyMzk3NmI4M2YifQ.5EpHoZ-NBtsjI9h5encROPNGzU7MUcGFpJobffjrVsswFJTidKS2XT3PGFj3HUUvufZQRRurDbZKwOHBkzXyIA", + "consent": "Please confirm to start the DCC Exchange flow. If you not confirm, the flow is aborted.", + "subject": "46c77f09-d0b5-4565-a694-5d923976b83f", + "serviceProvider": "Booking Demo" +} +""") + } +#else + captureSession = AVCaptureSession() + checkPermissions() + setupCameraLiveView() +#endif + SquareViewFinder.create(from: self) + createDismissButton() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + captureSession?.stopRunning() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + captureSession?.startRunning() + } + + private func createDismissButton() { + let button = UIButton(frame: .zero) + button.translatesAutoresizingMaskIntoConstraints = false + button.backgroundColor = .clear + button.setAttributedTitle( + NSAttributedString(string: "Cancel".localized, attributes: [.font: UIFont.systemFont(ofSize: 22, + weight: .semibold), .foregroundColor: UIColor.white]), for: .normal) + button.addTarget(self, action: #selector(dismissScaner), for: .touchUpInside) + view.addSubview(button) + + NSLayoutConstraint.activate([ + button.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16.0), + button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16.0) + ]) + } + + @objc func dismissScaner() { + self.dismiss(animated: true, completion: nil) + } +} + +extension ScanWalletController { + private func checkPermissions() { + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .notDetermined: + delegate?.disableBackgroundDetection() + AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in + self?.delegate?.enableBackgroundDetection() + if !granted { + self?.showPermissionsAlert() + } + } + case .denied, .restricted: + showPermissionsAlert() + default: + break + } + } + + private func setupCameraLiveView() { + captureSession?.sessionPreset = .hd1280x720 + + let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) + + guard let device = videoDevice, + let videoDeviceInput = try? AVCaptureDeviceInput(device: device), + captureSession?.canAddInput(videoDeviceInput) == true + else { + showAlert( withTitle: "No camera available".localized, message: "The app cannot access the camera".localized) + return + } + + captureSession?.addInput(videoDeviceInput) + + let captureOutput = AVCaptureVideoDataOutput() + captureOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)] + captureOutput.setSampleBufferDelegate(self, queue: DispatchQueue.global(qos: DispatchQoS.QoSClass.default)) + captureSession?.addOutput(captureOutput) + + configurePreviewLayer() + } + + private func processClassification(_ request: VNRequest) { + + guard let barcodes = request.results else { return } + DispatchQueue.main.async { [self] in + if captureSession?.isRunning == true { + camView.layer.sublayers?.removeSubrange(1...) + + if let barcode = barcodes.first { + var potentialQRCode: VNBarcodeObservation + if #available(iOS 15, *) { + guard let potentialCode = barcode as? VNBarcodeObservation, + [.Aztec, .QR, .DataMatrix].contains(potentialCode.symbology), + potentialCode.confidence > 0.9 + else { return } + potentialQRCode = potentialCode + } else { + guard let potentialCode = barcode as? VNBarcodeObservation, + [.aztec, .qr, .dataMatrix].contains(potentialCode.symbology), + potentialCode.confidence > 0.9 + else { return } + potentialQRCode = potentialCode + } + DGCLogger.logInfo(potentialQRCode.symbology.rawValue.description) + observationHandler(payloadString: potentialQRCode.payloadStringValue) + } + } + } + } + + private func observationHandler(payloadString: String?) { + guard let barcodeString = payloadString, !barcodeString.isEmpty else { return } + + if let hCert = try? HCert(from: barcodeString) { + delegate?.walletController(self, didScanCertificate: hCert) + + } else if let payloadData = (payloadString ?? "").data(using: .utf8), + let ticketing = try? JSONDecoder().decode(CheckInQR.self, from: payloadData) { + delegate?.walletController(self, didScanInfo: ticketing) + } else { + DGCLogger.logInfo("Error when validating the certificate? \(barcodeString)") + delegate?.walletController(self, didFailWithError: CertificateParsingError.unknown) + } + } +} + +extension ScanWalletController: AVCaptureVideoDataOutputSampleBufferDelegate { + func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, + from connection: AVCaptureConnection) { + guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } + + let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: .right) + do { + try imageRequestHandler.perform([detectBarcodeRequest]) + } catch { + DGCLogger.logError(error) + } + } +} + +extension ScanWalletController { + private func configurePreviewLayer() { + guard let captureSession = captureSession else { return } + + let cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + cameraPreviewLayer.videoGravity = .resizeAspectFill + cameraPreviewLayer.connection?.videoOrientation = .portrait + cameraPreviewLayer.frame = view.frame + camView.layer.insertSublayer(cameraPreviewLayer, at: 0) + } + + private func showAlert(withTitle title: String, message: String) { + DispatchQueue.main.async { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK".localized, style: .default)) + self.present(alertController, animated: true) + } + } + + private func showPermissionsAlert() { + showAlert( withTitle: "Camera Permissions".localized, + message: "Please open Settings and grant permission for this app to use your camera.".localized + ) + } +} diff --git a/DGCAWallet/ViewControllers/Settings.swift b/DGCAWallet/ViewControllers/Settings.swift deleted file mode 100644 index 7ff30ad..0000000 --- a/DGCAWallet/ViewControllers/Settings.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -/*- - * ---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 - */ -// -// Settings.swift -// DGCAWallet -// -// Created by Paul Ballmann on 14.05.21. -// - -import Foundation -import UIKit -import FloatingPanel -import SwiftDGC - -class SettingsVC: UINavigationController { - - weak var childDismissedDelegate: CertViewerDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - - additionalSafeAreaInsets.top = 16.0 - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - - childDismissedDelegate?.childDismissed(false) - } -} - -class SettingsTableVC: UITableViewController { - - @IBAction - func cancelButton() { - dismiss(animated: true, completion: nil) - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if indexPath.row == 0 { - openPrivacyDoc() - } - } - - func openPrivacyDoc() { - let link = LocalData.sharedInstance.versionedConfig["privacyUrl"].string ?? "" - openUrl(link) - } - - func openEuCertDoc() { - let link = "https://ec.europa.eu/health/ehealth/covid-19_en" - openUrl(link) - } - - func openUrl(_ string: String!) { - if let url = URL(string: string) { - UIApplication.shared.open(url) - } - } -} diff --git a/DGCAWallet/ViewControllers/LicenseVC.swift b/DGCAWallet/ViewControllers/SettingsControllers/LicenseController.swift similarity index 83% rename from DGCAWallet/ViewControllers/LicenseVC.swift rename to DGCAWallet/ViewControllers/SettingsControllers/LicenseController.swift index be30061..b09b2d4 100644 --- a/DGCAWallet/ViewControllers/LicenseVC.swift +++ b/DGCAWallet/ViewControllers/SettingsControllers/LicenseController.swift @@ -19,7 +19,7 @@ * ---license-end */ // -// LicenseVC.swift +// LicenseController.swift // DGCAWallet // // Created by Paul Ballmann on 19.05.21. @@ -29,10 +29,10 @@ import UIKit import SwiftyJSON import WebKit -class LicenseVC: UIViewController, WKNavigationDelegate { - @IBOutlet weak var packageNameLabel: UILabel! - @IBOutlet weak var licenseWebView: WKWebView! - @IBOutlet weak var activityIndicator: UIActivityIndicatorView! +class LicenseController: UIViewController, WKNavigationDelegate { + @IBOutlet fileprivate weak var packageNameLabel: UILabel! + @IBOutlet fileprivate weak var licenseWebView: WKWebView! + @IBOutlet fileprivate weak var activityIndicator: UIActivityIndicatorView! var licenseObject: JSON = [] @@ -52,6 +52,10 @@ class LicenseVC: UIViewController, WKNavigationDelegate { } } + @IBAction func doneAction(_ sender: Any) { + self.dismiss(animated: true) + } + func loadWebView(_ packageLink: String) { DispatchQueue.main.async { [weak self] in let request = URLRequest(url: URL(string: packageLink)!) diff --git a/DGCAWallet/ViewControllers/LicenseList.swift b/DGCAWallet/ViewControllers/SettingsControllers/LicenseTableController.swift similarity index 57% rename from DGCAWallet/ViewControllers/LicenseList.swift rename to DGCAWallet/ViewControllers/SettingsControllers/LicenseTableController.swift index 48f6d12..91adcfb 100644 --- a/DGCAWallet/ViewControllers/LicenseList.swift +++ b/DGCAWallet/ViewControllers/SettingsControllers/LicenseTableController.swift @@ -19,63 +19,45 @@ * ---license-end */ // -// Licenses.swift +// LicenseTableController.swift // DGCAWallet // // Created by Paul Ballmann on 19.05.21. // -import Foundation import UIKit -import FloatingPanel import SwiftDGC import SwiftyJSON -class LicenseList: UINavigationController { - override func viewDidLoad() { - super.viewDidLoad() - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - } - -} - -class LicenseTableVC: UITableViewController { +class LicenseTableController: UITableViewController { public var licenses: [JSON] = [] private var selectedLicense: JSON = [] + private let showLicenseDetails = "showLicenseDetails" + override func viewDidLoad() { super.viewDidLoad() + self.title = "Licenses".localized self.loadLicenses() } - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.destination is LicenseVC { - if let destVC = segue.destination as? LicenseVC { - destVC.licenseObject = self.selectedLicense - } - } + @IBAction func doneAction(_ sender: Any) { + self.dismiss(animated: true) } - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "LicenseCell", for: indexPath) as? LicenseCell - else { - return UITableViewCell() + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.destination is LicenseController { + if let destanationController = segue.destination as? LicenseController, let json = sender as? JSON { + destanationController.licenseObject = json + } } - let index = indexPath.row - cell.drawLabel(self.licenses[index]) - return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if let cell = tableView.cellForRow(at: indexPath) as? LicenseCell { - self.selectedLicense = cell.licenseObject - } - // segue to the vc - performSegue(withIdentifier: "licenseSegue", sender: nil) + let licenseObject = self.licenses[indexPath.row] + performSegue(withIdentifier: showLicenseDetails, sender: licenseObject) + tableView.deselectRow(at: indexPath, animated: true) } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -84,21 +66,16 @@ class LicenseTableVC: UITableViewController { private func loadLicenses() { do { - guard let licenseFileLocation = Bundle.main.path(forResource: "OpenSourceNotices", ofType: "json") - else { - return - } - guard let jsonData = try String(contentsOfFile: licenseFileLocation).data(using: .utf8) - else { - return - } + guard let licenseFileLocation = Bundle.main.path(forResource: "OpenSourceNotices", ofType: "json"), + let jsonData = try String(contentsOfFile: licenseFileLocation).data(using: .utf8) + else { return } + let jsonDoc = try JSON(data: jsonData) self.licenses = jsonDoc["licenses"].array ?? [] } catch { print(error) return } - print(self.licenses) } } diff --git a/DGCAWallet/ViewControllers/SettingsControllers/SettingsTableController.swift b/DGCAWallet/ViewControllers/SettingsControllers/SettingsTableController.swift new file mode 100644 index 0000000..be4d62c --- /dev/null +++ b/DGCAWallet/ViewControllers/SettingsControllers/SettingsTableController.swift @@ -0,0 +1,124 @@ +// +/*- + * ---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 + */ +// +// SettingsTableController.swift +// DGCAWallet +// +// Created by Paul Ballmann on 14.05.21. +// + +import UIKit +import SwiftDGC + + +class SettingsTableController: UITableViewController { + + @IBOutlet fileprivate weak var activityIndicator: UIActivityIndicatorView! + @IBOutlet fileprivate weak var versionLabel: UILabel! + @IBOutlet fileprivate weak var reloadLabel: UILabel! + @IBOutlet fileprivate weak var privacyInfoLabel: UILabel! + @IBOutlet fileprivate weak var licensesLabel: UILabel! + + + deinit { + let center = NotificationCenter.default + center.removeObserver(self) + } + + override func viewDidLoad() { + super.viewDidLoad() + versionLabel.text = DataCenter.appVersion + reloadLabel.text = "Reload".localized + licensesLabel.text = "Licenses".localized + privacyInfoLabel.text = "Privacy Information".localized + self.title = "Settings".localized + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + switch indexPath.section { + case 0: + if indexPath.row == 0 { + openPrivacyDoc() + } else if indexPath.row == 1 { + showLicenses() + } + case 1: + reloadAllData() + default: + break + } + + tableView.deselectRow(at: indexPath, animated: true) + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + switch section { + case 1: + return "COVID-19 vaccination verification data".localized + default: + return nil + } + + } + override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + switch section { + case 1: + let format = "Last Updated: %@".localized + return String(format: format, DataCenter.lastFetch.dateTimeString) + default: + return nil + } + } + + @IBAction func doneAction(_ sender: Any) { + self.dismiss(animated: true) + } + + func reloadAllData() { + activityIndicator.startAnimating() + DataCenter.reloadStorageData { result in + DispatchQueue.main.async { [weak self] in + self?.activityIndicator.stopAnimating() + self?.tableView.reloadData() + } + } + } + + func openPrivacyDoc() { + let link = DataCenter.localDataManager.versionedConfig["privacyUrl"].string ?? "" + openUrl(link) + } + + func openEuCertDoc() { + let link = "https://ec.europa.eu/health/ehealth/covid-19_en" + openUrl(link) + } + + func showLicenses() { + self.performSegue(withIdentifier: "showLicenses", sender: nil) + } + + func openUrl(_ string: String) { + if let url = URL(string: string) { + UIApplication.shared.open(url) + } + } +} diff --git a/DGCAWallet/ViewControllers/TicketingControllers/CertificateListController.swift b/DGCAWallet/ViewControllers/TicketingControllers/CertificateListController.swift new file mode 100644 index 0000000..43305be --- /dev/null +++ b/DGCAWallet/ViewControllers/TicketingControllers/CertificateListController.swift @@ -0,0 +1,152 @@ +// +/*- + * ---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 + */ +// +// CertificateListController.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 21.09.2021. +// + +import UIKit + +class CertificateListController: UIViewController { + let showTicketAcceptController = "showTicketAcceptController" + + @IBOutlet fileprivate weak var tableView: UITableView! + @IBOutlet fileprivate weak var nextButton: UIButton! + + var ticketingAcceptance: TicketingAcceptance? + + private var stringCertificates = [DatedCertString]() + private var selectedStringCertificate: DatedCertString? { + return stringCertificates.filter({ $0.isSelected }).first + } + + private var isNavigationEnabled: Bool { + return ticketingAcceptance != nil && selectedStringCertificate?.cert != nil + } + + override func viewDidLoad() { + super.viewDidLoad() + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + tableView.tableFooterView = UIView() + title = "Certificates".localized + nextButton.setTitle("Next".localized, for: .normal) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.stringCertificates = ticketingAcceptance?.ticketingCertificates ?? [] + tableView.reloadData() + } + + @IBAction func nextButtonAction(_ sender: Any) { + guard let _ = selectedStringCertificate?.cert else { + self.showInfoAlert(withTitle: "Please select a certificate".localized, + message: "Here are all the appropriate certificates.".localized) + return + } + self.performSegue(withIdentifier: showTicketAcceptController, sender: nil) + } + + private func deselectAllCertificates() { + for i in 0.. Int { + return stringCertificates.isEmpty ? 1 : 2 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if section == 0 { + guard let infoTableValue = ticketingAcceptance?.accessInfo.t else { return 0 } + + switch infoTableValue { + case 0: + return 0 + case 1: + return 2 + case 2: + return 9 + default: + return 0 + } + + } else { + return stringCertificates.count + } + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + if section == 0 { + return "Ticketing information".localized + } else { + return "Available Certificates".localized + } + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if indexPath.section == 0 { + let cellID = String(describing: TokenInfoCell.self) + guard let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as? TokenInfoCell + else { return UITableViewCell() } + + cell.certificateRecord = ticketingAcceptance?.certificateRecords[indexPath.row] + return cell + + } else { + let cellID = String(describing: CertificateCell.self) + guard let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as? CertificateCell + else { return UITableViewCell() } + + let savedCert = stringCertificates[indexPath.row] + cell.accessoryType = savedCert.isSelected ? .checkmark : .none + if let cert = savedCert.cert { + cell.setCertificate(cert: cert) + } + return cell + } + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.section == 1 { + deselectAllCertificates() + stringCertificates[indexPath.row].isSelected = true + tableView.reloadData() + } + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + switch segue.identifier { + case showTicketAcceptController: + guard let ticketingController = segue.destination as? TicketingAcceptanceController, + let acceptance = ticketingAcceptance, let selectedCertificate = selectedStringCertificate?.cert else { return } + ticketingController.prepareTicketing(with: acceptance, certificate: selectedCertificate) + + default: + break + } + } +} diff --git a/DGCAWallet/Protocols/CertViewerDelegate.swift b/DGCAWallet/ViewControllers/TicketingControllers/Models/CertificateRecord.swift similarity index 82% rename from DGCAWallet/Protocols/CertViewerDelegate.swift rename to DGCAWallet/ViewControllers/TicketingControllers/Models/CertificateRecord.swift index 332e9a3..b2bbc44 100644 --- a/DGCAWallet/Protocols/CertViewerDelegate.swift +++ b/DGCAWallet/ViewControllers/TicketingControllers/Models/CertificateRecord.swift @@ -1,3 +1,4 @@ +// /*- * ---license-start * eu-digital-green-certificates / dgca-wallet-app-ios @@ -17,15 +18,17 @@ * limitations under the License. * ---license-end */ -// -// ChildDismissedDelegate.swift +// +// CertificateRecord.swift // DGCAWallet -// -// Created by Yannick Spreen on 4/20/21. -// +// +// Created by Igor Khomiak on 06.12.2021. +// + import Foundation -protocol CertViewerDelegate: AnyObject { - func childDismissed(_ newCertAdded: Bool) +struct CertificateRecord { + let keyName: String + let value: String } diff --git a/DGCAWallet/ViewControllers/TicketingControllers/Models/TicketingAcceptance.swift b/DGCAWallet/ViewControllers/TicketingControllers/Models/TicketingAcceptance.swift new file mode 100644 index 0000000..f465977 --- /dev/null +++ b/DGCAWallet/ViewControllers/TicketingControllers/Models/TicketingAcceptance.swift @@ -0,0 +1,181 @@ +// +/*- + * ---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 + */ +// +// TicketingAcceptance.swift +// DGCAWallet +// +// Created by Igor Khomiak on 17.11.2021. +// + + +import UIKit +import SwiftDGC +import CryptoSwift + + +struct TicketingAcceptance { + let validationInfo: ServerListResponse + let accessInfo : AccessTokenResponse + + var certificateRecords: [CertificateRecord] { + guard let vcValue = accessInfo.vc else { return [] } + var records = [CertificateRecord]() + records.append(CertificateRecord(keyName: "Name".localized, value: "\(vcValue.gnt) \(vcValue.fnt)")) + records.append(CertificateRecord(keyName: "Date of birth".localized, value: vcValue.dob)) + records.append(CertificateRecord(keyName: "Departure".localized, value: "\(vcValue.cod),\(vcValue.rod)")) + records.append(CertificateRecord(keyName: "Arrival".localized, value: "\(vcValue.coa),\(vcValue.roa)")) + records.append(CertificateRecord(keyName: "Accepted certificate type".localized, value: vcValue.type.joined(separator: ","))) + records.append(CertificateRecord(keyName: "Category".localized, value: vcValue.category.joined(separator: ","))) + records.append(CertificateRecord(keyName: "Validation Time".localized, value: vcValue.validationClock)) + records.append(CertificateRecord(keyName: "Valid from".localized, value: vcValue.validFrom)) + records.append(CertificateRecord(keyName: "Valid to".localized, value: vcValue.validTo)) + + return records + } + + init(validationInfo: ServerListResponse, accessInfo: AccessTokenResponse) { + self.validationInfo = validationInfo + self.accessInfo = accessInfo + } + + var ticketingCertificates: [DatedCertString] { + guard let validationCertificate = self.accessInfo.vc else { return [] } + let givenName = validationCertificate.gnt + let familyName = validationCertificate.fnt + + var collectArray = DataCenter.certStrings.filter { ($0.cert!.fullName.lowercased() == "\(givenName) \(familyName)".lowercased()) && + ($0.cert!.dateOfBirth == validationCertificate.dob) } + + let validDateFrom = validationCertificate.validFrom + if let dateValidFrom = Date(rfc3339DateTimeString: validDateFrom) { + collectArray = collectArray.filter{ $0.cert!.iat < dateValidFrom } + } + + let validDateTo = validationCertificate.validTo + if let dateValidUntil = Date(rfc3339DateTimeString: validDateTo) { + collectArray = collectArray.filter {$0.cert!.exp > dateValidUntil } + } + return collectArray + } + + func requestGrandPermissions(for certificate: HCert, completion: @escaping TicketingCompletion) { + guard let urlPath = self.accessInfo.aud, let url = URL(string: urlPath) else { completion(nil, GatewayError.insufficientData); return } + guard let tokenData = KeyChain.load(key: SharedConstants.keyXnonce) else { completion(nil, GatewayError.tokenError); return } + guard let privateKey = Enclave.loadOrGenerateKey(with: "validationKey") else { completion(nil, GatewayError.privateKeyError); return } + + guard let filteredMethod = validationInfo.verificationMethod?.first(where: { + $0.type == ValidationConstants.dccEncryptionScheme2021 && + $0.id.hasSuffix(ValidationConstants.rsaOAEPWithSHA256AESGCM) + }) else { completion(nil, GatewayError.insufficientData); return } + + guard let verificationId = filteredMethod.verificationMethods?.last, + let verificationMethod = validationInfo.verificationMethod?.first(where: { $0.id == verificationId }) + else { completion(nil, GatewayError.insufficientData); return } + + let ivToken = String(decoding: tokenData, as: UTF8.self) + + encodeDCC(dgcString: certificate.fullPayloadString, token: ivToken, method: verificationMethod) { data, error in + guard error == nil else { completion(nil, GatewayError.local(description: error!.localizedDescription)); return } + guard let dccData = data else { completion(nil, GatewayError.local(description: "EncodeDCC Error")); return } + + Enclave.sign(data: dccData.0, with: privateKey, using: SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256, completion: { (signature, error) in + guard error == nil else { completion(nil, GatewayError.local(description: error!)); return } + guard let sign = signature else { completion(nil, GatewayError.signingError); return } + + let parameters = ["kid" : verificationMethod.publicKeyJwk!.kid, + "dcc" : dccData.0.base64EncodedString(), + "sig": sign.base64EncodedString(), + "encKey" : dccData.1.base64EncodedString(), + "sigAlg" : ValidationConstants.sha256withECDSA, + "encScheme" : ValidationConstants.rsaOAEPWithSHA256AESGCM] + + GatewayConnection.validateTicketing(url: url, parameters: parameters, completion: completion) + }) + } + } + + private func encodeDCC(dgcString : String, token: String, method: VerificationMethod, completion: @escaping EncodingCompletion) { + guard (token.count > 16 || token.count < 16 || token.count % 8 > 0), + let b64EncodedCert = method.publicKeyJwk?.x5c.first, + let publicSecKey = pubKey(from: b64EncodedCert) + else { completion(nil, EncodeError.incorrectPayload); return } + + let tokenData : [UInt8] = Array(base64: token) + let dgcData : [UInt8] = Array(dgcString.utf8) + var encryptedDgcData : [UInt8] = Array() + + // AES GCM + let password: [UInt8] = Array("s33krit".utf8) + let salt: [UInt8] = Array("nacllcan".utf8) + + do { + /* Generate a key from a `password`. Optional if you already have a key */ + let key = try PKCS5.PBKDF2(password: password, salt: salt, iterations: 4096, keyLength: 32, /* AES-256 */ + variant: .sha2(.sha256)).calculate() + + let gcm = GCM(iv: tokenData, mode: .combined) + let aes = try AES(key: key, blockMode: gcm, padding: .noPadding) + encryptedDgcData = try aes.encrypt(dgcData) + if let encryptedData = encrypt(data: Data(key), with: publicSecKey).0 { + let comletionData = (Data(encryptedDgcData), encryptedData) + completion(comletionData, nil) + } else { + DGCLogger.logError(EncodeError.encryptionData) + completion(nil, EncodeError.encryptionData) + } + + } catch { + DGCLogger.logError(error) + completion(nil, EncodeError.encryptionData) + } + } + + private func encrypt(data: Data, with key: SecKey) -> (Data?, String?) { + guard let publicKey = SecKeyCopyPublicKey(key) else { return (nil, "Cannot retrieve public key.".localized) } + guard SecKeyIsAlgorithmSupported(publicKey, .encrypt, SecKeyAlgorithm.rsaEncryptionOAEPSHA256) else { + return (nil, "Algorithm is not supported.".localized) + } + var error: Unmanaged? + let cipherData = SecKeyCreateEncryptedData(publicKey, + SecKeyAlgorithm.rsaEncryptionOAEPSHA256, data as CFData, &error) as Data? + let err = error?.takeRetainedValue().localizedDescription + return (cipherData, err) + } + + private func keyFromData(_ data: Data) throws -> SecKey { + let options: [String: Any] = [kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeyClass as String: kSecAttrKeyClassPublic, kSecAttrKeySizeInBits as String : 4096] + + var error: Unmanaged? + + guard let key = SecKeyCreateWithData(data as CFData, options as CFDictionary, &error) + else { throw error!.takeRetainedValue() as Error } + return key + } + + private func pubKey(from b64EncodedCert: String) -> SecKey? { + guard let encodedCertData = Data(base64Encoded: b64EncodedCert), + let cert = SecCertificateCreateWithData(nil, encodedCertData as CFData), + let publicKey = SecCertificateCopyKey(cert) + else { return nil } + return publicKey + } +} diff --git a/DGCAWallet/ViewControllers/TicketingControllers/ServerListController.swift b/DGCAWallet/ViewControllers/TicketingControllers/ServerListController.swift new file mode 100644 index 0000000..7c71b6f --- /dev/null +++ b/DGCAWallet/ViewControllers/TicketingControllers/ServerListController.swift @@ -0,0 +1,170 @@ +// +/*- + * ---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 + */ +// +// ServerListController.swift +// DGCAWallet +// +// Created by Alexandr Chernyy on 21.09.2021. +// + +import UIKit +import SwiftDGC + +class ServerListController: UIViewController { + private enum Segues { + static let showCertificatesList = "showCertificatesList" + } + + @IBOutlet fileprivate weak var tableView: UITableView! + @IBOutlet fileprivate weak var nextButton: UIButton! + + var serverListInfo: ServerListResponse? { + didSet { + listOfServices = serverListInfo?.service?.filter{ $0.type == "ValidationService" } ?? [] + } + } + + private var listOfServices = [ValidationService]() + + private var selectedServer: ValidationService? { + return listOfServices.filter({ $0.isSelected ?? false }).first + } + + override func viewDidLoad() { + super.viewDidLoad() + title = "Services".localized + nextButton.setTitle("Next".localized, for: .normal) + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + tableView.reloadData() + } + + override func willMove(toParent parent: UIViewController?) { + super.willMove(toParent: parent) + self.navigationController?.isNavigationBarHidden = (parent == nil) + } + + @IBAction func nextButtonAction(_ sender: Any) { + guard let service = selectedServer else { + showNotSelectedServise() + return + } + guard let privateKey = Enclave.loadOrGenerateKey(with: "validationKey") else { + showAlertInternalError() + return + } + guard let accessTokenService = serverListInfo?.service?.first(where: { $0.type == "AccessTokenService" }), + let url = URL(string: accessTokenService.serviceEndpoint), let serviceURL = URL(string: service.serviceEndpoint) else { + showAlertInternalError() + return + } + IdentityService.getServiceInfo(url: serviceURL) { [weak self] info, error in + guard error == nil, let serviceInfo = info else { + self?.showAlertServiceCannotUse() + return + } + + let pubKey = (X509.derPubKey(for: privateKey) ?? Data()).base64EncodedString() + + GatewayConnection.loadAccessToken(url, servicePath: service.id, publicKey: pubKey) { response, error in + DispatchQueue.main.async { [weak self] in + guard let response = response else { + self?.showTicketingError() + return + } + let ticketingAcceptance = TicketingAcceptance(validationInfo: serviceInfo, accessInfo: response) + self?.performSegue(withIdentifier: Segues.showCertificatesList, sender: ticketingAcceptance) + } + } + } + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + switch segue.identifier { + case Segues.showCertificatesList: + guard let certificateListController = segue.destination as? CertificateListController, + let acceptance = sender as? TicketingAcceptance else { return } + certificateListController.ticketingAcceptance = acceptance + default: + break + } + } +} + +extension ServerListController: UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return listOfServices.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cellID = String(describing: ServerCell.self) + guard let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as? ServerCell + else { return UITableViewCell() } + + let service = listOfServices[indexPath.row] + cell.accessoryType = (service.isSelected ?? false) ? .checkmark : .none + cell.setService(serv: service) + + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + for i in 0.. Void + +enum EncodeError: Error { + case incorrectPayload + case encryptionData +} + +enum ValidationConstants { + static let dccEncryptionScheme2021 = "DccEncryptionScheme2021" + static let rsaOAEPWithSHA256AESGCM = "RSAOAEPWithSHA256AESGCM" + static let sha256withECDSA = "SHA256withECDSA" +} diff --git a/DGCAWallet/ViewControllers/TicketingControllers/ValidationResultController.swift b/DGCAWallet/ViewControllers/TicketingControllers/ValidationResultController.swift new file mode 100644 index 0000000..28c415e --- /dev/null +++ b/DGCAWallet/ViewControllers/TicketingControllers/ValidationResultController.swift @@ -0,0 +1,110 @@ +// +/*- + * ---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 + */ +// +// ValidationResultController.swift +// DGCAWallet +// +// Created by Illia Vlasov on 20.10.2021. +// + + +import UIKit +import SwiftDGC + +class ValidationResultController: UIViewController { + @IBOutlet fileprivate weak var titleLabel: UILabel! + @IBOutlet fileprivate weak var iconImage: UIImageView! + @IBOutlet fileprivate weak var detailLabel: UILabel! + @IBOutlet fileprivate weak var limitationsTableView: UITableView! + @IBOutlet fileprivate weak var okButton: UIButton! + + public var accessTokenResponse : AccessTokenResponse? + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + } + + private func setupUI() { + limitationsTableView.reloadData() + limitationsTableView.tableFooterView = UIView() + iconImage.image = iconImage.image?.withRenderingMode(.alwaysTemplate) + okButton.setTitle("OK".localized, for: .normal) + + guard let result = accessTokenResponse?.result else { + showInfoAlert(withTitle: "Unable to verify certificate".localized, + message: "Make sure you select the desired service...".localized) + + titleLabel.text = "Validation error".localized + detailLabel.text = "Please refer to the Re-open EU website.".localized + iconImage.image = UIImage(named: "icon_large_invalid") + iconImage.tintColor = .walletRed + return + } + + switch result { + case "OK": + titleLabel.text = "Valid certificate".localized + detailLabel.text = "Your certificate is valid and confirms...".localized + iconImage.image = UIImage(named: "icon_large_valid") + iconImage.tintColor = .walletGreen + + case "NOK": + titleLabel.text = "Invalid certificate".localized + detailLabel.text = "Your certificate is not valid. Please refer to the Re-open EU website:".localized + iconImage.image = UIImage(named: "icon_large_invalid") + iconImage.tintColor = .walletRed + + case "CHK": + titleLabel.text = "Certificate has limitation".localized + detailLabel.text = "Your certificate is valid but has the following restrictions:".localized + iconImage.image = UIImage(named: "icon_large_warning") + iconImage.tintColor = .walletYellow + + default: + titleLabel.text = "Invalid certificate".localized + detailLabel.text = "Your certificate is not valid. Please refer to the Re-open EU website:".localized + iconImage.image = UIImage(named: "icon_large_invalid") + iconImage.tintColor = .walletRed + } + } + + @IBAction func onAccept(_ sender: Any) { + self.navigationController?.popToRootViewController(animated: true) + } +} + +extension ValidationResultController : UITableViewDelegate, UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + guard let issuesCount = accessTokenResponse?.results?.count else { return 0 } + return issuesCount + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "LimitationCell", for: indexPath) as! LimitationCell + cell.issueTextView.text = accessTokenResponse?.results?[indexPath.row].details ?? "" + return cell + } +} diff --git a/DGCAWallet/ViewControllers/CheckValidityVC.swift b/DGCAWallet/ViewControllers/ValidityControllers/CheckValidityController.swift similarity index 52% rename from DGCAWallet/ViewControllers/CheckValidityVC.swift rename to DGCAWallet/ViewControllers/ValidityControllers/CheckValidityController.swift index 62f482c..4ea81d9 100644 --- a/DGCAWallet/ViewControllers/CheckValidityVC.swift +++ b/DGCAWallet/ViewControllers/ValidityControllers/CheckValidityController.swift @@ -19,7 +19,7 @@ * ---license-end */ // -// CheckValidityVC.swift +// CheckValidityController.swift // DGCAWallet // // Created by Alexandr Chernyy on 08.07.2021. @@ -29,86 +29,76 @@ import UIKit import SwiftDGC -final class CheckValidityVC: UIViewController { - +class CheckValidityController: UIViewController { private enum Constants { - static let titleCellIndentifier = "CellWithTitleAndDescriptionTVC" - static let countryCellIndentifier = "CellWithDateAndCountryTVC" + static let titleCellIndentifier = "SimpleValidityCell" + static let countryCellIndentifier = "ExtendedValidityCell" + static let showRuleValidationResult = "showRuleValidationResult" static let bottomOffset: CGFloat = 32.0 - } - @IBOutlet private weak var closeButton: UIButton! - @IBOutlet private weak var checkValidityButton: UIButton! - @IBOutlet private weak var tableView: UITableView! + } + + @IBOutlet fileprivate weak var closeButton: UIButton! + @IBOutlet fileprivate weak var checkValidityButton: UIButton! + @IBOutlet fileprivate 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) + checkValidityButton.setTitle("I Agree, check validity".localized, for: .normal) + closeButton.setTitle("Done".localized, for: .normal) } + @IBAction func closeButtonAction(_ sender: Any) { self.dismiss(animated: true, completion: nil) } + private func setupView() { setupInitialDate() - setupTableView() + tableView.contentInset = .init(top: .zero, left: .zero, bottom: Constants.bottomOffset, right: .zero) 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(title: "Check country rules conformance of your certificate".localized, description: "", + needChangeTitleFont: true)) items.append(ValidityCellModel(cellType: .countryAndTimeSelection)) - items.append(ValidityCellModel(title: l10n("disclaimer"), description: l10n("disclaimer_text"))) + items.append(ValidityCellModel(title: "Disclaimer".localized, description: "disclaimer_text".localized)) } - func setHCert(cert: HCert?) { + + func setupCheckValidity(with 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) - } + performSegue(withIdentifier: Constants.showRuleValidationResult, sender: nil) } } -extension CheckValidityVC: UITableViewDataSource { +extension CheckValidityController: 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 - } + guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.titleCellIndentifier, + for: indexPath) as? SimpleValidityCell else { return UITableViewCell() } 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 - } + guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.countryCellIndentifier, for: indexPath) as? ExtendedValidityCell + else { return UITableViewCell() } + cell.countryHandler = { [weak self] countryCode in self?.hCert?.ruleCountryCode = countryCode } @@ -119,4 +109,16 @@ extension CheckValidityVC: UITableViewDataSource { return cell } } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + switch segue.identifier { + case Constants.showRuleValidationResult: + guard let validationController = segue.destination as? RuleValidationResultVC, let hCert = hCert else { return } + validationController.closeHandler = { self.closeButtonAction(self) } + validationController.setupRuleValidation(with: hCert, selectedDate: self.selectedDate) + + default: + break + } + } } diff --git a/DGCAWallet/ViewControllers/ValidityControllers/RuleValidationResultVC.swift b/DGCAWallet/ViewControllers/ValidityControllers/RuleValidationResultVC.swift new file mode 100644 index 0000000..5f1bc01 --- /dev/null +++ b/DGCAWallet/ViewControllers/ValidityControllers/RuleValidationResultVC.swift @@ -0,0 +1,225 @@ +// +/*- + * ---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 + +class RuleValidationResultVC: UIViewController { + private enum Constants { + static let ruleCellId = "RuleErrorCell" + } + + @IBOutlet fileprivate weak var closeButton: UIButton! + @IBOutlet fileprivate weak var okButton: UIButton! + @IBOutlet fileprivate weak var resultLabel: UILabel! + @IBOutlet fileprivate weak var resultIcon: UIImageView! + @IBOutlet fileprivate weak var resultDescriptionLabel: UILabel! + @IBOutlet fileprivate weak var noWarrantyLabel: UILabel! + @IBOutlet fileprivate weak var tableView: UITableView! + + var closeHandler: OnCloseHandler? + + private var hCert: HCert? + private var selectedDate = Date() + private var items: [InfoSection] = [] + + override func viewDidLoad() { + super.viewDidLoad() + setupInterface() + } + + func setupRuleValidation(with hcert: HCert, selectedDate: Date) { + self.hCert = hcert + self.selectedDate = selectedDate + } + + private func setupInterface() { + resultLabel.text = "Validating certificate with country rules".localized + resultDescriptionLabel.text = "" + noWarrantyLabel.text = "This check gives an indication on eligibility...".localized + closeButton.setTitle("Close".localized, for: .normal) + okButton.setTitle("OK".localized, for: .normal) + tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 32, right: 0) + + let validity: HCertValidity = self.validateCertLogicRules() + switch validity { + case .valid: + resultLabel.text = "Valid certificate".localized + resultDescriptionLabel.text = "Your certificate is valid and confirms...".localized + resultIcon.image = UIImage(named: "icon_large_valid") + case .invalid: + resultLabel.text = "Invalid certificate".localized + resultDescriptionLabel.text = "Your certificate did not allows you to enter the chosen country".localized + case .ruleInvalid: + resultLabel.text = "Certificate has limitation".localized + resultDescriptionLabel.text = "Your certificate is valid but has the following restrictions:".localized + resultIcon.image = UIImage(named: "icon_large_warning") + case .revocated: + resultLabel.text = "Certificate was revoked".localized + resultDescriptionLabel.text = "Your certificate did not allows you to enter the chosen country".localized + resultIcon.image = UIImage(named: "icon_large_warning") + + } + resultIcon.isHidden = false + tableView.isHidden = false + noWarrantyLabel.isHidden = false + resultDescriptionLabel.sizeToFit() + noWarrantyLabel.sizeToFit() + } + + @IBAction func closeAction(_ sender: Any) { + dismiss(animated: true) { [weak self] in + self?.closeHandler?() + } + } +} + +extension RuleValidationResultVC: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + // Please uncomment this if you need to show message about no rules for selected country +// 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 { + guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.ruleCellId, for: indexPath) as? RuleErrorCell else { return UITableViewCell() } + let item: InfoSection = items[indexPath.row] + cell.setupCell(with: item) + return cell + } +} + +extension RuleValidationResultVC { + private func validateCertLogicRules() -> HCertValidity { + var validity: HCertValidity = .valid + guard let hCert = hCert else { return validity } + + let certType = getCertificationType(type: hCert.certificateType) + if let countryCode = hCert.ruleCountryCode { + let valueSets = DataCenter.localDataManager.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 = CertLogicManager.shared.validate(filter: filterParameter, + external: externalParameters, payload: hCert.body.description) + + let failsAndOpen = result.filter { $0.result != .passed } + if failsAndOpen.count > 0 { + validity = .ruleInvalid + result.sorted(by: { $0.result.rawValue < $1.result.rawValue }).forEach { validationResult in + if let error = validationResult.validationErrors?.first { + switch validationResult.result { + case .fail: + items.append(InfoSection(header: "Certificate logic engine error".localized, content: error.localizedDescription, + countryName: hCert.ruleCountryCode, + ruleValidationResult: SwiftDGC.RuleValidationResult.failed)) + case .open: + items.append(InfoSection(header: "Certificate logic engine error".localized, content: error.localizedDescription, + countryName: hCert.ruleCountryCode, + ruleValidationResult: SwiftDGC.RuleValidationResult.open)) + case .passed: + items.append(InfoSection(header: "Certificate logic engine error".localized, 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 = CertLogicManager.shared.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: RuleValidationResult.failed)) + case .open: + items.append(InfoSection(header: errorString, + content: detailsError, + countryName: hCert.ruleCountryCode, + ruleValidationResult: RuleValidationResult.open)) + case .passed: + items.append(InfoSection(header: errorString, + content: detailsError, + countryName: hCert.ruleCountryCode, + ruleValidationResult: RuleValidationResult.passed)) + } + } + } + self.tableView.reloadData() + } + } + return validity + } +} + +// MARK: External CertType from HCert type +extension RuleValidationResultVC { + private 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/DGCAWalletTests/EHNTests.swift b/DGCAWalletTests/EHNTests.swift index fac4932..ad53b14 100644 --- a/DGCAWalletTests/EHNTests.swift +++ b/DGCAWalletTests/EHNTests.swift @@ -62,11 +62,11 @@ class EHNTests: XCTestCase { for case let elem: Dictionary in trust { if kid == Data(hexString: (elem["kid"] as? String) ?? "")?.uint, - let xParam = (elem["coord"] as? [String])?[0], - let yParam = (elem["coord"] as? [String])?[1] + let xParam = (elem["coord"] as? [String])?[0] + //let yParam = (elem["coord"] as? [String])?[1] { print("We know this KID - check if this sig works...") - if COSE.verify(data, with: xParam, and: yParam) { + if COSE.verify(_cbor: data, with: xParam) { print("All is well! Payload: ", payload) return } @@ -128,7 +128,7 @@ class EHNTests: XCTestCase { } let encodedCert = body.base64EncodedString() XCTAssert(KID.string(from: KID.from(encodedCert)) == kid) - if COSE.verify(data, with: encodedCert) { + if COSE.verify(_cbor: data, with: encodedCert) { expectation.fulfill() } else { XCTAssert(false) diff --git a/codestyle/checkstyle.xml b/codestyle/checkstyle.xml deleted file mode 100644 index d385615..0000000 --- a/codestyle/checkstyle.xml +++ /dev/null @@ -1,318 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -