diff --git a/Covid.xcodeproj/project.pbxproj b/Covid.xcodeproj/project.pbxproj index d86c9ac..16ee09b 100644 --- a/Covid.xcodeproj/project.pbxproj +++ b/Covid.xcodeproj/project.pbxproj @@ -61,9 +61,23 @@ 2110EB41246B4F480007C8DA /* iengine.lic in Resources */ = {isa = PBXBuildFile; fileRef = 2110EB40246B4F470007C8DA /* iengine.lic */; }; 212204B8244B307B00CCC72B /* CLLocationCoordinate2D.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212204B7244B307B00CCC72B /* CLLocationCoordinate2D.swift */; }; 21220504244D084900CCC72B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21220503244D084900CCC72B /* ViewController.swift */; }; + 2125E16C2472099600D3A526 /* NoRandomCheckViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2125E16B2472099600D3A526 /* NoRandomCheckViewController.swift */; }; 21591AED2439E2C100495263 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 21591AEB2439E2C000495263 /* Localizable.strings */; }; 21591AEF2439E3AF00495263 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 21591AEE2439E3AE00495263 /* Settings.bundle */; }; 21662E59244D87B0002883E8 /* UINavigationBar+Transparent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21662E58244D87B0002883E8 /* UINavigationBar+Transparent.swift */; }; + 219593F82471C48300937D59 /* PasscodeLock.strings in Resources */ = {isa = PBXBuildFile; fileRef = 219593E22471C48200937D59 /* PasscodeLock.strings */; }; + 219593F92471C48300937D59 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219593E42471C48200937D59 /* Functions.swift */; }; + 219593FD2471C48300937D59 /* PasscodeLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219593E92471C48200937D59 /* PasscodeLock.swift */; }; + 219593FE2471C48300937D59 /* EnterPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219593EA2471C48200937D59 /* EnterPasscodeState.swift */; }; + 219593FF2471C48300937D59 /* PasscodeLockView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 219593EC2471C48200937D59 /* PasscodeLockView.xib */; }; + 219594002471C48300937D59 /* PasscodeSignButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219593ED2471C48200937D59 /* PasscodeSignButton.swift */; }; + 219594012471C48300937D59 /* PasscodeSignPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219593EE2471C48300937D59 /* PasscodeSignPlaceholderView.swift */; }; + 219594022471C48300937D59 /* PasscodeLockType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219593F02471C48300937D59 /* PasscodeLockType.swift */; }; + 219594032471C48300937D59 /* PasscodeLockConfigurationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219593F12471C48300937D59 /* PasscodeLockConfigurationType.swift */; }; + 219594042471C48300937D59 /* PasscodeRepositoryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219593F22471C48300937D59 /* PasscodeRepositoryType.swift */; }; + 219594052471C48300937D59 /* PasscodeLockStateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219593F32471C48300937D59 /* PasscodeLockStateType.swift */; }; + 219594082471C48300937D59 /* PasscodeLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219593F72471C48300937D59 /* PasscodeLockViewController.swift */; }; + 2195940E2471E0CF00937D59 /* CovidPassChallengeResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2195940D2471E0CF00937D59 /* CovidPassChallengeResultViewController.swift */; }; 21B0F7ED2438AF7A00552491 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B0F7EC2438AF7A00552491 /* General.swift */; }; 21B0F85B2438D0AA00552491 /* NotificationCenterObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B0F85A2438D0AA00552491 /* NotificationCenterObserver.swift */; }; 21B0F8942439194100552491 /* UIStoryBoard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B0F8932439194100552491 /* UIStoryBoard.swift */; }; @@ -180,6 +194,7 @@ 2110EB40246B4F470007C8DA /* iengine.lic */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = iengine.lic; sourceTree = ""; }; 212204B7244B307B00CCC72B /* CLLocationCoordinate2D.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLLocationCoordinate2D.swift; sourceTree = ""; }; 21220503244D084900CCC72B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 2125E16B2472099600D3A526 /* NoRandomCheckViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoRandomCheckViewController.swift; sourceTree = ""; }; 21355B35243A7B660087ADCB /* FaceIDVerification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceIDVerification.swift; sourceTree = ""; }; 213B74DC2437E7BE005BB14F /* FaceCaptureCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceCaptureCoordinator.swift; sourceTree = ""; }; 213B74DE2437F6F8005BB14F /* FaceCaptureCompletedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceCaptureCompletedViewController.swift; sourceTree = ""; }; @@ -191,6 +206,19 @@ 2189714E24369949009987EA /* FaceIDValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceIDValidator.swift; sourceTree = ""; }; 2189715024369980009987EA /* FaceIDStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceIDStorage.swift; sourceTree = ""; }; 2189715224369CE5009987EA /* FaceIDStepVerifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceIDStepVerifier.swift; sourceTree = ""; }; + 219593E32471C48200937D59 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/PasscodeLock.strings; sourceTree = ""; }; + 219593E42471C48200937D59 /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; + 219593E92471C48200937D59 /* PasscodeLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLock.swift; sourceTree = ""; }; + 219593EA2471C48200937D59 /* EnterPasscodeState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterPasscodeState.swift; sourceTree = ""; }; + 219593EC2471C48200937D59 /* PasscodeLockView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PasscodeLockView.xib; sourceTree = ""; }; + 219593ED2471C48200937D59 /* PasscodeSignButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeSignButton.swift; sourceTree = ""; }; + 219593EE2471C48300937D59 /* PasscodeSignPlaceholderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeSignPlaceholderView.swift; sourceTree = ""; }; + 219593F02471C48300937D59 /* PasscodeLockType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockType.swift; sourceTree = ""; }; + 219593F12471C48300937D59 /* PasscodeLockConfigurationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockConfigurationType.swift; sourceTree = ""; }; + 219593F22471C48300937D59 /* PasscodeRepositoryType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeRepositoryType.swift; sourceTree = ""; }; + 219593F32471C48300937D59 /* PasscodeLockStateType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockStateType.swift; sourceTree = ""; }; + 219593F72471C48300937D59 /* PasscodeLockViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockViewController.swift; sourceTree = ""; }; + 2195940D2471E0CF00937D59 /* CovidPassChallengeResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CovidPassChallengeResultViewController.swift; sourceTree = ""; }; 21B0F7AE2438A1A900552491 /* FaceIDUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceIDUseCase.swift; sourceTree = ""; }; 21B0F7B02438A68C00552491 /* FaceID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceID.swift; sourceTree = ""; }; 21B0F7B42438ACCC00552491 /* FaceCaptureOnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceCaptureOnboardingViewController.swift; sourceTree = ""; }; @@ -301,20 +329,22 @@ 0F11813B2416C70500213533 /* Covid */ = { isa = PBXGroup; children = ( - 21220502244D083D00CCC72B /* UI */, - 0F1181472416C70900213533 /* LaunchScreen.storyboard */, - 0F1181422416C70500213533 /* Main.storyboard */, 0F11813C2416C70500213533 /* AppDelegate.swift */, 0FCB4F0A2423B3D400389493 /* PrivacyViewController.swift */, + 21C12B32243D21EF0086AF8B /* Segue.swift */, 0F247F07241A63F000986740 /* WelcomeViewController.swift */, 0FD28B102426D4FF0015F7E7 /* AdditionalInfo */, + 2195940A2471C5E100937D59 /* BorderCrossing */, 21B0F7EB2438AF5D00552491 /* Extensions */, + 0F1181472416C70900213533 /* LaunchScreen.storyboard */, + 0F1181422416C70500213533 /* Main.storyboard */, 0FCB4F0724238BA700389493 /* MainScreen */, 0FD28B132426EB530015F7E7 /* Map */, 0FCB4F0624238B7700389493 /* Quarantine */, 0F247F04241A617000986740 /* Resources */, 213B74D82437E69F005BB14F /* Services */, - 21C12B32243D21EF0086AF8B /* Segue.swift */, + 21220502244D083D00CCC72B /* UI */, + 219593E02471C48200937D59 /* Vendors */, ); path = Covid; sourceTree = ""; @@ -372,9 +402,10 @@ 0FCB4F0724238BA700389493 /* MainScreen */ = { isa = PBXGroup; children = ( + C0F99628241FB387009C6BF5 /* ForeignDecisionViewController.swift */, 0F1181402416C70500213533 /* MainViewController.swift */, + 2125E16B2472099600D3A526 /* NoRandomCheckViewController.swift */, 0F6727BF241F2CDB00AD8C48 /* QuarantineViewController.swift */, - C0F99628241FB387009C6BF5 /* ForeignDecisionViewController.swift */, 0FCB4F0824238BDC00389493 /* StatsViewController.swift */, ); path = MainScreen; @@ -392,7 +423,6 @@ C0F9962C241FCB04009C6BF5 /* PreventionTableViewCell.swift */, C0F9962E241FCBB2009C6BF5 /* PreventionViewController.swift */, C0F99634241FE632009C6BF5 /* SymptomsViewController.swift */, - 0F949673246C6575003A75A0 /* CovidPassViewController.swift */, ); path = AdditionalInfo; sourceTree = ""; @@ -431,15 +461,15 @@ 213B74D82437E69F005BB14F /* Services */ = { isa = PBXGroup; children = ( - 0F149DE8246DF2FB004345E0 /* XCConfig.swift */, 0F149DEA246E12A0004345E0 /* Crypto.swift */, + 0F149DEC246E23E4004345E0 /* Device.swift */, 0FB533D6243F83DF00A70E62 /* RemoteConfig.swift */, 0F745772241D9A9D000546FA /* UserDefaultsKeys.swift */, + 0F149DE8246DF2FB004345E0 /* XCConfig.swift */, 0FBA451D24218F0F00816388 /* Disk */, 2189714B24368B42009987EA /* FaceID */, 0F62927C241D4C6C00FF5E14 /* LocationServices */, 0FF4F218241AEF8100643297 /* Networking */, - 0F149DEC246E23E4004345E0 /* Device.swift */, ); path = Services; sourceTree = ""; @@ -469,6 +499,66 @@ path = FaceID; sourceTree = ""; }; + 219593E02471C48200937D59 /* Vendors */ = { + isa = PBXGroup; + children = ( + 219593E12471C48200937D59 /* PasscodeLock */, + ); + path = Vendors; + sourceTree = ""; + }; + 219593E12471C48200937D59 /* PasscodeLock */ = { + isa = PBXGroup; + children = ( + 219593E22471C48200937D59 /* PasscodeLock.strings */, + 219593E42471C48200937D59 /* Functions.swift */, + 219593E52471C48200937D59 /* PasscodeLock */, + 219593EB2471C48200937D59 /* Views */, + 219593EF2471C48300937D59 /* Protocols */, + 219593F72471C48300937D59 /* PasscodeLockViewController.swift */, + ); + path = PasscodeLock; + sourceTree = ""; + }; + 219593E52471C48200937D59 /* PasscodeLock */ = { + isa = PBXGroup; + children = ( + 219593E92471C48200937D59 /* PasscodeLock.swift */, + 219593EA2471C48200937D59 /* EnterPasscodeState.swift */, + ); + path = PasscodeLock; + sourceTree = ""; + }; + 219593EB2471C48200937D59 /* Views */ = { + isa = PBXGroup; + children = ( + 219593EC2471C48200937D59 /* PasscodeLockView.xib */, + 219593ED2471C48200937D59 /* PasscodeSignButton.swift */, + 219593EE2471C48300937D59 /* PasscodeSignPlaceholderView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 219593EF2471C48300937D59 /* Protocols */ = { + isa = PBXGroup; + children = ( + 219593F02471C48300937D59 /* PasscodeLockType.swift */, + 219593F12471C48300937D59 /* PasscodeLockConfigurationType.swift */, + 219593F22471C48300937D59 /* PasscodeRepositoryType.swift */, + 219593F32471C48300937D59 /* PasscodeLockStateType.swift */, + ); + path = Protocols; + sourceTree = ""; + }; + 2195940A2471C5E100937D59 /* BorderCrossing */ = { + isa = PBXGroup; + children = ( + 0F949673246C6575003A75A0 /* CovidPassViewController.swift */, + 2195940D2471E0CF00937D59 /* CovidPassChallengeResultViewController.swift */, + ); + path = BorderCrossing; + sourceTree = ""; + }; 21B0F7EB2438AF5D00552491 /* Extensions */ = { isa = PBXGroup; children = ( @@ -642,6 +732,7 @@ knownRegions = ( sk, Base, + en, ); mainGroup = 0F1181302416C70500213533; productRefGroup = 0F11813A2416C70500213533 /* Products */; @@ -672,6 +763,7 @@ C03E0A17242013CB00B3A204 /* Inter-LightItalic.otf in Resources */, C03E0A03242013CB00B3A204 /* Inter-UI-Medium.otf in Resources */, C03E0A13242013CB00B3A204 /* Inter.otf in Resources */, + 219593F82471C48300937D59 /* PasscodeLock.strings in Resources */, C03E09F5242013CB00B3A204 /* Inter-UI-Regular.otf in Resources */, C03E09ED242013CB00B3A204 /* Poppins-LightItalic.otf in Resources */, C03E09E8242013CB00B3A204 /* Poppins-Medium.otf in Resources */, @@ -705,6 +797,7 @@ C03E09E5242013CB00B3A204 /* Poppins-ExtraLightItalic.otf in Resources */, 0F1181442416C70500213533 /* Main.storyboard in Resources */, C03E09E3242013CB00B3A204 /* Poppins-ExtraLight.otf in Resources */, + 219593FF2471C48300937D59 /* PasscodeLockView.xib in Resources */, C03E09F1242013CB00B3A204 /* Poppins-Regular.otf in Resources */, C03E09EF242013CB00B3A204 /* Poppins-Black.otf in Resources */, ); @@ -840,12 +933,16 @@ 0F015CA524461588004E80A1 /* hashids.c in Sources */, 0F149DE9246DF2FB004345E0 /* XCConfig.swift in Sources */, 2110EB36246B4DEB0007C8DA /* FaceCaptureCoordinator.swift in Sources */, + 2195940E2471E0CF00937D59 /* CovidPassChallengeResultViewController.swift in Sources */, C0F99629241FB387009C6BF5 /* ForeignDecisionViewController.swift in Sources */, 0F949676246C66D2003A75A0 /* StringExtensions.swift in Sources */, 0FF4F21B241AEF8100643297 /* HTTPRequest.swift in Sources */, 0FB533D7243F83DF00A70E62 /* RemoteConfig.swift in Sources */, 0FBA451A24218EB900816388 /* Disk.swift in Sources */, 0F149DEB246E12A0004345E0 /* Crypto.swift in Sources */, + 2125E16C2472099600D3A526 /* NoRandomCheckViewController.swift in Sources */, + 219594082471C48300937D59 /* PasscodeLockViewController.swift in Sources */, + 219593F92471C48300937D59 /* Functions.swift in Sources */, 2110EB3C246B4E110007C8DA /* FaceIDCapture.swift in Sources */, 21B0F7ED2438AF7A00552491 /* General.swift in Sources */, 0F149DEF246EB0A2004345E0 /* LocationMonitoring.swift in Sources */, @@ -853,23 +950,30 @@ 2110EB3B246B4E110007C8DA /* FaceID.swift in Sources */, 0F6727C0241F2CDB00AD8C48 /* QuarantineViewController.swift in Sources */, 212204B8244B307B00CCC72B /* CLLocationCoordinate2D.swift in Sources */, + 219594032471C48300937D59 /* PasscodeLockConfigurationType.swift in Sources */, 21B0F8942439194100552491 /* UIStoryBoard.swift in Sources */, 0FBA451C24218EF100816388 /* Disk+Codable.swift in Sources */, 2110EB35246B4DEB0007C8DA /* FaceCaptureCompletedViewController.swift in Sources */, 0FBA45212421916B00816388 /* Disk+Helpers.swift in Sources */, 0F745773241D9A9D000546FA /* UserDefaultsKeys.swift in Sources */, + 219594002471C48300937D59 /* PasscodeSignButton.swift in Sources */, 0FBA451F24218F2D00816388 /* Disk+InternalHelpers.swift in Sources */, 21662E59244D87B0002883E8 /* UINavigationBar+Transparent.swift in Sources */, 0FCB4F0B2423B3D400389493 /* PrivacyViewController.swift in Sources */, + 219594042471C48300937D59 /* PasscodeRepositoryType.swift in Sources */, 0F149DED246E23E4004345E0 /* Device.swift in Sources */, + 219594052471C48300937D59 /* PasscodeLockStateType.swift in Sources */, + 219593FD2471C48300937D59 /* PasscodeLock.swift in Sources */, C0091574241EBCD10012F557 /* CountryCodeViewController.swift in Sources */, 2110EB39246B4DEB0007C8DA /* FaceIDUseCase.swift in Sources */, 0FBA45232421919200816388 /* Disk+Errors.swift in Sources */, + 219594022471C48300937D59 /* PasscodeLockType.swift in Sources */, C009157C241EF7610012F557 /* AddressConfirmationViewController.swift in Sources */, 0FF4F21E241AEFBC00643297 /* CovidService.swift in Sources */, 21B0F85B2438D0AA00552491 /* NotificationCenterObserver.swift in Sources */, 0F4A292C2457BB48003274B0 /* DateExtensions.swift in Sources */, 0FB533D5243F82F500A70E62 /* NCZIService.swift in Sources */, + 219594012471C48300937D59 /* PasscodeSignPlaceholderView.swift in Sources */, 0FF4F224241AF61600643297 /* APIRequests.swift in Sources */, 2110EB3F246B4E110007C8DA /* FaceIDValidator.swift in Sources */, 0F1181412416C70500213533 /* MainViewController.swift in Sources */, @@ -880,6 +984,7 @@ 0F949674246C6575003A75A0 /* CovidPassViewController.swift in Sources */, 0F015CC22449B376004E80A1 /* AboutViewController.swift in Sources */, 0FBA4518242183EE00816388 /* LocationReporter.swift in Sources */, + 219593FE2471C48300937D59 /* EnterPasscodeState.swift in Sources */, 0FF4F220241AF21200643297 /* APIResponses.swift in Sources */, 0F11813D2416C70500213533 /* AppDelegate.swift in Sources */, ); @@ -928,6 +1033,14 @@ name = Localizable.strings; sourceTree = ""; }; + 219593E22471C48200937D59 /* PasscodeLock.strings */ = { + isa = PBXVariantGroup; + children = ( + 219593E32471C48200937D59 /* en */, + ); + name = PasscodeLock.strings; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -1055,9 +1168,9 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Covid/Covid.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Manual; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 192; + DEVELOPMENT_TEAM = SJNG272QAP; ENABLE_BITCODE = YES; INFOPLIST_FILE = Covid/Resources/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -1083,10 +1196,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Covid/Covid.entitlements; - CODE_SIGN_IDENTITY = "Apple Distribution: Narodne centrum zdravotnickych informacii (SJNG272QAP)"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 192; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = SJNG272QAP; ENABLE_BITCODE = YES; INFOPLIST_FILE = Covid/Resources/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -1095,8 +1208,9 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.1.2; + PRODUCT_BUNDLE_IDENTIFIER = sk.nczi.ekarantena; PRODUCT_NAME = "Covid-19_SR"; - PROVISIONING_PROFILE_SPECIFIER = "Covid AppStore"; + PROVISIONING_PROFILE_SPECIFIER = "eKarantena Adhoc"; SWIFT_OBJC_BRIDGING_HEADER = "Covid/AdditionalInfo/Covid-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; diff --git a/Covid/AdditionalInfo/IdentityViewController.swift b/Covid/AdditionalInfo/IdentityViewController.swift index cefee45..cf884d4 100644 --- a/Covid/AdditionalInfo/IdentityViewController.swift +++ b/Covid/AdditionalInfo/IdentityViewController.swift @@ -34,9 +34,13 @@ import SwiftyUserDefaults final class IdentityViewController: ViewController { @IBOutlet private var uploadDataView: UIView! + private var faceCaptureCoordinator: FaceCaptureCoordinator? + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) uploadDataView.isHidden = Defaults.covidPass == nil + uploadDataView.layer.cornerRadius = 20 + uploadDataView.layer.masksToBounds = true navigationController?.navigationBar.isHidden = true } @@ -52,4 +56,47 @@ final class IdentityViewController: ViewController { uploadDataView.layer.cornerRadius = 20 uploadDataView.layer.masksToBounds = true } + + @IBAction private func showCovidPass(_ sender: Any) { + showFaceVerification() + } + + private func showCovidPass(in navigationController: UINavigationController) { + guard let viewController = UIStoryboard.controller(ofType: CovidPassViewController.self) else { + return + } + + navigationController.pushViewController(viewController, animated: true) + } +} + +// MARK: Border crossing +extension IdentityViewController { + + private func showFaceVerification() { + faceCaptureCoordinator = FaceCaptureCoordinator(useCase: .borderCrossing) + let viewController = faceCaptureCoordinator!.startFaceCapture() + let navigationController = UINavigationController(rootViewController: viewController) + navigationController.modalPresentationStyle = .fullScreen + faceCaptureCoordinator?.navigationController = navigationController + + faceCaptureCoordinator?.onAlert = { alertControler in + navigationController.present(alertControler, animated: true, completion: nil) + } + faceCaptureCoordinator?.onCoordinatorResolution = { [weak self ] result in + + switch result { + case .success(let isSuccess): + if isSuccess { + self?.showCovidPass(in: navigationController) + return + } + case .failure: + break + } + navigationController.dismiss(animated: true, completion: nil) + } + present(navigationController, animated: true, completion: nil) + } + } diff --git a/Covid/AppDelegate.swift b/Covid/AppDelegate.swift index 6725d5d..336d99a 100644 --- a/Covid/AppDelegate.swift +++ b/Covid/AppDelegate.swift @@ -1,25 +1,25 @@ /*- -* Copyright (c) 2020 Sygic -* + * Copyright (c) 2020 Sygic + * * Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * * The above copyright notice and this permission notice shall be included in -* copies or substantial portions of the Software. -* + * copies or substantial portions of the Software. + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -* THE SOFTWARE. -* -*/ + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ // // AppDelegate.swift @@ -30,6 +30,7 @@ import UIKit import SwiftyUserDefaults +import FirebaseDynamicLinks import FirebaseCore import FirebaseCrashlytics import FirebaseAnalytics @@ -145,6 +146,25 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { } return nil } + + func application(_ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { [weak self] (dynamiclink, _) in + guard dynamiclink != nil, + let url = userActivity.webpageURL else { + return + } + self?.handleRandomCheckIfNeeded(for: url) + } + + return handled + } + + private func handleRandomCheckIfNeeded(for url: URL) { + guard url.absoluteString.contains("overenie") else { return } + NotificationCenter.default.post(name: .startRandomCheck, object: self) + } } extension AppDelegate: MessagingDelegate { @@ -169,13 +189,13 @@ extension AppDelegate { remoteConfig.configSettings = settings remoteConfig.setDefaults(defaults) remoteConfig.fetch { (status, error) -> Void in - if status == .success { - print("Config fetched!") - self.remoteConfig?.activate { _ in } - } else { - print("Config not fetched") - print("Error: \(error?.localizedDescription ?? "No error available.")") - } + if status == .success { + print("Config fetched!") + self.remoteConfig?.activate { _ in } + } else { + print("Config not fetched") + print("Error: \(error?.localizedDescription ?? "No error available.")") + } } } } diff --git a/Covid/Base.lproj/Main.storyboard b/Covid/Base.lproj/Main.storyboard index 9237e01..c89425b 100644 --- a/Covid/Base.lproj/Main.storyboard +++ b/Covid/Base.lproj/Main.storyboard @@ -59,7 +59,7 @@ @@ -1603,8 +1602,9 @@ identifikátor karantény - + + @@ -1614,8 +1614,12 @@ identifikátor karantény - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/Covid/BorderCrossing/CovidPassChallengeResultViewController.swift b/Covid/BorderCrossing/CovidPassChallengeResultViewController.swift new file mode 100644 index 0000000..d7c9401 --- /dev/null +++ b/Covid/BorderCrossing/CovidPassChallengeResultViewController.swift @@ -0,0 +1,73 @@ +/*- +* Copyright (c) 2020 Sygic +* + * Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* + * The above copyright notice and this permission notice shall be included in +* copies or substantial portions of the Software. +* + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +* +*/ +// +// CovidPassChallengeResultViewController.swift +// Covid +// +// Created by Boris Bielik on 17/05/2020. +// Copyright © 2020 Sygic. All rights reserved. +// + +import Foundation +import UIKit +import SwiftOTP +import UILabel_Copyable + +extension CovidPassChallengeResultViewController: HasStoryBoardIdentifier { + static let storyboardIdentifier = "CovidPassChallengeResultViewController" +} + +final class CovidPassChallengeResultViewController: ViewController { + + var code: Int64? + @IBOutlet weak var nextButton: UIButton! + @IBOutlet private var challengeLabel: UILabel! + + override func loadView() { + super.loadView() + + challengeLabel.isCopyingEnabled = true + nextButton.layer.cornerRadius = 20 + nextButton.layer.masksToBounds = true + + if let code = code { + // TODO: remove mock data + let data = "hzQQGQkuquVAZpuXLUpErEovepgODIGl".data(using: .utf8)! + let hotp = HOTP(secret: data, digits: 6, algorithm: .sha256)! + let counter = UInt64(code) + let challenge = hotp.generate(counter: counter) + challengeLabel.text = challenge + } + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationItem.rightBarButtonItem = nil + } + + @IBAction private func onNext(_ sender: Any) { + navigationController?.dismiss(animated: true, completion: nil) + } + +} diff --git a/Covid/AdditionalInfo/CovidPassViewController.swift b/Covid/BorderCrossing/CovidPassViewController.swift similarity index 63% rename from Covid/AdditionalInfo/CovidPassViewController.swift rename to Covid/BorderCrossing/CovidPassViewController.swift index 4e29dfe..7c3281c 100644 --- a/Covid/AdditionalInfo/CovidPassViewController.swift +++ b/Covid/BorderCrossing/CovidPassViewController.swift @@ -32,17 +32,42 @@ import UIKit import UILabel_Copyable import SwiftyUserDefaults +extension CovidPassViewController: HasStoryBoardIdentifier { + static let storyboardIdentifier = "CovidPassViewController" +} + final class CovidPassViewController: ViewController { @IBOutlet private var idLabel: UILabel! @IBOutlet private var qrCodeImageView: UIImageView! override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - let covidPass = Defaults.covidPass + // TODO: remove mock data + let covidPass: String? = "C45 - 34 - 92 - 111" //Defaults.covidPass + idLabel.text = covidPass qrCodeImageView.image = covidPass?.barCodeImage } + @IBAction private func onNext(_ sender: Any) { + let viewController = PasscodeLockViewController() + navigationController?.pushViewController(viewController, animated: true) + viewController.successCallback = { [weak self] passcodeString in + if let passCode = Int64(passcodeString) { + self?.showResult(for: passCode) + } + } + } + + private func showResult(for passCode: Int64) { + guard let resultViewController = UIStoryboard.controller(ofType: CovidPassChallengeResultViewController.self) else { + return + } + resultViewController.code = passCode + + navigationController?.pushViewController(resultViewController, animated: true) + } + override func loadView() { super.loadView() diff --git a/Covid/MainScreen/MainViewController.swift b/Covid/MainScreen/MainViewController.swift index c02388e..ea02bf4 100644 --- a/Covid/MainScreen/MainViewController.swift +++ b/Covid/MainScreen/MainViewController.swift @@ -220,6 +220,10 @@ extension MainViewController { self?.showFaceRegistration(in: navigationController, completion: completion) } } + + observeNotification(withName: .startRandomCheck) { [weak self] _ in + self?.startRandomCheck() + } } private func showFaceRegistration(in navigationController: UINavigationController, completion: @escaping () -> Void) { @@ -242,7 +246,10 @@ extension MainViewController { } let cameraAccess = AVCaptureDevice.authorizationStatus(for: .video) == .authorized - let locationAccess = CLLocationManager.locationServicesEnabled() && (CLLocationManager.authorizationStatus() == .authorizedAlways || CLLocationManager.authorizationStatus() == .authorizedWhenInUse) + let authorizationStatus = CLLocationManager.authorizationStatus() + let isAuthorized = authorizationStatus == .authorizedAlways || authorizationStatus == .authorizedWhenInUse + let locationAccess = CLLocationManager.locationServicesEnabled() && isAuthorized + if cameraAccess && locationAccess { faceCaptureCoordinator?.showOnboarding(in: navigationController) } else { @@ -260,25 +267,47 @@ extension MainViewController { case .success(let data): // TODO: update profile and complete onboarding print(data) - completion() + DispatchQueue.main.async { + completion() + } case .failure: break } } } - private func showFaceVerification() { + private func showFaceVerification(in navigationController: UINavigationController) { + guard faceCaptureCoordinator == nil else { + print("face coordinator is active, skipping..") + return + } + faceCaptureCoordinator = FaceCaptureCoordinator(useCase: .verifyFace) let viewController = faceCaptureCoordinator!.startFaceCapture() - let navigationController = UINavigationController(rootViewController: viewController) - navigationController.modalPresentationStyle = .fullScreen faceCaptureCoordinator?.navigationController = navigationController faceCaptureCoordinator?.onAlert = { alertControler in navigationController.present(alertControler, animated: true, completion: nil) } - faceCaptureCoordinator?.onCoordinatorResolution = { _ in + faceCaptureCoordinator?.onCoordinatorResolution = { result in + // TODO: Notify server about the result from the random check navigationController.dismiss(animated: true, completion: nil) } + navigationController.pushViewController(viewController, animated: true) + } + + func startRandomCheck() { + guard FaceIDStorage().referenceFaceData != nil else { + print("Face template is missing. Random check can't start.") + return + } + guard let viewController = UIStoryboard.controller(ofType: SelectAddressInfoViewController.self) else { return } + + let navigationController = UINavigationController(rootViewController: viewController) + navigationController.modalPresentationStyle = .fullScreen + viewController.onContinue = { [weak self] in + self?.showFaceVerification(in: navigationController) + } + present(navigationController, animated: true, completion: nil) } } diff --git a/Covid/MainScreen/NoRandomCheckViewController.swift b/Covid/MainScreen/NoRandomCheckViewController.swift new file mode 100644 index 0000000..9e1e2a0 --- /dev/null +++ b/Covid/MainScreen/NoRandomCheckViewController.swift @@ -0,0 +1,43 @@ +/*- +* Copyright (c) 2020 Sygic +* + * Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* + * The above copyright notice and this permission notice shall be included in +* copies or substantial portions of the Software. +* + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +* +*/ +// +// NoRandomCheckViewController.swift +// Covid +// +// Created by Boris Bielik on 18/05/2020. +// Copyright © 2020 Sygic. All rights reserved. +// + +import Foundation +import UIKit + +extension NoRandomCheckViewController: HasStoryBoardIdentifier { + static let storyboardIdentifier = "NoRandomCheckViewController" +} + +final class NoRandomCheckViewController: ViewController { + + @IBAction private func onNext(_ sender: Any) { + dismiss(animated: true, completion: nil) + } +} diff --git a/Covid/Quarantine/FaceID/FaceCaptureCoordinator.swift b/Covid/Quarantine/FaceID/FaceCaptureCoordinator.swift index 0f72b67..83f366f 100644 --- a/Covid/Quarantine/FaceID/FaceCaptureCoordinator.swift +++ b/Covid/Quarantine/FaceID/FaceCaptureCoordinator.swift @@ -52,8 +52,8 @@ final class FaceCaptureCoordinator { var onAlert: ((UIAlertController) -> Void)? private(set) var step: FaceCaptureCoordinatorStep = .initialised - private var retryCaptureCount = 0 private var retryVerifyCount = 0 + private var numberOfRetries = 1 deinit { debugPrint("deallocated") @@ -104,7 +104,9 @@ extension FaceCaptureCoordinator { FaceIDStorage().saveReferenceFace(face) completeFaceCapture(didSuccess: true) case .verifyFace: - verifyFace(face) + verifyFace(face, isBorderCrossing: false) + case .borderCrossing: + verifyFace(face, isBorderCrossing: true) } case .failedToCaptureFace: @@ -115,7 +117,7 @@ extension FaceCaptureCoordinator { } } - private func verifyFace(_ face: FaceCaptureImage) { + private func verifyFace(_ face: FaceCaptureImage, isBorderCrossing: Bool) { let result = faceIdValidator.validateCapturedFaceToReferenceTemplate(face) switch result { case .success: @@ -138,8 +140,8 @@ extension FaceCaptureCoordinator { } private func handleFailedVerification() { - if retryCaptureCount == 0 { - retryCaptureCount += 1 + if retryVerifyCount < numberOfRetries { + retryVerifyCount += 1 askToVerifyAgain { [weak self] _ in self?.faceIdCapture.requestFaceCapture() @@ -171,14 +173,20 @@ extension FaceCaptureCoordinator { private func completeFaceCapture(didSuccess: Bool) { step = .completion - FaceCaptureCompletedViewController.show(using: { [weak self] (viewController) in - guard let self = self else { return } - viewController.useCase = self.useCase - viewController.didSuccess = didSuccess - viewController.navigationItem.hidesBackButton = true - self.navigationController?.pushViewController(viewController, animated: true) - }, onCompletion: { [weak self] in - self?.onCoordinatorResolution?(.success(true)) - }) + + switch useCase { + case .borderCrossing where didSuccess == true: + onCoordinatorResolution?(.success(true)) + default: + FaceCaptureCompletedViewController.show(using: { [weak self] (viewController) in + guard let self = self else { return } + viewController.useCase = self.useCase + viewController.didSuccess = didSuccess + viewController.navigationItem.hidesBackButton = true + self.navigationController?.pushViewController(viewController, animated: true) + }, onCompletion: { [weak self] in + self?.onCoordinatorResolution?(.success(didSuccess)) + }) + } } } diff --git a/Covid/Quarantine/FaceID/FaceIDNotification.swift b/Covid/Quarantine/FaceID/FaceIDNotification.swift index d03e2ca..484bf57 100644 --- a/Covid/Quarantine/FaceID/FaceIDNotification.swift +++ b/Covid/Quarantine/FaceID/FaceIDNotification.swift @@ -32,7 +32,8 @@ import Foundation import UIKit extension Notification.Name { - static let startFaceIDRegistration = Notification.Name("com.covid.startFaceIDRegistration") + static let startFaceIDRegistration = Notification.Name("sk.nczi.ekarantena.startFaceIDRegistration") + static let startRandomCheck = Notification.Name("sk.nczi.ekarantena.randomCheck") } struct StartFaceIDRegistrationNotification { diff --git a/Covid/Quarantine/FaceID/FaceIDUseCase.swift b/Covid/Quarantine/FaceID/FaceIDUseCase.swift index 378ab68..1efaa0a 100644 --- a/Covid/Quarantine/FaceID/FaceIDUseCase.swift +++ b/Covid/Quarantine/FaceID/FaceIDUseCase.swift @@ -34,13 +34,15 @@ import UIKit enum FaceIDUseCase { case registerFace case verifyFace + case borderCrossing var title: String { switch self { case .registerFace: return "Odfotiť tvár" - case .verifyFace: - return "Indentifikujte sa tvárou" + case .verifyFace, + .borderCrossing: + return "Identifikujte sa tvárou" } } @@ -57,16 +59,22 @@ enum FaceIDUseCase { case .verifyFace where didSuccess: return """ Dodržiavajte naďalej dôsledne domácu - karanténu. Zabránite tak šíreniu vírusu + izoláciu. Zabránite tak šíreniu vírusu COVID-19. """ case .verifyFace where didSuccess == false: return """ - Nepodarilo sa dokončiť proces zadania karentény. Ak si želáte, môžete neskôr proces zopakovať z hlavného menu. + Nepodarilo sa dokončiť proces overenia prostredníctvom biometrie. Ak si želáte, môžete neskôr proces zopakovať z hlavného menu. + """ + case .borderCrossing where didSuccess == true: + return "" + case .borderCrossing where didSuccess == false: + return """ + Nepodarilo sa dokončiť proces overenia prostredníctvom biometrie. """ default: return """ - Dodržiavajte dôsledne domácu karanténu. + Dodržiavajte dôsledne domácu izoláciu. Zabránite tak šíreniu vírusu COVID-19. """ } @@ -76,7 +84,8 @@ enum FaceIDUseCase { switch self { case .registerFace: return UIColor(red: 80.0 / 255.0, green: 88.0 / 255.0, blue: 249.0 / 255.0, alpha: 1.0) - case .verifyFace: + case .verifyFace, + .borderCrossing: return UIColor(red: 41.0 / 255.0, green: 192.0 / 255.0, blue: 154.0 / 255.0, alpha: 1.0) } } @@ -87,7 +96,8 @@ enum FaceIDUseCase { switch self { case .registerFace: return #imageLiteral(resourceName: "ic_check-green") - case.verifyFace: + case.verifyFace, + .borderCrossing: return #imageLiteral(resourceName: "ic_check-grey") } } diff --git a/Covid/Quarantine/SelectAddressInfoViewController.swift b/Covid/Quarantine/SelectAddressInfoViewController.swift index 4d7a896..6c2714a 100644 --- a/Covid/Quarantine/SelectAddressInfoViewController.swift +++ b/Covid/Quarantine/SelectAddressInfoViewController.swift @@ -1,25 +1,25 @@ /*- -* Copyright (c) 2020 Sygic -* + * Copyright (c) 2020 Sygic + * * Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * * The above copyright notice and this permission notice shall be included in -* copies or substantial portions of the Software. -* + * copies or substantial portions of the Software. + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -* THE SOFTWARE. -* -*/ + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ import UIKit import AVFoundation @@ -60,7 +60,10 @@ final class SelectAddressInfoViewController: ViewController { override func viewDidLoad() { super.viewDidLoad() - NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(applicationDidBecomeActive), + name: UIApplication.didBecomeActiveNotification, + object: nil) } override func viewDidAppear(_ animated: Bool) { @@ -74,7 +77,7 @@ final class SelectAddressInfoViewController: ViewController { checkAccess() } - @IBAction func didTapContinue(_ sender: Any) { + @IBAction private func didTapContinue(_ sender: Any) { if permissions.camera == .error { presentSettings(message: "Camera access is denied") } else if permissions.location == .error { @@ -85,16 +88,19 @@ final class SelectAddressInfoViewController: ViewController { } private func checkAccess() { - checkCameraAccess(completion: { [weak self] result in - self?.permissions.camera = result - self?.cameraAccessLabel.text = result.text - self?.cameraAccessLabel.textColor = result.color - self?.checkLocationAccess(completion: { result in - self?.permissions.location = result - self?.locationAccessLabel.text = result.text - self?.locationAccessLabel.textColor = result.color - }) - }) + checkCameraAccess() { [weak self] result in + + DispatchQueue.main.async { + self?.permissions.camera = result + self?.cameraAccessLabel.text = result.text + self?.cameraAccessLabel.textColor = result.color + self?.checkLocationAccess() { result in + self?.permissions.location = result + self?.locationAccessLabel.text = result.text + self?.locationAccessLabel.textColor = result.color + } + } + } } private func checkCameraAccess(completion: @escaping (AccessPermission) -> Void) { @@ -121,7 +127,7 @@ final class SelectAddressInfoViewController: ViewController { switch CLLocationManager.authorizationStatus() { case .notDetermined: locationManager.requestWhenInUseAuthorization() - // netreba completion, zavola sa didBecomeActive + // netreba completion, zavola sa didBecomeActive case .restricted, .denied: completion(.error) case .authorizedAlways, .authorizedWhenInUse: @@ -136,8 +142,8 @@ final class SelectAddressInfoViewController: ViewController { private func presentSettings(message: String) { let alertController = UIAlertController(title: "Error", - message: message, - preferredStyle: .alert) + message: message, + preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "Cancel", style: .default)) alertController.addAction(UIAlertAction(title: "Settings", style: .cancel) { _ in if let url = URL(string: UIApplication.openSettingsURLString) { diff --git a/Covid/Segue.swift b/Covid/Segue.swift index ab28749..9b19547 100644 --- a/Covid/Segue.swift +++ b/Covid/Segue.swift @@ -1,3 +1,25 @@ +/*- +* Copyright (c) 2020 Sygic +* + * Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* + * The above copyright notice and this permission notice shall be included in +* copies or substantial portions of the Software. +* + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +* +*/ // // Segue.swift // Covid diff --git a/Covid/Services/Device.swift b/Covid/Services/Device.swift index aabe796..4eeaff0 100644 --- a/Covid/Services/Device.swift +++ b/Covid/Services/Device.swift @@ -10,20 +10,20 @@ import LocalAuthentication enum Device { - //To check that device has secure enclave or not + // To check that device has secure enclave or not public static var hasSecureEnclave: Bool { !isSimulator && hasBiometrics } - //To Check that this is this simulator + // To Check that this is this simulator public static var isSimulator: Bool { TARGET_OS_SIMULATOR == 1 } - //Check that this device has Biometrics features available + // Check that this device has Biometrics features available private static var hasBiometrics: Bool { - //Local Authentication Context + // Local Authentication Context let localAuthContext = LAContext() var error: NSError? diff --git a/Covid/Services/LocationServices/LocationMonitoring.swift b/Covid/Services/LocationServices/LocationMonitoring.swift index 1ff4230..7438f11 100644 --- a/Covid/Services/LocationServices/LocationMonitoring.swift +++ b/Covid/Services/LocationServices/LocationMonitoring.swift @@ -46,7 +46,10 @@ final class LocationMonitoring: NSObject { class func monitorLocationIfNeeded() { // TODO: apply rules - LocationMonitoring.shared.setQurantineRegion(center: CLLocationCoordinate2D(latitude: 48.145842, longitude: 17.126651), radius: Firebase.remoteDoubleValue(for: .desiredPositionAccuracy)) + // TODO: remove mock data + let coordinate = CLLocationCoordinate2D(latitude: 48.145842, longitude: 17.126651) + LocationMonitoring.shared.setQurantineRegion(center: coordinate, + radius: Firebase.remoteDoubleValue(for: .desiredPositionAccuracy)) } private func setQurantineRegion(center: CLLocationCoordinate2D, radius: CLLocationDistance) { diff --git a/Covid/Services/UserDefaultsKeys.swift b/Covid/Services/UserDefaultsKeys.swift index 51f2a67..a157a50 100644 --- a/Covid/Services/UserDefaultsKeys.swift +++ b/Covid/Services/UserDefaultsKeys.swift @@ -58,6 +58,7 @@ extension DefaultsKeys { var lastQuarantineUpdate: DefaultsKey { .init("lastQuarantineUpdate") } var referenceFace: DefaultsKey<[Int8]?> { .init("dot-face") } + var referenceFaceImage: DefaultsKey<[Int8]?> { .init("dot-face-image") } var allowVerifyFaceId: DefaultsKey { .init("face_id") } } diff --git a/Covid/Vendors/PasscodeLock/Functions.swift b/Covid/Vendors/PasscodeLock/Functions.swift new file mode 100644 index 0000000..83ae276 --- /dev/null +++ b/Covid/Vendors/PasscodeLock/Functions.swift @@ -0,0 +1,26 @@ +// +// Functions.swift +// PasscodeLock +// +// Created by Yanko Dimitrov on 8/28/15. +// Copyright © 2015 Yanko Dimitrov. All rights reserved. +// + +import Foundation + +func localizedStringFor(key: String, comment: String) -> String { + + let name = "PasscodeLock" + let bundle = bundleForResource(name: name, ofType: "strings") + + return NSLocalizedString(key, tableName: name, bundle: bundle, comment: comment) +} + +func bundleForResource(name: String, ofType type: String) -> Bundle { + + if Bundle.main.path(forResource: name, ofType: type) != nil { + return Bundle.main + } + + return Bundle(for: PasscodeLock.self) +} diff --git a/Covid/Vendors/PasscodeLock/PasscodeLock/EnterPasscodeState.swift b/Covid/Vendors/PasscodeLock/PasscodeLock/EnterPasscodeState.swift new file mode 100644 index 0000000..b117ef7 --- /dev/null +++ b/Covid/Vendors/PasscodeLock/PasscodeLock/EnterPasscodeState.swift @@ -0,0 +1,43 @@ +// +// EnterPasscodeState.swift +// PasscodeLock +// +// Created by Yanko Dimitrov on 8/28/15. +// Copyright © 2015 Yanko Dimitrov. All rights reserved. +// + +import Foundation + +let PasscodeLockIncorrectPasscodeNotification = "passcode.lock.incorrect.passcode.notification" + +struct EnterPasscodeState: PasscodeLockStateType { + + let title: String + let description: String + let isCancellableAction: Bool + + private var inccorectPasscodeAttempts = 0 + private var isNotificationSent = false + + init(allowCancellation: Bool = false) { + + isCancellableAction = allowCancellation + title = localizedStringFor(key: "PasscodeLockEnterTitle", comment: "Enter passcode title") + description = localizedStringFor(key: "PasscodeLockEnterDescription", comment: "Enter passcode description") + } + + mutating func acceptPasscode(passcode: [String], fromLock lock: PasscodeLockType) { + lock.delegate?.passcodeLockDidSucceed(lock: lock, passcode: passcode) + } + + private mutating func postNotification() { + + guard !isNotificationSent else { return } + + let center = NotificationCenter.default + + center.post(name: NSNotification.Name(rawValue: PasscodeLockIncorrectPasscodeNotification), object: nil) + + isNotificationSent = true + } +} diff --git a/Covid/Vendors/PasscodeLock/PasscodeLock/PasscodeLock.swift b/Covid/Vendors/PasscodeLock/PasscodeLock/PasscodeLock.swift new file mode 100644 index 0000000..1234127 --- /dev/null +++ b/Covid/Vendors/PasscodeLock/PasscodeLock/PasscodeLock.swift @@ -0,0 +1,57 @@ +// +// PasscodeLock.swift +// PasscodeLock +// +// Created by Yanko Dimitrov on 8/28/15. +// Copyright © 2015 Yanko Dimitrov. All rights reserved. +// + +import Foundation + +class PasscodeLock: PasscodeLockType { + + weak var delegate: PasscodeLockTypeDelegate? + let configuration: PasscodeLockConfigurationType + + var state: PasscodeLockStateType { + lockState + } + + private var lockState: PasscodeLockStateType + private var passcode = [String]() + + init(state: PasscodeLockStateType, configuration: PasscodeLockConfigurationType) { + + precondition(configuration.passcodeLength > 0, "Passcode length sould be greather than zero.") + + self.lockState = state + self.configuration = configuration + } + + func addSign(sign: String) { + + passcode.append(sign) + delegate?.passcodeLock(lock: self, addedSignAtIndex: passcode.count - 1) + + if passcode.count >= configuration.passcodeLength { + + lockState.acceptPasscode(passcode: passcode, fromLock: self) + passcode.removeAll(keepingCapacity: true) + } + } + + func removeSign() { + + guard passcode.count > 0 else { return } + + passcode.removeLast() + delegate?.passcodeLock(lock: self, removedSignAtIndex: passcode.count) + } + + func changeStateTo(state: PasscodeLockStateType) { + DispatchQueue.main.async { + self.lockState = state + self.delegate?.passcodeLockDidChangeState(lock: self) + } + } +} diff --git a/Covid/Vendors/PasscodeLock/PasscodeLockViewController.swift b/Covid/Vendors/PasscodeLock/PasscodeLockViewController.swift new file mode 100644 index 0000000..caff014 --- /dev/null +++ b/Covid/Vendors/PasscodeLock/PasscodeLockViewController.swift @@ -0,0 +1,255 @@ +// +// PasscodeLockViewController.swift +// PasscodeLock +// +// Created by Yanko Dimitrov on 8/28/15. +// Copyright © 2015 Yanko Dimitrov. All rights reserved. +// + +import UIKit + +struct PasscodeLockConfiguration: PasscodeLockConfigurationType { + + let passcodeLength = 4 + let maximumIncorrectPasscodeAttempts = -1 +} + +final class PasscodeLockViewController: ViewController, PasscodeLockTypeDelegate { + + @IBOutlet private weak var titleLabel: UILabel? + @IBOutlet private weak var descriptionLabel: UILabel? + @IBOutlet private var placeholders: [PasscodeSignPlaceholderView] = [PasscodeSignPlaceholderView]() + @IBOutlet private weak var cancelButton: UIButton? + @IBOutlet private weak var deleteSignButton: UIButton? + @IBOutlet private weak var touchIDButton: UIButton? + @IBOutlet private weak var placeholdersX: NSLayoutConstraint? + + var successCallback: ((_ passCode: String) -> Void)? + var dismissCompletionCallback: (() -> Void)? + var animateOnDismiss: Bool + var notificationCenter: NotificationCenter? + + internal let passcodeConfiguration: PasscodeLockConfigurationType + internal var passcodeLock: PasscodeLockType + internal var isPlaceholdersAnimationCompleted = true + + private var shouldTryToAuthenticateWithBiometrics = true + + // MARK: - Initializers + + init(animateOnDismiss: Bool = true) { + + self.animateOnDismiss = animateOnDismiss + + passcodeConfiguration = PasscodeLockConfiguration() + passcodeLock = PasscodeLock(state: EnterPasscodeState(), configuration: passcodeConfiguration) + + let nibName = "PasscodeLockView" + let bundle: Bundle = bundleForResource(name: nibName, ofType: "nib") + + super.init(nibName: nibName, bundle: bundle) + + passcodeLock.delegate = self + notificationCenter = NotificationCenter.default + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + + clearEvents() + } + + // MARK: - View + + override func viewDidLoad() { + super.viewDidLoad() + + updatePasscodeView() + deleteSignButton?.isEnabled = false + + setupEvents() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if shouldTryToAuthenticateWithBiometrics { + + authenticateWithBiometrics() + } + } + + internal func updatePasscodeView() { + + titleLabel?.text = passcodeLock.state.title + descriptionLabel?.text = passcodeLock.state.description + cancelButton?.isHidden = !passcodeLock.state.isCancellableAction + touchIDButton?.isHidden = true + } + + // MARK: - Events + + private func setupEvents() { + + notificationCenter?.addObserver(self, + selector: #selector(appWillEnterForegroundHandler), + name: UIApplication.willEnterForegroundNotification, + object: nil) + notificationCenter?.addObserver(self, + selector: #selector(appDidEnterBackgroundHandler), + name: UIApplication.didEnterBackgroundNotification, + object: nil) + } + + private func clearEvents() { + + notificationCenter?.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil) + notificationCenter?.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil) + } + + @objc + func appWillEnterForegroundHandler(notification: NSNotification) { + + authenticateWithBiometrics() + } + + @objc + func appDidEnterBackgroundHandler(notification: NSNotification) { + + shouldTryToAuthenticateWithBiometrics = false + } + + // MARK: - Actions + + @IBAction private func passcodeSignButtonTap(_ sender: PasscodeSignButton) { + + guard isPlaceholdersAnimationCompleted else { return } + + passcodeLock.addSign(sign: sender.passcodeSign) + } + + @IBAction private func cancelButtonTap(_ sender: UIButton) { + dismissPasscodeLock(lock: passcodeLock) + } + + @IBAction private func deleteSignButtonTap(_ sender: UIButton) { + passcodeLock.removeSign() + } + + @IBAction private func touchIDButtonTap(_ sender: UIButton) { + } + + private func authenticateWithBiometrics() { + } + + internal func dismissPasscodeLock(lock: PasscodeLockType, completionHandler: (() -> Void)? = nil) { + + // if presented as modal + if presentingViewController?.presentedViewController == self { + + dismiss(animated: animateOnDismiss) { [weak self] in + guard let self = self else { return } + self.dismissCompletionCallback?() + + completionHandler?() + } + + return + + // if pushed in a navigation controller + } else if navigationController != nil { + + navigationController?.popViewController(animated: animateOnDismiss) + } + + dismissCompletionCallback?() + + completionHandler?() + } + + // MARK: - Animations + + internal func animateWrongPassword() { + + deleteSignButton?.isEnabled = false + isPlaceholdersAnimationCompleted = false + + animatePlaceholders(placeholders: placeholders, toState: .Error) + + placeholdersX?.constant = -40 + view.layoutIfNeeded() + + UIView.animate( + withDuration: 0.5, + delay: 0, + usingSpringWithDamping: 0.2, + initialSpringVelocity: 0, + options: [], + animations: { [weak self] in + guard let self = self else { return } + + self.placeholdersX?.constant = 0 + self.view.layoutIfNeeded() + }, + completion: { [weak self] _ in + guard let self = self else { return } + + self.isPlaceholdersAnimationCompleted = true + self.animatePlaceholders(placeholders: self.placeholders, toState: .Inactive) + }) + } + + internal func animatePlaceholders(placeholders: [PasscodeSignPlaceholderView], toState state: PasscodeSignPlaceholderView.State) { + + for placeholder in placeholders { + + placeholder.animateState(state: state) + } + } + + private func animatePlacehodlerAtIndex(index: Int, toState state: PasscodeSignPlaceholderView.State) { + + guard index < placeholders.count && index >= 0 else { return } + + placeholders[index].animateState(state: state) + } + + // MARK: - PasscodeLockDelegate + + func passcodeLockDidSucceed(lock: PasscodeLockType, passcode: [String]) { + deleteSignButton?.isEnabled = true + animatePlaceholders(placeholders: placeholders, toState: .Inactive) + successCallback?(passcode.joined()) + } + + func passcodeLockDidFail(lock: PasscodeLockType) { + + animateWrongPassword() + } + + func passcodeLockDidChangeState(lock: PasscodeLockType) { + + updatePasscodeView() + animatePlaceholders(placeholders: placeholders, toState: .Inactive) + deleteSignButton?.isEnabled = false + } + + func passcodeLock(lock: PasscodeLockType, addedSignAtIndex index: Int) { + + animatePlacehodlerAtIndex(index: index, toState: .Active) + deleteSignButton?.isEnabled = true + } + + func passcodeLock(lock: PasscodeLockType, removedSignAtIndex index: Int) { + + animatePlacehodlerAtIndex(index: index, toState: .Inactive) + + if index == 0 { + + deleteSignButton?.isEnabled = false + } + } +} diff --git a/Covid/Vendors/PasscodeLock/Protocols/PasscodeLockConfigurationType.swift b/Covid/Vendors/PasscodeLock/Protocols/PasscodeLockConfigurationType.swift new file mode 100644 index 0000000..bcba46a --- /dev/null +++ b/Covid/Vendors/PasscodeLock/Protocols/PasscodeLockConfigurationType.swift @@ -0,0 +1,15 @@ +// +// PasscodeLockConfigurationType.swift +// PasscodeLock +// +// Created by Yanko Dimitrov on 8/28/15. +// Copyright © 2015 Yanko Dimitrov. All rights reserved. +// + +import Foundation + +protocol PasscodeLockConfigurationType { + + var passcodeLength: Int {get} + var maximumIncorrectPasscodeAttempts: Int {get} +} diff --git a/Covid/Vendors/PasscodeLock/Protocols/PasscodeLockStateType.swift b/Covid/Vendors/PasscodeLock/Protocols/PasscodeLockStateType.swift new file mode 100644 index 0000000..1cee700 --- /dev/null +++ b/Covid/Vendors/PasscodeLock/Protocols/PasscodeLockStateType.swift @@ -0,0 +1,18 @@ +// +// PasscodeLockStateType.swift +// PasscodeLock +// +// Created by Yanko Dimitrov on 8/28/15. +// Copyright © 2015 Yanko Dimitrov. All rights reserved. +// + +import Foundation + +protocol PasscodeLockStateType { + + var title: String {get} + var description: String {get} + var isCancellableAction: Bool {get} + + mutating func acceptPasscode(passcode: [String], fromLock lock: PasscodeLockType) +} diff --git a/Covid/Vendors/PasscodeLock/Protocols/PasscodeLockType.swift b/Covid/Vendors/PasscodeLock/Protocols/PasscodeLockType.swift new file mode 100644 index 0000000..24d3bcb --- /dev/null +++ b/Covid/Vendors/PasscodeLock/Protocols/PasscodeLockType.swift @@ -0,0 +1,29 @@ +// +// PasscodeLockType.swift +// PasscodeLock +// +// Created by Yanko Dimitrov on 8/28/15. +// Copyright © 2015 Yanko Dimitrov. All rights reserved. +// + +import Foundation + +protocol PasscodeLockType { + + var delegate: PasscodeLockTypeDelegate? {get set} + var configuration: PasscodeLockConfigurationType {get} + var state: PasscodeLockStateType {get} + + func addSign(sign: String) + func removeSign() + func changeStateTo(state: PasscodeLockStateType) +} + +protocol PasscodeLockTypeDelegate: class { + + func passcodeLockDidSucceed(lock: PasscodeLockType, passcode: [String]) + func passcodeLockDidFail(lock: PasscodeLockType) + func passcodeLockDidChangeState(lock: PasscodeLockType) + func passcodeLock(lock: PasscodeLockType, addedSignAtIndex index: Int) + func passcodeLock(lock: PasscodeLockType, removedSignAtIndex index: Int) +} diff --git a/Covid/Vendors/PasscodeLock/Protocols/PasscodeRepositoryType.swift b/Covid/Vendors/PasscodeLock/Protocols/PasscodeRepositoryType.swift new file mode 100644 index 0000000..2d0afc5 --- /dev/null +++ b/Covid/Vendors/PasscodeLock/Protocols/PasscodeRepositoryType.swift @@ -0,0 +1,18 @@ +// +// PasscodeRepositoryType.swift +// PasscodeLock +// +// Created by Yanko Dimitrov on 8/28/15. +// Copyright © 2015 Yanko Dimitrov. All rights reserved. +// + +import Foundation + +protocol PasscodeRepositoryType { + + var hasPasscode: Bool {get} + var passcode: [String]? {get} + + func savePasscode(passcode: [String]) + func deletePasscode() +} diff --git a/Covid/Vendors/PasscodeLock/Views/PasscodeLockView.xib b/Covid/Vendors/PasscodeLock/Views/PasscodeLockView.xib new file mode 100644 index 0000000..306bfc2 --- /dev/null +++ b/Covid/Vendors/PasscodeLock/Views/PasscodeLockView.xib @@ -0,0 +1,453 @@ + + + + + + + + + + + Poppins-Regular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Covid/Vendors/PasscodeLock/Views/PasscodeSignButton.swift b/Covid/Vendors/PasscodeLock/Views/PasscodeSignButton.swift new file mode 100644 index 0000000..0807b8e --- /dev/null +++ b/Covid/Vendors/PasscodeLock/Views/PasscodeSignButton.swift @@ -0,0 +1,101 @@ +// +// PasscodeSignButton.swift +// PasscodeLock +// +// Created by Yanko Dimitrov on 8/28/15. +// Copyright © 2015 Yanko Dimitrov. All rights reserved. +// + +import UIKit + +@IBDesignable +class PasscodeSignButton: UIButton { + + @IBInspectable + var passcodeSign: String = "1" + + @IBInspectable + var borderColor: UIColor = UIColor.white { + didSet { + setupView() + } + } + + @IBInspectable + var borderRadius: CGFloat = 30 { + didSet { + setupView() + } + } + + @IBInspectable + var highlightBackgroundColor: UIColor = UIColor.clear { + didSet { + setupView() + } + } + + override init(frame: CGRect) { + + super.init(frame: frame) + + setupView() + setupActions() + } + + required init?(coder aDecoder: NSCoder) { + + super.init(coder: aDecoder) + + setupActions() + } + + override var intrinsicContentSize: CGSize { + CGSize(width: 60, height: 60) + + } + + private var defaultBackgroundColor = UIColor.clear + + private func setupView() { + + layer.borderWidth = 1 + layer.cornerRadius = borderRadius + layer.borderColor = borderColor.cgColor + + if let backgroundColor = backgroundColor { + + defaultBackgroundColor = backgroundColor + } + } + + private func setupActions() { + addTarget(self, action: #selector(handleTouchDown), for: .touchDown) + addTarget(self, action: #selector(handleTouchUp), for: [.touchUpInside, .touchDragOutside, .touchCancel]) + } + + @objc func handleTouchDown() { + + animateBackgroundColor(color: highlightBackgroundColor) + } + + @objc func handleTouchUp() { + + animateBackgroundColor(color: defaultBackgroundColor) + } + + private func animateBackgroundColor(color: UIColor) { + + UIView.animate( + withDuration: 0.3, + delay: 0.0, + usingSpringWithDamping: 1, + initialSpringVelocity: 0.0, + options: [.allowUserInteraction, .beginFromCurrentState], + animations: { + self.backgroundColor = color + }, + completion: nil + ) + } +} diff --git a/Covid/Vendors/PasscodeLock/Views/PasscodeSignPlaceholderView.swift b/Covid/Vendors/PasscodeLock/Views/PasscodeSignPlaceholderView.swift new file mode 100644 index 0000000..6c1bfc5 --- /dev/null +++ b/Covid/Vendors/PasscodeLock/Views/PasscodeSignPlaceholderView.swift @@ -0,0 +1,91 @@ +// +// PasscodeSignPlaceholderView.swift +// PasscodeLock +// +// Created by Yanko Dimitrov on 8/28/15. +// Copyright © 2015 Yanko Dimitrov. All rights reserved. +// + +import UIKit + +@IBDesignable +class PasscodeSignPlaceholderView: UIView { + + enum State { + case Inactive + case Active + case Error + } + + @IBInspectable var inactiveColor: UIColor = UIColor.white { + didSet { + setupView() + } + } + + @IBInspectable var activeColor: UIColor = UIColor.gray { + didSet { + setupView() + } + } + + @IBInspectable var errorColor: UIColor = UIColor.red { + didSet { + setupView() + } + } + + override init(frame: CGRect) { + + super.init(frame: frame) + + setupView() + } + + required init?(coder aDecoder: NSCoder) { + + super.init(coder: aDecoder) + } + + override var intrinsicContentSize: CGSize { + CGSize(width: 16, height: 16) + + } + + private func setupView() { + + layer.cornerRadius = 8 + layer.borderWidth = 1 + layer.borderColor = activeColor.cgColor + backgroundColor = inactiveColor + } + + private func colorsForState(state: State) -> (backgroundColor: UIColor, borderColor: UIColor) { + + switch state { + case .Inactive: return (inactiveColor, activeColor) + case .Active: return (activeColor, activeColor) + case .Error: return (errorColor, errorColor) + } + } + + func animateState(state: State) { + + let colors = colorsForState(state: state) + + UIView.animate( + withDuration: 0.5, + delay: 0, + usingSpringWithDamping: 1, + initialSpringVelocity: 0, + options: [], + animations: { + + self.backgroundColor = colors.backgroundColor + self.layer.borderColor = colors.borderColor.cgColor + + }, + completion: nil + ) + } +} diff --git a/Covid/Vendors/PasscodeLock/en.lproj/PasscodeLock.strings b/Covid/Vendors/PasscodeLock/en.lproj/PasscodeLock.strings new file mode 100755 index 0000000..fe7b761 --- /dev/null +++ b/Covid/Vendors/PasscodeLock/en.lproj/PasscodeLock.strings @@ -0,0 +1,3 @@ + +"PasscodeLockEnterTitle" = "Zadajte prosím 4 ciferné číslo sprostredkované autoritou"; +"PasscodeLockEnterDescription" = ""; diff --git a/Podfile b/Podfile index 012e491..6eeeb1b 100644 --- a/Podfile +++ b/Podfile @@ -16,7 +16,10 @@ target 'Covid' do pod 'Firebase/Crashlytics'#, '6.21.0' pod 'Firebase/RemoteConfig'#, '6.21.0' pod 'Firebase/Messaging'#, '4.3.0' + pod 'Firebase/DynamicLinks'#, '4.0.8' - pod 'dot', '2.14.5' + pod 'SwiftOTP', '2.0.0' + + pod 'dot', '2.14.2' end diff --git a/Podfile.lock b/Podfile.lock index aa9539b..52464f7 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,6 +1,7 @@ PODS: - - dot (2.14.5): - - iface (= 3.8.1) + - CryptoSwift (1.3.1) + - dot (2.14.2): + - iface (~> 3.8.1) - Firebase/Analytics (6.24.0): - Firebase/Core - Firebase/Core (6.24.0): @@ -11,6 +12,9 @@ PODS: - Firebase/Crashlytics (6.24.0): - Firebase/CoreOnly - FirebaseCrashlytics (~> 4.1.0) + - Firebase/DynamicLinks (6.24.0): + - Firebase/CoreOnly + - FirebaseDynamicLinks (~> 4.0.8) - Firebase/Messaging (6.24.0): - Firebase/CoreOnly - FirebaseMessaging (~> 4.4.0) @@ -51,6 +55,9 @@ PODS: - GoogleDataTransportCCTSupport (~> 3.1) - nanopb (~> 1.30905.0) - PromisesObjC (~> 1.2) + - FirebaseDynamicLinks (4.0.8): + - FirebaseAnalyticsInterop (~> 1.3) + - FirebaseCore (~> 6.2) - FirebaseInstallations (1.2.0): - FirebaseCore (~> 6.6) - GoogleUtilities/Environment (~> 6.6) @@ -117,22 +124,27 @@ PODS: - PromisesObjC (1.2.8) - Protobuf (3.11.4) - ReachabilitySwift (5.0.0) + - SwiftOTP (2.0.0): + - CryptoSwift (>= 1.0.0) - SwiftyUserDefaults (5.0.0) - "UILabel+Copyable (2.0)" DEPENDENCIES: - - dot (= 2.14.5) + - dot (= 2.14.2) - Firebase/Analytics - Firebase/Crashlytics + - Firebase/DynamicLinks - Firebase/Messaging - Firebase/RemoteConfig - JWTDecode (~> 2.4) - ReachabilitySwift (= 5.0.0) + - SwiftOTP (= 2.0.0) - SwiftyUserDefaults (= 5.0.0) - "UILabel+Copyable (~> 2.0)" SPEC REPOS: https://github.com/CocoaPods/Specs.git: + - CryptoSwift - Firebase - FirebaseABTesting - FirebaseAnalytics @@ -141,6 +153,7 @@ SPEC REPOS: - FirebaseCoreDiagnostics - FirebaseCoreDiagnosticsInterop - FirebaseCrashlytics + - FirebaseDynamicLinks - FirebaseInstallations - FirebaseInstanceID - FirebaseMessaging @@ -154,6 +167,7 @@ SPEC REPOS: - PromisesObjC - Protobuf - ReachabilitySwift + - SwiftOTP - SwiftyUserDefaults - "UILabel+Copyable" https://github.com/innovatrics/innovatrics-podspecs: @@ -161,7 +175,8 @@ SPEC REPOS: - iface SPEC CHECKSUMS: - dot: 45ce81e97cbb43ed9cd1ea0f26ed473769fc7ed7 + CryptoSwift: f12f037f6d0fcd6d48c96db0071b653de64e6c4d + dot: 20df3ef7e26864a7b04c216607cb683a5089d196 Firebase: b28e55c60efd98963cd9011fe2fac5a10c2ba124 FirebaseABTesting: 821a3a3e4a145987e80fee3657c4de1cb9adf693 FirebaseAnalytics: 7386fc2176e3f93ad8ef34b5b1f2b33a891e4962 @@ -170,6 +185,7 @@ SPEC CHECKSUMS: FirebaseCoreDiagnostics: 4a773a47bd83bbd5a9b1ccf1ce7caa8b2d535e67 FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 FirebaseCrashlytics: fff345f16a07f0b23cb342d8de5a42cec6de3492 + FirebaseDynamicLinks: 417dc6dbb6013233c77558290d73296f429656a6 FirebaseInstallations: 2119fb3e46b0a88bfdbf12562f855ee3252462fa FirebaseInstanceID: cef67c4967c7cecb56ea65d8acbb4834825c587b FirebaseMessaging: 58cb17277641a0084d98d4951ba1f1fb0588c8c6 @@ -184,9 +200,10 @@ SPEC CHECKSUMS: PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + SwiftOTP: f5e4df69cd4d1642e4bb3cb9e12bf6e9669ea164 SwiftyUserDefaults: 33fcb42bd1feb53a37d873feb62c82967db5f7f6 "UILabel+Copyable": 9ccc55680d6f8162568906654ae4d26782175300 -PODFILE CHECKSUM: b4ad67dfac5e2882d0f544f1a6a6826dc754d7a8 +PODFILE CHECKSUM: 33b5f6ab562a9e66087ddf5ac76b628aea3a8b03 COCOAPODS: 1.9.1