From 26a110f1139175bee65420d9bd64ad3595d4d424 Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Tue, 10 Dec 2024 12:42:22 +0100 Subject: [PATCH] Liveness Timeout Images (#253) * decouple selfieviewmodel from livenesscheckmanager * improve object references so to prevent retain cycles. * write custom encoding function for failure reason, replace forced failure with failure reason enum. append failure reason data to multipart form request body. * check that submission task is nil before assigning it. * remove unnecessary comment * feat: update changelog (#254) * feat: update changelog * chore: lint fix * fix wrong version set for fingerprintjs package and dependency name causing spm not to resolve (#257) * added autoassign to workflow (#259) * added autoassign to workflow * added autoassign to workflow/fix * pod install * add beta tag to strict mode products. add a cancel toolbar button to all product screens. remove cancel button from liveness instructions screen. * use a different multiplier for checking face bounds for selfie and liveness capture hide liveness progress if face is not valid. * bump build number and add haptic feedback to selfie capture. * reduce luminance threshold lowerbound * reduce luminace threshold lower bound * introduce a function to flip the selfie image for preview during submission. * reduce head turn thresholds * remove brightness check from selfie capture v2 * adjust screen brightness for selfie capture screen v2 * Chore(deps): bump rexml from 3.3.6 to 3.3.9 (#249) Bumps [rexml](https://github.com/ruby/rexml) from 3.3.6 to 3.3.9. - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](https://github.com/ruby/rexml/compare/v3.3.6...v3.3.9) --- updated-dependencies: - dependency-name: rexml dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JNdhlovu Co-authored-by: Tobi Omotayo * bump version number. * replace selfie quality check with vision face quality in face detector and face validator. * reset the submission task when there is a response or error. * improve publisher reference for legacy selfie view model. * use weak reference in legacy selfie view model to prevent retain cycle. * introduce a backport of StateObject into sdk helpers * disable idle timer when capturing selfie and enable it back when dismissed. * use a backport of stateobject in selfie capture screen. * bump build number. * restore brightness check * check orientation before analyzing camera frames. * add a did cancel delegate method to selfie result delegate. * Chore(deps): bump slackapi/slack-github-action (#260) * add property to set camera name in camera manager. implement fallback for pre-ios 15 devices. add didCancel endpoint to smart selfie result delegate. * use uniqueID for cameraname. add a delay when capturing random liveness images during timeout. * import new typeface DMSans and define a model to manage its fonts and styles. * add face not within frame case to face bounds state. * remove overlay from face bounding area. introduce new version of animations. * code formatting. * correct instructions typo. * change new smart selfie products name. disable lint checks for backport files. code formatting. * rename smart selfie v2 to enhanced * pod install. --------- Signed-off-by: dependabot[bot] Co-authored-by: JNdhlovu Co-authored-by: Davina Anthony <97633603+daviinaa@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Example/SmileID.xcodeproj/project.pbxproj | 146 +++++++------- Example/SmileID/Home/HomeView.swift | 4 +- Example/SmileID/Home/ProductCell.swift | 10 + Example/SmileID/WelcomeScreen.swift | 2 +- Example/Tests/FaceValidatorTests.swift | 22 +-- .../Classes/Camera/CameraManager.swift | 3 + .../Classes/Camera/CameraViewController.swift | 2 +- ...torV2.swift => EnhancedFaceDetector.swift} | 42 ++-- .../Classes/FaceDetector/FaceValidator.swift | 40 ++-- .../FaceDetector/LivenessCheckManager.swift | 40 ++-- .../SmileID/Classes/FaceDetector/Models.swift | 1 + .../SmileID/Classes/Helpers/Backport.swift | 52 +++++ .../SmileID/Classes/Helpers/DMSansFont.swift | 36 ++++ .../Helpers/DeviceRotationViewModifier.swift | 19 ++ .../Classes/Helpers/HapticManager.swift | 23 +++ .../SmileID/Classes/Helpers/StateObject.swift | 151 ++++++++++++++ Sources/SmileID/Classes/Helpers/Theme.swift | 14 +- .../Networking/Models/FailureReason.swift | 14 +- .../Classes/Networking/ServiceRunnable.swift | 16 +- .../SelfieCapture/CaptureGuideAnimation.swift | 11 +- ...ift => EnhancedSmartSelfieViewModel.swift} | 185 ++++++++++++++---- .../SelfieCaptureInstruction.swift | 5 + .../SelfieSubmissionManager.swift | 43 ++-- .../SelfieCapture/SelfieViewModel.swift | 13 +- .../SelfieCapture/SelfieViewModelAction.swift | 5 +- .../SmartSelfieResultDelegate.swift | 5 + .../SelfieCapture/View/CameraView.swift | 2 +- ...wift => EnhancedSelfieCaptureScreen.swift} | 17 +- .../SelfieCapture/View/FaceBoundingArea.swift | 30 ++- .../View/FaceShapedProgressIndicator.swift | 2 +- .../LivenessCaptureInstructionsView.swift | 24 +-- ...estratedEnhancedSelfieCaptureScreen.swift} | 10 +- Sources/SmileID/Classes/SmileID.swift | 4 +- Sources/SmileID/Classes/Util.swift | 4 + .../SmileID/Resources/Fonts/DMSans-Bold.ttf | Bin 0 -> 56268 bytes .../SmileID/Resources/Fonts/DMSans-Medium.ttf | Bin 0 -> 56376 bytes .../Resources/Fonts/DMSans-Regular.ttf | Bin 0 -> 56348 bytes .../Localization/en.lproj/Localizable.strings | 3 +- .../LottieFiles/device_orientation.lottie | Bin 0 -> 2249 bytes .../instruction_screen_with_side_bar.lottie | Bin 0 -> 3318 bytes .../instructions_no_progress.lottie | Bin 2808 -> 0 bytes .../light_animation_with_bg.lottie | Bin 0 -> 2061 bytes .../liveness_guides_with_bg.lottie | Bin 0 -> 2759 bytes 43 files changed, 718 insertions(+), 282 deletions(-) rename Sources/SmileID/Classes/FaceDetector/{FaceDetectorV2.swift => EnhancedFaceDetector.swift} (85%) create mode 100644 Sources/SmileID/Classes/Helpers/Backport.swift create mode 100644 Sources/SmileID/Classes/Helpers/DMSansFont.swift create mode 100644 Sources/SmileID/Classes/Helpers/DeviceRotationViewModifier.swift create mode 100644 Sources/SmileID/Classes/Helpers/HapticManager.swift create mode 100644 Sources/SmileID/Classes/Helpers/StateObject.swift rename Sources/SmileID/Classes/SelfieCapture/{SelfieViewModelV2.swift => EnhancedSmartSelfieViewModel.swift} (68%) rename Sources/SmileID/Classes/SelfieCapture/View/{SelfieCaptureScreenV2.swift => EnhancedSelfieCaptureScreen.swift} (90%) rename Sources/SmileID/Classes/SelfieCapture/View/{OrchestratedSelfieCaptureScreenV2.swift => OrchestratedEnhancedSelfieCaptureScreen.swift} (86%) create mode 100644 Sources/SmileID/Resources/Fonts/DMSans-Bold.ttf create mode 100644 Sources/SmileID/Resources/Fonts/DMSans-Medium.ttf create mode 100644 Sources/SmileID/Resources/Fonts/DMSans-Regular.ttf create mode 100644 Sources/SmileID/Resources/LottieFiles/device_orientation.lottie create mode 100644 Sources/SmileID/Resources/LottieFiles/instruction_screen_with_side_bar.lottie delete mode 100644 Sources/SmileID/Resources/LottieFiles/instructions_no_progress.lottie create mode 100644 Sources/SmileID/Resources/LottieFiles/light_animation_with_bg.lottie create mode 100644 Sources/SmileID/Resources/LottieFiles/liveness_guides_with_bg.lottie diff --git a/Example/SmileID.xcodeproj/project.pbxproj b/Example/SmileID.xcodeproj/project.pbxproj index ebf5b21c6..01757d569 100644 --- a/Example/SmileID.xcodeproj/project.pbxproj +++ b/Example/SmileID.xcodeproj/project.pbxproj @@ -39,7 +39,7 @@ 20B6D5EC2C21CE660023D51C /* DataStoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20B6D5EB2C21CE660023D51C /* DataStoreError.swift */; }; 20C360C82C454C130008DBDE /* RootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C360C72C454C130008DBDE /* RootViewModel.swift */; }; 20DFA0EC2C21917100AC2AE7 /* View+TextSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20DFA0EB2C21917100AC2AE7 /* View+TextSelection.swift */; }; - 20F3D6F32C25F4D700B32751 /* (null) in Sources */ = {isa = PBXBuildFile; }; + 20F3D6F32C25F4D700B32751 /* BuildFile in Sources */ = {isa = PBXBuildFile; }; 20F3D6F62C25F5C100B32751 /* SmileID.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 20F3D6F42C25F5C100B32751 /* SmileID.xcdatamodeld */; }; 5829A8C02BC7429A001C1E7E /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 5829A8BF2BC7429A001C1E7E /* PrivacyInfo.xcprivacy */; }; 585BE4882AC7748E0091DDD8 /* RestartableTimerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585BE4872AC7748E0091DDD8 /* RestartableTimerTest.swift */; }; @@ -48,10 +48,11 @@ 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 620F1E982B69194900185CD2 /* AlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 620F1E972B69194900185CD2 /* AlertView.swift */; }; - 620F1E9A2B691ABB00185CD2 /* (null) in Resources */ = {isa = PBXBuildFile; }; + 620F1E9A2B691ABB00185CD2 /* BuildFile in Resources */ = {isa = PBXBuildFile; }; 624777D02B0CDC9F00952842 /* EnhancedKycWithIdInputScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 624777CF2B0CDC9F00952842 /* EnhancedKycWithIdInputScreen.swift */; }; 62F6766F2B0D173600417419 /* EnhancedKycWithIdInputScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F6766E2B0D173600417419 /* EnhancedKycWithIdInputScreenViewModel.swift */; }; 62F676712B0E00E800417419 /* EnhancedKycResultDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F676702B0E00E800417419 /* EnhancedKycResultDelegate.swift */; }; + 682911036C447B7771BFFECF /* Pods_SmileID_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00CD865E9409021F98A68FCB /* Pods_SmileID_Tests.framework */; }; 6AC9802B9D1A630961B5454B /* CodeScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC98436935FFEA40E632182 /* CodeScanner.swift */; }; 6AC983F056A8F9088D6CF3F7 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC982147640002B81F72DEC /* SettingsView.swift */; }; 6AC984526F49F4E8F52C7494 /* ScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC98BA00298258573CBCBD4 /* ScannerViewController.swift */; }; @@ -72,8 +73,7 @@ 91CB21A52AC10C61005AEBF5 /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CB21A42AC10C61005AEBF5 /* NavigationBar.swift */; }; 91D9FBC42AB481FE00A8D36B /* PollingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D9FBC32AB481FE00A8D36B /* PollingTests.swift */; }; 91D9FBD52AB8AB4700A8D36B /* CalculateSignatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91D9FBD42AB8AB4700A8D36B /* CalculateSignatureTests.swift */; }; - C449A598C4B78EB14B493293 /* Pods_SmileID_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02FE7FAA5FCF2B4172E1B98D /* Pods_SmileID_Example.framework */; }; - D4216F3C762CB28B31699F35 /* Pods_SmileID_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F3A0FB77997B5C7D540E6873 /* Pods_SmileID_Tests.framework */; }; + A6888C0B92766926550DD3A8 /* Pods_SmileID_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C99338201884FF93283419C /* Pods_SmileID_Example.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -87,7 +87,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 02FE7FAA5FCF2B4172E1B98D /* Pods_SmileID_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SmileID_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 00CD865E9409021F98A68FCB /* Pods_SmileID_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SmileID_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 08F30BD5CB20E5AB9E6E211E /* Pods-SmileID_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SmileID_Tests.debug.xcconfig"; path = "Target Support Files/Pods-SmileID_Tests/Pods-SmileID_Tests.debug.xcconfig"; sourceTree = ""; }; 1E59E33D2BA1E64C00D2BAD2 /* PartnerParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartnerParamsTests.swift; sourceTree = ""; }; 1E60ED322A29C306002695FF /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 1E60ED332A29C306002695FF /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; @@ -120,7 +121,7 @@ 20C360C72C454C130008DBDE /* RootViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = ""; }; 20DFA0EB2C21917100AC2AE7 /* View+TextSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+TextSelection.swift"; sourceTree = ""; }; 20F3D6F52C25F5C100B32751 /* SmileID.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = SmileID.xcdatamodel; sourceTree = ""; }; - 23822FF3F5838ECB320564F5 /* Pods-SmileID_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SmileID_Tests.release.xcconfig"; path = "Target Support Files/Pods-SmileID_Tests/Pods-SmileID_Tests.release.xcconfig"; sourceTree = ""; }; + 573532D688419D93192B1FA9 /* Pods-SmileID_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SmileID_Example.release.xcconfig"; path = "Target Support Files/Pods-SmileID_Example/Pods-SmileID_Example.release.xcconfig"; sourceTree = ""; }; 5829A8BF2BC7429A001C1E7E /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 585BE4872AC7748E0091DDD8 /* RestartableTimerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartableTimerTest.swift; sourceTree = ""; }; 58C5F1D72B05925800A6080C /* BiometricKycWithIdInputScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricKycWithIdInputScreen.swift; sourceTree = ""; }; @@ -148,9 +149,7 @@ 6AC9893915EBA33F6984A6D9 /* DocumentSelectorViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentSelectorViewModel.swift; sourceTree = ""; }; 6AC98BA00298258573CBCBD4 /* ScannerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScannerViewController.swift; sourceTree = ""; }; 6AC98BC49871655D87C7DEE3 /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; - 70FFBEEAB7A2E3A33EE9EA93 /* Pods-SmileID_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SmileID_Example.release.xcconfig"; path = "Target Support Files/Pods-SmileID_Example/Pods-SmileID_Example.release.xcconfig"; sourceTree = ""; }; - 784454F0D57FB1E2742E2156 /* Pods-SmileID_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SmileID_Example.debug.xcconfig"; path = "Target Support Files/Pods-SmileID_Example/Pods-SmileID_Example.debug.xcconfig"; sourceTree = ""; }; - 821B859ACAC64E44F59427CD /* Pods-SmileID_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SmileID_Tests.debug.xcconfig"; path = "Target Support Files/Pods-SmileID_Tests/Pods-SmileID_Tests.debug.xcconfig"; sourceTree = ""; }; + 7C99338201884FF93283419C /* Pods_SmileID_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SmileID_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 918321E02A52E36A00D6FB7F /* URLSessionRestServiceClientTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionRestServiceClientTests.swift; sourceTree = ""; }; 918321E12A52E36A00D6FB7F /* XCTestExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XCTestExtension.swift; path = ../../Tests/XCTestExtension.swift; sourceTree = ""; }; 918321E32A52E36A00D6FB7F /* DependencyContainerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DependencyContainerTests.swift; sourceTree = ""; }; @@ -162,7 +161,8 @@ 94E7560A47E255DD8215C183 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 9755B6A19CF28DE212F24C83 /* SmileID.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = SmileID.podspec; path = ../SmileID.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; C8CD2E3DB817D8C6334E9240 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; - F3A0FB77997B5C7D540E6873 /* Pods_SmileID_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SmileID_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CEDFCC61E89691A1B378145A /* Pods-SmileID_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SmileID_Tests.release.xcconfig"; path = "Target Support Files/Pods-SmileID_Tests/Pods-SmileID_Tests.release.xcconfig"; sourceTree = ""; }; + F346EF1ED1DF5227E7973AF9 /* Pods-SmileID_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SmileID_Example.debug.xcconfig"; path = "Target Support Files/Pods-SmileID_Example/Pods-SmileID_Example.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -170,7 +170,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C449A598C4B78EB14B493293 /* Pods_SmileID_Example.framework in Frameworks */, + A6888C0B92766926550DD3A8 /* Pods_SmileID_Example.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -178,7 +178,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D4216F3C762CB28B31699F35 /* Pods_SmileID_Tests.framework in Frameworks */, + 682911036C447B7771BFFECF /* Pods_SmileID_Tests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -280,15 +280,6 @@ path = Helpers; sourceTree = ""; }; - 34F29B5AE452286D795FCD29 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 02FE7FAA5FCF2B4172E1B98D /* Pods_SmileID_Example.framework */, - F3A0FB77997B5C7D540E6873 /* Pods_SmileID_Tests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 58C5F1D62B05922100A6080C /* BiometricKYC */ = { isa = PBXGroup; children = ( @@ -308,7 +299,7 @@ 607FACE81AFB9204008FA782 /* Tests */, 607FACD11AFB9204008FA782 /* Products */, 828BF541E068101B2E6ED55F /* Pods */, - 34F29B5AE452286D795FCD29 /* Frameworks */, + F16927B498A45E522FEB3C95 /* Frameworks */, ); sourceTree = ""; }; @@ -415,10 +406,10 @@ 828BF541E068101B2E6ED55F /* Pods */ = { isa = PBXGroup; children = ( - 784454F0D57FB1E2742E2156 /* Pods-SmileID_Example.debug.xcconfig */, - 70FFBEEAB7A2E3A33EE9EA93 /* Pods-SmileID_Example.release.xcconfig */, - 821B859ACAC64E44F59427CD /* Pods-SmileID_Tests.debug.xcconfig */, - 23822FF3F5838ECB320564F5 /* Pods-SmileID_Tests.release.xcconfig */, + F346EF1ED1DF5227E7973AF9 /* Pods-SmileID_Example.debug.xcconfig */, + 573532D688419D93192B1FA9 /* Pods-SmileID_Example.release.xcconfig */, + 08F30BD5CB20E5AB9E6E211E /* Pods-SmileID_Tests.debug.xcconfig */, + CEDFCC61E89691A1B378145A /* Pods-SmileID_Tests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -455,6 +446,15 @@ path = ../../Tests/Mocks; sourceTree = ""; }; + F16927B498A45E522FEB3C95 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7C99338201884FF93283419C /* Pods_SmileID_Example.framework */, + 00CD865E9409021F98A68FCB /* Pods_SmileID_Tests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -462,13 +462,13 @@ isa = PBXNativeTarget; buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SmileID_Example" */; buildPhases = ( - 09CE3487C58D7803D9B1254B /* [CP] Check Pods Manifest.lock */, + 8512F34E77FF72CD8905BE42 /* [CP] Check Pods Manifest.lock */, 607FACCC1AFB9204008FA782 /* Sources */, 917D79282AA8024400FA6624 /* SwiftLint */, 607FACCD1AFB9204008FA782 /* Frameworks */, 607FACCE1AFB9204008FA782 /* Resources */, C0BE335FFECD4DF6892309F3 /* Upload Debug Symbols to Sentry */, - 088EDD4D204057C2E745D8BC /* [CP] Embed Pods Frameworks */, + B4E6B9DF4045B6E197C117D0 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -483,7 +483,7 @@ isa = PBXNativeTarget; buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SmileID_Tests" */; buildPhases = ( - FB435C9B79B1BE5E6BD677F2 /* [CP] Check Pods Manifest.lock */, + C3515D148A433E1DCDCE17DF /* [CP] Check Pods Manifest.lock */, 607FACE11AFB9204008FA782 /* Sources */, 607FACE21AFB9204008FA782 /* Frameworks */, 607FACE31AFB9204008FA782 /* Resources */, @@ -549,7 +549,7 @@ buildActionMask = 2147483647; files = ( 1EFAB3172A375265008E3C13 /* Images.xcassets in Resources */, - 620F1E9A2B691ABB00185CD2 /* (null) in Resources */, + 620F1E9A2B691ABB00185CD2 /* BuildFile in Resources */, 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, 5829A8C02BC7429A001C1E7E /* PrivacyInfo.xcprivacy in Resources */, 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, @@ -566,39 +566,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 088EDD4D204057C2E745D8BC /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-SmileID_Example/Pods-SmileID_Example-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/ArkanaKeys/ArkanaKeys.framework", - "${BUILT_PRODUCTS_DIR}/ArkanaKeysInterfaces/ArkanaKeysInterfaces.framework", - "${BUILT_PRODUCTS_DIR}/FingerprintJS/FingerprintJS.framework", - "${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework", - "${BUILT_PRODUCTS_DIR}/SmileID/SmileID.framework", - "${BUILT_PRODUCTS_DIR}/ZIPFoundation/ZIPFoundation.framework", - "${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework", - "${BUILT_PRODUCTS_DIR}/netfox/netfox.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ArkanaKeys.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ArkanaKeysInterfaces.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FingerprintJS.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SmileID.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZIPFoundation.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lottie.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/netfox.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SmileID_Example/Pods-SmileID_Example-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 09CE3487C58D7803D9B1254B /* [CP] Check Pods Manifest.lock */ = { + 8512F34E77FF72CD8905BE42 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -638,6 +606,38 @@ shellPath = /bin/sh; shellScript = "# Type a script or drag a script file from your workspace to insert its path.\ncd ../Sources\n\"${PODS_ROOT}/SwiftLint/swiftlint\"\n"; }; + B4E6B9DF4045B6E197C117D0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-SmileID_Example/Pods-SmileID_Example-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/ArkanaKeys/ArkanaKeys.framework", + "${BUILT_PRODUCTS_DIR}/ArkanaKeysInterfaces/ArkanaKeysInterfaces.framework", + "${BUILT_PRODUCTS_DIR}/FingerprintJS/FingerprintJS.framework", + "${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework", + "${BUILT_PRODUCTS_DIR}/SmileID/SmileID.framework", + "${BUILT_PRODUCTS_DIR}/ZIPFoundation/ZIPFoundation.framework", + "${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework", + "${BUILT_PRODUCTS_DIR}/netfox/netfox.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ArkanaKeys.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ArkanaKeysInterfaces.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FingerprintJS.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SmileID.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZIPFoundation.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lottie.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/netfox.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SmileID_Example/Pods-SmileID_Example-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; C0BE335FFECD4DF6892309F3 /* Upload Debug Symbols to Sentry */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -653,7 +653,7 @@ shellPath = /bin/sh; shellScript = "# This script is responsable to upload debug symbols and source context for Sentry.\nif which sentry-cli >/dev/null; then\nexport SENTRY_ORG=smile-identity\nexport SENTRY_PROJECT=ios\nERROR=$(sentry-cli debug-files upload --include-sources \"$DWARF_DSYM_FOLDER_PATH\" 2>&1 >/dev/null)\nif [ ! $? -eq 0 ]; then\necho \"warning: sentry-cli - $ERROR\"\nfi\nelse\necho \"warning: sentry-cli not installed, download from https://github.com/getsentry/sentry-cli/releases\"\nfi\n"; }; - FB435C9B79B1BE5E6BD677F2 /* [CP] Check Pods Manifest.lock */ = { + C3515D148A433E1DCDCE17DF /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -698,7 +698,7 @@ 1ED53F6D2A2F28590020BEFB /* SmileTextField.swift in Sources */, 91CB21A52AC10C61005AEBF5 /* NavigationBar.swift in Sources */, 1ED53F6B2A2F28590020BEFB /* ProductCell.swift in Sources */, - 20F3D6F32C25F4D700B32751 /* (null) in Sources */, + 20F3D6F32C25F4D700B32751 /* BuildFile in Sources */, 1E60ED382A29C306002695FF /* Constants.swift in Sources */, 624777D02B0CDC9F00952842 /* EnhancedKycWithIdInputScreen.swift in Sources */, 1ED53F712A2F28590020BEFB /* EnterUserIDView.swift in Sources */, @@ -885,13 +885,13 @@ }; 607FACF01AFB9204008FA782 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 784454F0D57FB1E2742E2156 /* Pods-SmileID_Example.debug.xcconfig */; + baseConfigurationReference = F346EF1ED1DF5227E7973AF9 /* Pods-SmileID_Example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 36; + CURRENT_PROJECT_VERSION = 39; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 99P7YGX9Q6; @@ -901,7 +901,7 @@ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.3.0; + MARKETING_VERSION = 1.3.1; MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "com.smileidentity.example-ios"; PRODUCT_NAME = "Smile ID"; @@ -918,13 +918,13 @@ }; 607FACF11AFB9204008FA782 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 70FFBEEAB7A2E3A33EE9EA93 /* Pods-SmileID_Example.release.xcconfig */; + baseConfigurationReference = 573532D688419D93192B1FA9 /* Pods-SmileID_Example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Apple Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 36; + CURRENT_PROJECT_VERSION = 39; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 99P7YGX9Q6; @@ -934,7 +934,7 @@ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.3.0; + MARKETING_VERSION = 1.3.1; MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "com.smileidentity.example-ios"; PRODUCT_NAME = "Smile ID"; @@ -950,7 +950,7 @@ }; 607FACF31AFB9204008FA782 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 821B859ACAC64E44F59427CD /* Pods-SmileID_Tests.debug.xcconfig */; + baseConfigurationReference = 08F30BD5CB20E5AB9E6E211E /* Pods-SmileID_Tests.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; @@ -983,7 +983,7 @@ }; 607FACF41AFB9204008FA782 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 23822FF3F5838ECB320564F5 /* Pods-SmileID_Tests.release.xcconfig */; + baseConfigurationReference = CEDFCC61E89691A1B378145A /* Pods-SmileID_Tests.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Distribution"; diff --git a/Example/SmileID/Home/HomeView.swift b/Example/SmileID/Home/HomeView.swift index 430291899..86cc62d57 100644 --- a/Example/SmileID/Home/HomeView.swift +++ b/Example/SmileID/Home/HomeView.swift @@ -54,7 +54,7 @@ struct HomeView: View { ) ProductCell( image: "smart_selfie_enroll", - name: "SmartSelfie™ Enrollment (Strict Mode)", + name: "SmartSelfie™ Enrollment (Enhanced)", onClick: { viewModel.onProductClicked() }, @@ -74,7 +74,7 @@ struct HomeView: View { ) ProductCell( image: "smart_selfie_authentication", - name: "SmartSelfie™ Authentication (Strict Mode)", + name: "SmartSelfie™ Authentication (Enhanced)", onClick: { viewModel.onProductClicked() }, diff --git a/Example/SmileID/Home/ProductCell.swift b/Example/SmileID/Home/ProductCell.swift index a25967ce2..d8283b8f9 100644 --- a/Example/SmileID/Home/ProductCell.swift +++ b/Example/SmileID/Home/ProductCell.swift @@ -46,6 +46,16 @@ struct ProductCell: View { content: { NavigationView { content() + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button { + isPresented = false + } label: { + Text(SmileIDResourcesHelper.localizedString(for: "Action.Cancel")) + .foregroundColor(SmileID.theme.accent) + } + } + } } .environment(\.modalMode, $isPresented) } diff --git a/Example/SmileID/WelcomeScreen.swift b/Example/SmileID/WelcomeScreen.swift index 0fa83dd0c..d4ae0e37a 100644 --- a/Example/SmileID/WelcomeScreen.swift +++ b/Example/SmileID/WelcomeScreen.swift @@ -24,7 +24,7 @@ struct WelcomeScreen: View { .padding(.vertical) Text("To begin testing, you need to add a configuration from the Smile Portal") - .font(EpilogueFont.regular(with: 16)) + .font(DMSansFont.regular(with: 16)) .foregroundColor(SmileID.theme.onLight) .padding(.vertical) diff --git a/Example/Tests/FaceValidatorTests.swift b/Example/Tests/FaceValidatorTests.swift index a345a42f6..bde035c08 100644 --- a/Example/Tests/FaceValidatorTests.swift +++ b/Example/Tests/FaceValidatorTests.swift @@ -24,7 +24,7 @@ class FaceValidatorTests: XCTestCase { func testValidateWithValidFace() { let result = performValidation( faceBoundingBox: CGRect(x: 65, y: 164, width: 190, height: 190), - selfieQualityData: SelfieQualityData(failed: 0.1, passed: 0.9), + faceQuality: 0.5, brighness: 100 ) @@ -36,7 +36,7 @@ class FaceValidatorTests: XCTestCase { func testValidateWithFaceTooSmall() { let result = performValidation( faceBoundingBox: CGRect(x: 65, y: 164, width: 100, height: 100), - selfieQualityData: SelfieQualityData(failed: 0.1, passed: 0.9), + faceQuality: 0.5, brighness: 100 ) @@ -48,7 +48,7 @@ class FaceValidatorTests: XCTestCase { func testValidateWithFaceTooLarge() { let result = performValidation( faceBoundingBox: CGRect(x: 65, y: 164, width: 250, height: 250), - selfieQualityData: SelfieQualityData(failed: 0.1, passed: 0.9), + faceQuality: 0.5, brighness: 100 ) @@ -60,7 +60,7 @@ class FaceValidatorTests: XCTestCase { func testValidWithFaceOffCentre() { let result = performValidation( faceBoundingBox: CGRect(x: 125, y: 164, width: 190, height: 190), - selfieQualityData: SelfieQualityData(failed: 0.1, passed: 0.9), + faceQuality: 0.5, brighness: 100 ) @@ -72,8 +72,8 @@ class FaceValidatorTests: XCTestCase { func testValidateWithPoorBrightness() { let result = performValidation( faceBoundingBox: CGRect(x: 65, y: 164, width: 190, height: 190), - selfieQualityData: SelfieQualityData(failed: 0.1, passed: 0.9), - brighness: 70 + faceQuality: 0.5, + brighness: 35 ) XCTAssertTrue(result.faceInBounds) @@ -81,10 +81,10 @@ class FaceValidatorTests: XCTestCase { XCTAssertEqual(result.userInstruction, .goodLight) } - func testValidateWithPoorSelfieQuality() { + func testValidateWithPoorFaceQuality() { let result = performValidation( faceBoundingBox: CGRect(x: 65, y: 164, width: 190, height: 190), - selfieQualityData: SelfieQualityData(failed: 0.6, passed: 0.4), + faceQuality: 0.2, brighness: 70 ) @@ -96,7 +96,7 @@ class FaceValidatorTests: XCTestCase { func testValidateWithLivenessTask() { let result = performValidation( faceBoundingBox: CGRect(x: 65, y: 164, width: 190, height: 190), - selfieQualityData: SelfieQualityData(failed: 0.3, passed: 0.7), + faceQuality: 0.3, brighness: 100, livenessTask: .lookLeft ) @@ -111,7 +111,7 @@ class FaceValidatorTests: XCTestCase { extension FaceValidatorTests { func performValidation( faceBoundingBox: CGRect, - selfieQualityData: SelfieQualityData, + faceQuality: Float, brighness: Int, livenessTask: LivenessTask? = nil ) -> FaceValidationResult { @@ -124,7 +124,7 @@ extension FaceValidatorTests { ) faceValidator.validate( faceGeometry: faceGeometry, - selfieQuality: selfieQualityData, + faceQuality: faceQuality, brightness: brighness, currentLivenessTask: livenessTask ) diff --git a/Sources/SmileID/Classes/Camera/CameraManager.swift b/Sources/SmileID/Classes/Camera/CameraManager.swift index 439f31d95..f57cee050 100644 --- a/Sources/SmileID/Classes/Camera/CameraManager.swift +++ b/Sources/SmileID/Classes/Camera/CameraManager.swift @@ -35,6 +35,8 @@ class CameraManager: NSObject, ObservableObject { (session.inputs.first as? AVCaptureDeviceInput)?.device.position } + private(set) var cameraName: String? + // Used to queue and then resume tasks while waiting for Camera permissions private let sessionQueue = DispatchQueue(label: "com.smileidentity.ios") private let videoOutput = AVCaptureVideoDataOutput() @@ -90,6 +92,7 @@ class CameraManager: NSObject, ObservableObject { status = .failed return } + cameraName = camera.uniqueID do { let cameraInput = try AVCaptureDeviceInput(device: camera) diff --git a/Sources/SmileID/Classes/Camera/CameraViewController.swift b/Sources/SmileID/Classes/Camera/CameraViewController.swift index 26f2797dd..d96b773d7 100644 --- a/Sources/SmileID/Classes/Camera/CameraViewController.swift +++ b/Sources/SmileID/Classes/Camera/CameraViewController.swift @@ -3,7 +3,7 @@ import Vision import AVFoundation class CameraViewController: UIViewController { - var faceDetector: FaceDetectorV2? + var faceDetector: EnhancedFaceDetector? var previewLayer: AVCaptureVideoPreviewLayer? private weak var cameraManager: CameraManager? diff --git a/Sources/SmileID/Classes/FaceDetector/FaceDetectorV2.swift b/Sources/SmileID/Classes/FaceDetector/EnhancedFaceDetector.swift similarity index 85% rename from Sources/SmileID/Classes/FaceDetector/FaceDetectorV2.swift rename to Sources/SmileID/Classes/FaceDetector/EnhancedFaceDetector.swift index 8a9ba94f6..d00eee4a8 100644 --- a/Sources/SmileID/Classes/FaceDetector/FaceDetectorV2.swift +++ b/Sources/SmileID/Classes/FaceDetector/EnhancedFaceDetector.swift @@ -16,16 +16,15 @@ protocol FaceDetectorViewDelegate: NSObjectProtocol { protocol FaceDetectorResultDelegate: AnyObject { func faceDetector( - _ detector: FaceDetectorV2, + _ detector: EnhancedFaceDetector, didDetectFace faceGeometry: FaceGeometryData, withFaceQuality faceQuality: Float, - selfieQuality: SelfieQualityData, brightness: Int ) - func faceDetector(_ detector: FaceDetectorV2, didFailWithError error: Error) + func faceDetector(_ detector: EnhancedFaceDetector, didFailWithError error: Error) } -class FaceDetectorV2: NSObject { +class EnhancedFaceDetector: NSObject { private var selfieQualityModel: SelfieQualityDetector? private let cropSize = (width: 120, height: 120) @@ -78,29 +77,32 @@ class FaceDetectorV2: NSObject { let uiImage = UIImage(pixelBuffer: imageBuffer) let brightness = self.calculateBrightness(uiImage) - let croppedImage = try self.cropImageToFace(uiImage) - - let selfieQualityData = try self.selfieQualityRequest(imageBuffer: croppedImage) + let faceGeometryData: FaceGeometryData if #available(iOS 15.0, *) { - let faceGeometryData = FaceGeometryData( + faceGeometryData = FaceGeometryData( boundingBox: convertedBoundingBox, roll: faceObservation.roll ?? 0.0, yaw: faceObservation.yaw ?? 0.0, pitch: faceObservation.pitch ?? 0.0, direction: faceDirection(faceObservation: faceObservation) ) - self.resultDelegate? - .faceDetector( - self, - didDetectFace: faceGeometryData, - withFaceQuality: faceQualityObservation.faceCaptureQuality ?? 0.0, - selfieQuality: selfieQualityData, - brightness: brightness - ) - } else { - // Fallback on earlier versions + } else { // Fallback on earlier versions + faceGeometryData = FaceGeometryData( + boundingBox: convertedBoundingBox, + roll: faceObservation.roll ?? 0.0, + yaw: faceObservation.yaw ?? 0.0, + pitch: 0.0, + direction: faceDirection(faceObservation: faceObservation) + ) } + self.resultDelegate? + .faceDetector( + self, + didDetectFace: faceGeometryData, + withFaceQuality: faceQualityObservation.faceCaptureQuality ?? 0.0, + brightness: brightness + ) } catch { self.resultDelegate?.faceDetector(self, didFailWithError: error) } @@ -180,8 +182,8 @@ class FaceDetectorV2: NSObject { private func calculateBrightness(_ image: UIImage?) -> Int { guard let image, let cgImage = image.cgImage, - let imageData = cgImage.dataProvider?.data, - let dataPointer = CFDataGetBytePtr(imageData) + let imageData = cgImage.dataProvider?.data, + let dataPointer = CFDataGetBytePtr(imageData) else { return 0 } diff --git a/Sources/SmileID/Classes/FaceDetector/FaceValidator.swift b/Sources/SmileID/Classes/FaceDetector/FaceValidator.swift index bee6b2ece..80f54e312 100644 --- a/Sources/SmileID/Classes/FaceDetector/FaceValidator.swift +++ b/Sources/SmileID/Classes/FaceDetector/FaceValidator.swift @@ -15,9 +15,10 @@ final class FaceValidator { private var faceLayoutGuideFrame: CGRect = .zero // MARK: Constants - private let selfieQualityThreshold: Float = 0.5 - private let luminanceThreshold: ClosedRange = 80...200 - private let faceBoundsMultiplier: CGFloat = 1.5 + private let faceQualityThreshold: Float = 0.25 + private let luminanceThreshold: ClosedRange = 40...200 + private let selfiefaceBoundsMultiplier: CGFloat = 1.5 + private let livenessfaceBoundsMultiplier: CGFloat = 2.2 private let faceBoundsThreshold: CGFloat = 50 init() {} @@ -28,7 +29,7 @@ final class FaceValidator { func validate( faceGeometry: FaceGeometryData, - selfieQuality: SelfieQualityData, + faceQuality: Float, brightness: Int, currentLivenessTask: LivenessTask? ) { @@ -42,14 +43,14 @@ final class FaceValidator { // check brightness let isAcceptableBrightness = luminanceThreshold.contains(brightness) - // check selfie quality - let isAcceptableSelfieQuality = checkSelfieQuality(selfieQuality) + // check face quality + let isAcceptableFaceQuality = checkFaceQuality(faceQuality) // check that face is ready for capture let hasDetectedValidFace = checkValidFace( isAcceptableBounds, isAcceptableBrightness, - isAcceptableSelfieQuality + isAcceptableFaceQuality ) // determine what instruction/animation to display to users @@ -57,7 +58,7 @@ final class FaceValidator { from: faceBoundsState, detectedValidFace: hasDetectedValidFace, isAcceptableBrightness: isAcceptableBrightness, - isAcceptableSelfieQuality: isAcceptableSelfieQuality, + isAcceptableFaceQuality: isAcceptableFaceQuality, livenessTask: currentLivenessTask ) @@ -73,7 +74,7 @@ final class FaceValidator { from faceBoundsState: FaceBoundsState, detectedValidFace: Bool, isAcceptableBrightness: Bool, - isAcceptableSelfieQuality: Bool, + isAcceptableFaceQuality: Bool, livenessTask: LivenessTask? ) -> SelfieCaptureInstruction? { if detectedValidFace { @@ -88,29 +89,36 @@ final class FaceValidator { } } return nil - } else if faceBoundsState == .detectedFaceOffCentre { + } else if faceBoundsState == .detectedFaceOffCentre + || faceBoundsState == .detectedFaceNotWithinFrame { return .headInFrame } else if faceBoundsState == .detectedFaceTooSmall { return .moveCloser } else if faceBoundsState == .detectedFaceTooLarge { return .moveBack - } else if !isAcceptableSelfieQuality || !isAcceptableBrightness { + } else if !isAcceptableFaceQuality || !isAcceptableBrightness { return .goodLight } return nil } // MARK: Validation Checks - private func checkFaceSizeAndPosition(using boundingBox: CGRect, shouldCheckCentering: Bool) -> FaceBoundsState { + private func checkFaceSizeAndPosition( + using boundingBox: CGRect, + shouldCheckCentering: Bool + ) -> FaceBoundsState { let maxFaceWidth = faceLayoutGuideFrame.width - 20 + let faceBoundsMultiplier = shouldCheckCentering ? selfiefaceBoundsMultiplier : livenessfaceBoundsMultiplier let minFaceWidth = faceLayoutGuideFrame.width / faceBoundsMultiplier + // check how far/close face is if boundingBox.width > maxFaceWidth { return .detectedFaceTooLarge } else if boundingBox.width < minFaceWidth { return .detectedFaceTooSmall } + // check that face is centered for selfie capture only if shouldCheckCentering { let horizontalOffset = abs(boundingBox.midX - faceLayoutGuideFrame.midX) let verticalOffset = abs(boundingBox.midY - faceLayoutGuideFrame.midY) @@ -123,15 +131,15 @@ final class FaceValidator { return .detectedFaceAppropriateSizeAndPosition } - private func checkSelfieQuality(_ value: SelfieQualityData) -> Bool { - return value.passed >= selfieQualityThreshold + private func checkFaceQuality(_ value: Float) -> Bool { + return value >= faceQualityThreshold } private func checkValidFace( _ isAcceptableBounds: Bool, _ isAcceptableBrightness: Bool, - _ isAcceptableSelfieQuality: Bool + _ isAcceptableFaceQuality: Bool ) -> Bool { - return isAcceptableBounds && isAcceptableBrightness && isAcceptableSelfieQuality + return isAcceptableBounds && isAcceptableBrightness && isAcceptableFaceQuality } } diff --git a/Sources/SmileID/Classes/FaceDetector/LivenessCheckManager.swift b/Sources/SmileID/Classes/FaceDetector/LivenessCheckManager.swift index 9805aa481..033ed0f35 100644 --- a/Sources/SmileID/Classes/FaceDetector/LivenessCheckManager.swift +++ b/Sources/SmileID/Classes/FaceDetector/LivenessCheckManager.swift @@ -6,6 +6,20 @@ enum LivenessTask { case lookLeft case lookRight case lookUp + + static var numberOfFramesToCapture: Int { + if #available(iOS 15.0, *) { + return 2 + } else { + return 3 + } + } +} + +protocol LivenessCheckManagerDelegate: AnyObject { + func didCompleteLivenessTask() + func didCompleteLivenessChallenge() + func livenessChallengeTimeout() } class LivenessCheckManager: ObservableObject { @@ -13,20 +27,18 @@ class LivenessCheckManager: ObservableObject { private var livenessTaskSequence: [LivenessTask] = [] /// The index pointing to the current task in the sequence. private var currentTaskIndex: Int = 0 - /// The view model associated with the selfie capture process. - weak var selfieViewModel: SelfieViewModelV2? - /// A closure to trigger photo capture during the liveness check. - var captureImage: (() -> Void)? + + weak var delegate: LivenessCheckManagerDelegate? // MARK: Constants /// The minimum threshold for yaw (left-right head movement) private let minYawAngleThreshold: CGFloat = 0.15 /// The maximum threshold for yaw (left-right head movement) - private let maxYawAngleThreshold: CGFloat = 0.3 + private let maxYawAngleThreshold: CGFloat = 0.25 /// The minimum threshold for pitch (up-down head movement) private let minPitchAngleThreshold: CGFloat = 0.15 /// The maximum threshold for pitch (up-down head movement) - private let maxPitchAngleThreshold: CGFloat = 0.3 + private let maxPitchAngleThreshold: CGFloat = 0.25 /// The timeout duration for each task in seconds. private let taskTimeoutDuration: TimeInterval = 120 @@ -51,7 +63,11 @@ class LivenessCheckManager: ObservableObject { /// Initializes the LivenessCheckManager with a shuffled set of tasks. init() { - livenessTaskSequence = [.lookLeft, .lookRight, .lookUp].shuffled() + if #available(iOS 15.0, *) { + livenessTaskSequence = [.lookLeft, .lookRight, .lookUp].shuffled() + } else { + livenessTaskSequence = [.lookLeft, .lookRight].shuffled() + } } /// Cleans up resources when the manager is no longer needed. @@ -65,7 +81,8 @@ class LivenessCheckManager: ObservableObject { DispatchQueue.main.async { self.taskTimer = Timer.scheduledTimer( withTimeInterval: 1.0, - repeats: true) { [weak self] _ in + repeats: true + ) { [weak self] _ in self?.taskTimerFired() } } @@ -88,7 +105,7 @@ class LivenessCheckManager: ObservableObject { /// Handles the timeout event for a task. private func handleTaskTimeout() { stopTaskTimer() - selfieViewModel?.perform(action: .activeLivenessTimeout) + delegate?.livenessChallengeTimeout() } /// Advances to the next task in the sequence @@ -160,12 +177,11 @@ class LivenessCheckManager: ObservableObject { /// Completes the current task and moves to the next one. /// If all tasks are completed, it signals the completion of the liveness challenge. private func completeCurrentTask() { - captureImage?() - captureImage?() + delegate?.didCompleteLivenessTask() if !advanceToNextTask() { // Liveness challenge complete - selfieViewModel?.perform(action: .activeLivenessCompleted) + delegate?.didCompleteLivenessChallenge() self.currentTask = nil } } diff --git a/Sources/SmileID/Classes/FaceDetector/Models.swift b/Sources/SmileID/Classes/FaceDetector/Models.swift index 0d0bdd2ac..6566942c1 100644 --- a/Sources/SmileID/Classes/FaceDetector/Models.swift +++ b/Sources/SmileID/Classes/FaceDetector/Models.swift @@ -17,6 +17,7 @@ enum FaceBoundsState { case detectedFaceTooSmall case detectedFaceTooLarge case detectedFaceOffCentre + case detectedFaceNotWithinFrame case detectedFaceAppropriateSizeAndPosition } diff --git a/Sources/SmileID/Classes/Helpers/Backport.swift b/Sources/SmileID/Classes/Helpers/Backport.swift new file mode 100644 index 000000000..118f4b594 --- /dev/null +++ b/Sources/SmileID/Classes/Helpers/Backport.swift @@ -0,0 +1,52 @@ +// swiftlint:disable all +import SwiftUI +import ObjectiveC + +/// Provides a convenient method for backporting API, +/// including types, functions, properties, property wrappers and more. +/// +/// To backport a SwiftUI Label for example, you could apply the +/// following extension: +/// +/// extension Backport where Content == Any { +/// public struct Label { } +/// } +/// +/// Now if we want to provide further extensions to our backport type, +/// we need to ensure we retain the `Content == Any` generic requirement: +/// +/// extension Backport.Label where Content == Any, Title == Text, Icon == Image { +/// public init(_ title: S, systemName: String) { } +/// } +/// +/// In addition to types, we can also provide backports for properties +/// and methods: +/// +/// extension Backport.Label where Content: View { +/// func onChange(of value: Value, perform action: (Value) -> Void) -> some View { +/// // `content` provides access to the extended type +/// content.modifier(OnChangeModifier(value, action)) +/// } +/// } +/// +public struct Backport { + /// The underlying content this backport represents. + public let wrapped: Wrapped + + /// Initializes a new Backport for the specified content. + /// - Parameter content: The content (type) that's being backported + public init(_ wrapped: Wrapped) { + self.wrapped = wrapped + } +} + +public extension Backport where Wrapped == Any { + init(_ wrapped: Wrapped) { + self.wrapped = wrapped + } +} + +public extension NSObjectProtocol { + /// Wraps an `NSObject` that can be extended to provide backport functionality. + var backport: Backport { .init(self) } +} diff --git a/Sources/SmileID/Classes/Helpers/DMSansFont.swift b/Sources/SmileID/Classes/Helpers/DMSansFont.swift new file mode 100644 index 000000000..e4ea80da5 --- /dev/null +++ b/Sources/SmileID/Classes/Helpers/DMSansFont.swift @@ -0,0 +1,36 @@ +import SwiftUI + +enum DMSans: String, CaseIterable { + case regular = "DMSans-Regular" + case medium = "DMSans-Medium" + case bold = "DMSans-Bold" +} + +public struct DMSansFont: FontType { + public static var medium: Font { + medium(with: SmileIDResourcesHelper.pointSize) + } + + public static var bold: Font { + bold(with: SmileIDResourcesHelper.pointSize) + } + + public static var pointSize: CGFloat { + SmileIDResourcesHelper.pointSize + } + + public static func regular(with size: CGFloat) -> Font { + SmileIDResourcesHelper.loadFontIfNeeded(name: DMSans.regular.rawValue) + return Font.custom(DMSans.regular.rawValue, size: size) + } + + public static func medium(with size: CGFloat) -> Font { + SmileIDResourcesHelper.loadFontIfNeeded(name: DMSans.medium.rawValue) + return Font.custom(DMSans.medium.rawValue, size: size) + } + + public static func bold(with size: CGFloat) -> Font { + SmileIDResourcesHelper.loadFontIfNeeded(name: DMSans.bold.rawValue) + return Font.custom(DMSans.bold.rawValue, size: size) + } +} diff --git a/Sources/SmileID/Classes/Helpers/DeviceRotationViewModifier.swift b/Sources/SmileID/Classes/Helpers/DeviceRotationViewModifier.swift new file mode 100644 index 000000000..4ca0cc07c --- /dev/null +++ b/Sources/SmileID/Classes/Helpers/DeviceRotationViewModifier.swift @@ -0,0 +1,19 @@ +import SwiftUI + +struct DeviceRotationViewModifier: ViewModifier { + let action: (UIDeviceOrientation) -> Void + + func body(content: Content) -> some View { + content + .onAppear() + .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + action(UIDevice.current.orientation) + } + } +} + +extension View { + func onRotate(perform action: @escaping (UIDeviceOrientation) -> Void) -> some View { + self.modifier(DeviceRotationViewModifier(action: action)) + } +} diff --git a/Sources/SmileID/Classes/Helpers/HapticManager.swift b/Sources/SmileID/Classes/Helpers/HapticManager.swift new file mode 100644 index 000000000..fd1cf28b3 --- /dev/null +++ b/Sources/SmileID/Classes/Helpers/HapticManager.swift @@ -0,0 +1,23 @@ +import UIKit + +class HapticManager { + static let shared = HapticManager() + + private init() {} + + // MARK: Notification Feedback + + /// Triggers a notification haptic feedback + /// - Parameter type: The notification type (success, warning, error) + func notification(type: UINotificationFeedbackGenerator.FeedbackType) { + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(type) + } + + // MARK: Impact Feedback + + func impact(style: UIImpactFeedbackGenerator.FeedbackStyle) { + let generator = UIImpactFeedbackGenerator(style: style) + generator.impactOccurred() + } +} diff --git a/Sources/SmileID/Classes/Helpers/StateObject.swift b/Sources/SmileID/Classes/Helpers/StateObject.swift new file mode 100644 index 000000000..098f2392f --- /dev/null +++ b/Sources/SmileID/Classes/Helpers/StateObject.swift @@ -0,0 +1,151 @@ +// swiftlint:disable all +import Combine +import SwiftUI + +@available(iOS, deprecated: 14.0) +@available(macOS, deprecated: 11.0) +@available(tvOS, deprecated: 14.0) +@available(watchOS, deprecated: 7.0) +public extension Backport where Wrapped: ObservableObject { + + /// A property wrapper type that instantiates an observable object. + /// + /// Create a state object in a ``SwiftUI/View``, ``SwiftUI/App``, or + /// ``SwiftUI/Scene`` by applying the `@Backport.StateObject` attribute to a property + /// declaration and providing an initial value that conforms to the + /// + /// protocol: + /// + /// @Backport.StateObject var model = DataModel() + /// + /// SwiftUI creates a new instance of the object only once for each instance of + /// the structure that declares the object. When published properties of the + /// observable object change, SwiftUI updates the parts of any view that depend + /// on those properties: + /// + /// Text(model.title) // Updates the view any time `title` changes. + /// + /// You can pass the state object into a property that has the + /// ``SwiftUI/ObservedObject`` attribute. You can alternatively add the object + /// to the environment of a view hierarchy by applying the + /// ``SwiftUI/View/environmentObject(_:)`` modifier: + /// + /// ContentView() + /// .environmentObject(model) + /// + /// If you create an environment object as shown in the code above, you can + /// read the object inside `ContentView` or any of its descendants + /// using the ``SwiftUI/EnvironmentObject`` attribute: + /// + /// @EnvironmentObject var model: DataModel + /// + /// Get a ``SwiftUI/Binding`` to one of the state object's properties using the + /// `$` operator. Use a binding when you want to create a two-way connection to + /// one of the object's properties. For example, you can let a + /// ``SwiftUI/Toggle`` control a Boolean value called `isEnabled` stored in the + /// model: + /// + /// Toggle("Enabled", isOn: $model.isEnabled) + @propertyWrapper struct StateObject: DynamicProperty { + private final class Wrapper: ObservableObject { + private var subject = PassthroughSubject() + + var value: Wrapped? { + didSet { + cancellable = nil + cancellable = value?.objectWillChange + .sink { [subject] _ in subject.send() } + } + } + + private var cancellable: AnyCancellable? + + var objectWillChange: AnyPublisher { + subject.eraseToAnyPublisher() + } + } + + @State private var state = Wrapper() + + @ObservedObject private var observedObject = Wrapper() + + private var thunk: () -> Wrapped + + /// The underlying value referenced by the state object. + /// + /// The wrapped value property provides primary access to the value's data. + /// However, you don't access `wrappedValue` directly. Instead, use the + /// property variable created with the `@Backport.StateObject` attribute: + /// + /// @Backport.StateObject var contact = Contact() + /// + /// var body: some View { + /// Text(contact.name) // Accesses contact's wrapped value. + /// } + /// + /// When you change a property of the wrapped value, you can access the new + /// value immediately. However, SwiftUI updates views displaying the value + /// asynchronously, so the user interface might not update immediately. + public var wrappedValue: Wrapped { + if let object = state.value { + return object + } else { + let object = thunk() + state.value = object + return object + } + } + + /// A projection of the state object that creates bindings to its + /// properties. + /// + /// Use the projected value to pass a binding value down a view hierarchy. + /// To get the projected value, prefix the property variable with `$`. For + /// example, you can get a binding to a model's `isEnabled` Boolean so that + /// a ``SwiftUI/Toggle`` view can control the value: + /// + /// struct MyView: View { + /// @Backport.StateObject var model = DataModel() + /// + /// var body: some View { + /// Toggle("Enabled", isOn: $model.isEnabled) + /// } + /// } + public var projectedValue: ObservedObject.Wrapper { + ObservedObject(wrappedValue: wrappedValue).projectedValue + } + + /// Creates a new state object with an initial wrapped value. + /// + /// You don’t call this initializer directly. Instead, declare a property + /// with the `@Backport.StateObject` attribute in a ``SwiftUI/View``, + /// ``SwiftUI/App``, or ``SwiftUI/Scene``, and provide an initial value: + /// + /// struct MyView: View { + /// @Backport.StateObject var model = DataModel() + /// + /// // ... + /// } + /// + /// SwiftUI creates only one instance of the state object for each + /// container instance that you declare. In the code above, SwiftUI + /// creates `model` only the first time it initializes a particular instance + /// of `MyView`. On the other hand, each different instance of `MyView` + /// receives a distinct copy of the data model. + /// + /// - Parameter thunk: An initial value for the state object. + public init(wrappedValue thunk: @autoclosure @escaping () -> Wrapped) { + self.thunk = thunk + } + + public mutating func update() { + if state.value == nil { + state.value = thunk() + } + if observedObject.value !== state.value { + observedObject.value = state.value + } + } + } + +} diff --git a/Sources/SmileID/Classes/Helpers/Theme.swift b/Sources/SmileID/Classes/Helpers/Theme.swift index e3a73b020..40ba54432 100644 --- a/Sources/SmileID/Classes/Helpers/Theme.swift +++ b/Sources/SmileID/Classes/Helpers/Theme.swift @@ -68,19 +68,23 @@ public extension SmileIdTheme { // TO-DO: Rename fonts when Kwame comes up with a naming convention var header1: Font { - EpilogueFont.bold(with: 32) + DMSansFont.bold(with: 24) } var header2: Font { - EpilogueFont.bold(with: 20) + DMSansFont.bold(with: 20) + } + + var header3: Font { + DMSansFont.medium(with: 20) } var header4: Font { - EpilogueFont.bold(with: 16) + DMSansFont.medium(with: 16) } var header5: Font { - EpilogueFont.medium(with: 12) + DMSansFont.medium(with: 12) } var button: Font { @@ -88,7 +92,7 @@ public extension SmileIdTheme { } var body: Font { - EpilogueFont.medium(with: 14) + DMSansFont.regular(with: 16) } } diff --git a/Sources/SmileID/Classes/Networking/Models/FailureReason.swift b/Sources/SmileID/Classes/Networking/Models/FailureReason.swift index 8d901a64c..f68bcd028 100644 --- a/Sources/SmileID/Classes/Networking/Models/FailureReason.swift +++ b/Sources/SmileID/Classes/Networking/Models/FailureReason.swift @@ -1,11 +1,17 @@ import Foundation -public enum FailureReason { - case activeLivenessTimedOut +public enum FailureReason: Encodable { + case mobileActiveLivenessTimeout - var key: String { + private enum CodingKeys: String, CodingKey { + case mobileActiveLivenessTimeout = "mobile_active_liveness_timed_out" + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) switch self { - case .activeLivenessTimedOut: return "mobile_active_liveness_timed_out" + case .mobileActiveLivenessTimeout: + try container.encode(true, forKey: .mobileActiveLivenessTimeout) } } } diff --git a/Sources/SmileID/Classes/Networking/ServiceRunnable.swift b/Sources/SmileID/Classes/Networking/ServiceRunnable.swift index 16a944630..567e92f7a 100644 --- a/Sources/SmileID/Classes/Networking/ServiceRunnable.swift +++ b/Sources/SmileID/Classes/Networking/ServiceRunnable.swift @@ -337,15 +337,13 @@ extension ServiceRunnable { body.append(lineBreak.data(using: .utf8)!) // Append failure reason if available - if let failureReason { - let activeLivenessTimedOutString = "\(failureReason == .activeLivenessTimedOut)" - if let valueData = "\(activeLivenessTimedOutString)\(lineBreak)".data(using: .utf8) { - body.append("--\(boundary)\(lineBreak)".data(using: .utf8)!) - body.append( - "Content-Disposition: form-data; name=\"\(failureReason.key)\"\(lineBreak + lineBreak)".data( - using: .utf8)!) - body.append(valueData) - } + if let failureReason, + let failureReasonData = try? encoder.encode(failureReason) { + body.append("--\(boundary)\(lineBreak)".data(using: .utf8)!) + body.append("Content-Disposition: form-data; name=\"failure_reason\"\(lineBreak)".data(using: .utf8)!) + body.append("Content-Type: application/json\(lineBreak + lineBreak)".data(using: .utf8)!) + body.append(failureReasonData) + body.append(lineBreak.data(using: .utf8)!) } // Append final boundary diff --git a/Sources/SmileID/Classes/SelfieCapture/CaptureGuideAnimation.swift b/Sources/SmileID/Classes/SelfieCapture/CaptureGuideAnimation.swift index 13d274ba0..b9c3f26a8 100644 --- a/Sources/SmileID/Classes/SelfieCapture/CaptureGuideAnimation.swift +++ b/Sources/SmileID/Classes/SelfieCapture/CaptureGuideAnimation.swift @@ -8,11 +8,12 @@ enum CaptureGuideAnimation: Equatable { case lookRight case lookLeft case lookUp + case turnPhoneUp var fileName: String { switch self { case .goodLight: - return "light_animation" + return "light_animation_with_bg" case .headInFrame: return "positioning" case .moveBack: @@ -20,11 +21,13 @@ enum CaptureGuideAnimation: Equatable { case .moveCloser: return "positioning" case .lookRight: - return "liveness_guides" + return "liveness_guides_with_bg" case .lookLeft: - return "liveness_guides" + return "liveness_guides_with_bg" case .lookUp: - return "liveness_guides" + return "liveness_guides_with_bg" + case .turnPhoneUp: + return "device_orientation" } } diff --git a/Sources/SmileID/Classes/SelfieCapture/SelfieViewModelV2.swift b/Sources/SmileID/Classes/SelfieCapture/EnhancedSmartSelfieViewModel.swift similarity index 68% rename from Sources/SmileID/Classes/SelfieCapture/SelfieViewModelV2.swift rename to Sources/SmileID/Classes/SelfieCapture/EnhancedSmartSelfieViewModel.swift index 8057d48f7..b0cf63d08 100644 --- a/Sources/SmileID/Classes/SelfieCapture/SelfieViewModelV2.swift +++ b/Sources/SmileID/Classes/SelfieCapture/EnhancedSmartSelfieViewModel.swift @@ -2,10 +2,10 @@ import ARKit import Combine import SwiftUI -public class SelfieViewModelV2: ObservableObject { +public class EnhancedSmartSelfieViewModel: ObservableObject { // MARK: Dependencies let cameraManager = CameraManager.shared - let faceDetector = FaceDetectorV2() + let faceDetector = EnhancedFaceDetector() private let faceValidator = FaceValidator() var livenessCheckManager = LivenessCheckManager() private var subscribers = Set() @@ -13,25 +13,31 @@ public class SelfieViewModelV2: ObservableObject { private let metadataTimerStart = MonotonicTime() // MARK: Private Properties + private var deviceOrientation: UIDeviceOrientation { + return UIDevice.current.orientation + } private var faceLayoutGuideFrame = CGRect(x: 0, y: 0, width: 250, height: 350) private var elapsedGuideAnimationDelay: TimeInterval = 0 + private var currentFrameBuffer: CVPixelBuffer? var selfieImage: UIImage? - var selfieImageURL: URL? { + private var selfieImageURL: URL? { didSet { DispatchQueue.main.async { self.selfieCaptured = self.selfieImage != nil } } } - var livenessImages: [URL] = [] + private var livenessImages: [URL] = [] private var hasDetectedValidFace: Bool = false + private var isCapturingLivenessImages = false private var shouldBeginLivenessChallenge: Bool { hasDetectedValidFace && selfieImage != nil && livenessCheckManager.currentTask != nil } private var shouldSubmitJob: Bool { selfieImage != nil && livenessImages.count == numLivenessImages } - private var forcedFailure: Bool = false + private var submissionTask: Task? + private var failureReason: FailureReason? private var apiResponse: SmartSelfieResponse? private var error: Error? @Published public var errorMessageRes: String? @@ -100,13 +106,15 @@ public class SelfieViewModelV2: ObservableObject { } deinit { + subscribers.removeAll() stopGuideAnimationDelayTimer() + invalidateSubmissionTask() } private func initialSetup() { self.faceValidator.delegate = self self.faceDetector.resultDelegate = self - self.livenessCheckManager.selfieViewModel = self + self.livenessCheckManager.delegate = self self.faceValidator.setLayoutGuideFrame(with: faceLayoutGuideFrame) self.userInstruction = .headInFrame @@ -116,6 +124,7 @@ public class SelfieViewModelV2: ObservableObject { with: livenessCheckManager.$lookRightProgress, livenessCheckManager.$lookUpProgress ) + .receive(on: DispatchQueue.main) .sink { [weak self] _ in DispatchQueue.main.async { self?.resetGuideAnimationDelayTimer() @@ -127,46 +136,48 @@ public class SelfieViewModelV2: ObservableObject { .receive(on: DispatchQueue.main) .filter { $0 == .unauthorized } .map { _ in AlertState.cameraUnauthorized } - .sink { alert in self.unauthorizedAlert = alert } + .sink { [weak self] alert in self?.unauthorizedAlert = alert } .store(in: &subscribers) cameraManager.sampleBufferPublisher + .receive(on: DispatchQueue.main) .throttle( for: 0.35, scheduler: DispatchQueue.global(qos: .userInitiated), latest: true ) // Drop the first ~2 seconds to allow the user to settle in - .dropFirst(5) + .dropFirst(5) .compactMap { $0 } - .sink(receiveValue: analyzeFrame) + .sink { [weak self] imageBuffer in + self?.handleCameraImageBuffer(imageBuffer) + } .store(in: &subscribers) } + private func handleCameraImageBuffer(_ imageBuffer: CVPixelBuffer) { + if deviceOrientation == .portrait { + analyzeFrame(imageBuffer: imageBuffer) + } else { + publishUserInstruction(.turnPhoneUp) + } + } + private func analyzeFrame(imageBuffer: CVPixelBuffer) { + currentFrameBuffer = imageBuffer faceDetector.processImageBuffer(imageBuffer) if hasDetectedValidFace && selfieImage == nil { captureSelfieImage(imageBuffer) + HapticManager.shared.notification(type: .success) livenessCheckManager.initiateLivenessCheck() } - - livenessCheckManager.captureImage = { [weak self] in - self?.captureLivenessImage(imageBuffer) - } } // MARK: Actions func perform(action: SelfieViewModelAction) { switch action { case let .windowSizeDetected(windowRect, safeAreaInsets): - handleWindowSizeChanged(toRect: windowRect, edgeInsets: safeAreaInsets) - case .activeLivenessCompleted: - self.cameraManager.pauseSession() - handleSubmission() - case .activeLivenessTimeout: - self.forcedFailure = true - self.cameraManager.pauseSession() - handleSubmission() + handleWindowSizeChanged(to: windowRect, edgeInsets: safeAreaInsets) case .onViewAppear: handleViewAppeared() case .jobProcessingDone: @@ -189,7 +200,7 @@ public class SelfieViewModelV2: ObservableObject { } // MARK: Action Handlers -extension SelfieViewModelV2 { +extension EnhancedSmartSelfieViewModel { private func resetGuideAnimationDelayTimer() { elapsedGuideAnimationDelay = 0 showGuideAnimation = false @@ -219,14 +230,13 @@ extension SelfieViewModelV2 { selfieImage = nil livenessImages = [] selfieCaptureState = .capturingSelfie - forcedFailure = false + failureReason = nil } - private func handleWindowSizeChanged(toRect: CGSize, edgeInsets: EdgeInsets) { + private func handleWindowSizeChanged(to rect: CGSize, edgeInsets: EdgeInsets) { let topPadding: CGFloat = edgeInsets.top + 100 - print(edgeInsets.top) faceLayoutGuideFrame = CGRect( - x: (toRect.width / 2) - faceLayoutGuideFrame.width / 2, + x: (rect.width / 2) - faceLayoutGuideFrame.width / 2, y: topPadding, width: faceLayoutGuideFrame.width, height: faceLayoutGuideFrame.height @@ -241,17 +251,49 @@ extension SelfieViewModelV2 { pixelBuffer, height: selfieImageSize, orientation: .up - ) + ), + let uiImage = UIImage(data: imageData) else { throw SmileIDError.unknown("Error resizing selfie image") } - self.selfieImage = UIImage(data: imageData) + self.selfieImage = flipImageForPreview(uiImage) self.selfieImageURL = try LocalStorage.createSelfieFile(jobId: jobId, selfieFile: imageData) } catch { handleError(error) } } + private func flipImageForPreview(_ image: UIImage) -> UIImage? { + guard let cgImage = image.cgImage else { return nil } + + let contextSize = CGSize(width: image.size.width, height: image.size.height) + UIGraphicsBeginImageContextWithOptions(contextSize, false, 1.0) + defer { + UIGraphicsEndImageContext() + } + guard let context = UIGraphicsGetCurrentContext() else { + return nil + } + + // Apply a 180° counterclockwise rotation + // Translate the context to the center before rotating + // to ensure the image rotates around its center + context.translateBy(x: contextSize.width / 2, y: contextSize.height / 2) + context.rotate(by: -.pi) + + // Draw the image + context.draw( + cgImage, + in: CGRect( + x: -image.size.width / 2, y: -image.size.height / 2, width: image.size.width, height: image.size.height) + ) + + // Get the new UIImage from the context + let correctedImage = UIGraphicsGetImageFromCurrentImageContext() + + return correctedImage + } + private func captureLivenessImage(_ pixelBuffer: CVPixelBuffer) { do { guard @@ -271,14 +313,15 @@ extension SelfieViewModelV2 { } private func handleError(_ error: Error) { - print(error.localizedDescription) + debugPrint(error.localizedDescription) } private func handleSubmission() { DispatchQueue.main.async { self.selfieCaptureState = .processing(.inProgress) } - Task { + guard submissionTask == nil else { return } + submissionTask = Task { try await submitJob() } } @@ -290,36 +333,34 @@ extension SelfieViewModelV2 { } // MARK: FaceDetectorResultDelegate Methods -extension SelfieViewModelV2: FaceDetectorResultDelegate { +extension EnhancedSmartSelfieViewModel: FaceDetectorResultDelegate { func faceDetector( - _ detector: FaceDetectorV2, + _ detector: EnhancedFaceDetector, didDetectFace faceGeometry: FaceGeometryData, withFaceQuality faceQuality: Float, - selfieQuality: SelfieQualityData, brightness: Int ) { faceValidator .validate( faceGeometry: faceGeometry, - selfieQuality: selfieQuality, + faceQuality: faceQuality, brightness: brightness, currentLivenessTask: self.livenessCheckManager.currentTask ) - if shouldBeginLivenessChallenge { + if shouldBeginLivenessChallenge && !isCapturingLivenessImages { livenessCheckManager.processFaceGeometry(faceGeometry) } } - func faceDetector(_ detector: FaceDetectorV2, didFailWithError error: Error) { + func faceDetector(_ detector: EnhancedFaceDetector, didFailWithError error: Error) { DispatchQueue.main.async { self.publishUserInstruction(.headInFrame) } - print(error.localizedDescription) } } // MARK: FaceValidatorDelegate Methods -extension SelfieViewModelV2: FaceValidatorDelegate { +extension EnhancedSmartSelfieViewModel: FaceValidatorDelegate { func updateValidationResult(_ result: FaceValidationResult) { DispatchQueue.main.async { self.faceInBounds = result.faceInBounds @@ -329,8 +370,57 @@ extension SelfieViewModelV2: FaceValidatorDelegate { } } +// MARK: LivenessCheckManagerDelegate Methods +extension EnhancedSmartSelfieViewModel: LivenessCheckManagerDelegate { + func didCompleteLivenessTask() { + isCapturingLivenessImages = true + let capturedFrames = 0 + captureNextFrame(capturedFrames: capturedFrames) + } + + private func captureNextFrame(capturedFrames: Int) { + let maxFrames = LivenessTask.numberOfFramesToCapture + guard capturedFrames < maxFrames, + let currentFrame = currentFrameBuffer else { + return + } + + captureLivenessImage(currentFrame) + let nextCapturedFrames = capturedFrames + 1 + if nextCapturedFrames < maxFrames { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [weak self] in + self?.captureNextFrame(capturedFrames: nextCapturedFrames) + } + } else { + isCapturingLivenessImages = false + HapticManager.shared.notification(type: .success) + } + } + + func didCompleteLivenessChallenge() { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.cameraManager.pauseSession() + self.handleSubmission() + } + } + + func livenessChallengeTimeout() { + let remainingImages = numLivenessImages - livenessImages.count + let count = remainingImages > 0 ? remainingImages : 0 + for _ in 0.. (MultipartBody, [MultipartBody]) { - guard let smartSelfieImage = createMultipartBody(from: selfieImage) else { - throw SmileIDError.unknown("Failed to process selfie image") + guard let smartSelfieImage = createMultipartBody(from: selfieImageUrl) else { + throw SmileIDError.fileNotFound("Could not create multipart body for file") } let smartSelfieLivenessImages = livenessImages.compactMap { @@ -136,7 +131,9 @@ final class SelfieSubmissionManager { private func createMultipartBody(from fileURL: URL?) -> MultipartBody? { guard let fileURL = fileURL, let imageData = try? Data(contentsOf: fileURL) - else { return nil } + else { + return nil + } return MultipartBody( withImage: imageData, forKey: fileURL.lastPathComponent, @@ -148,7 +145,7 @@ final class SelfieSubmissionManager { authResponse: AuthenticationResponse, smartSelfieImage: MultipartBody, smartSelfieLivenessImages: [MultipartBody], - forcedFailure: Bool + failureReason: FailureReason? ) async throws -> SmartSelfieResponse { if isEnroll { return try await SmileID.api @@ -162,7 +159,7 @@ final class SelfieSubmissionManager { callbackUrl: SmileID.callbackUrl, sandboxResult: nil, allowNewEnroll: allowNewEnroll, - failureReason: forcedFailure ? .activeLivenessTimedOut : nil, + failureReason: failureReason, metadata: localMetadata.metadata ) } else { @@ -176,7 +173,7 @@ final class SelfieSubmissionManager { partnerParams: extraPartnerParams, callbackUrl: SmileID.callbackUrl, sandboxResult: nil, - failureReason: forcedFailure ? .activeLivenessTimedOut : nil, + failureReason: failureReason, metadata: localMetadata.metadata ) } @@ -187,7 +184,7 @@ final class SelfieSubmissionManager { try LocalStorage.moveToSubmittedJobs(jobId: self.jobId) // Update the references to the submitted selfie and liveness images - self.selfieImage = try LocalStorage.getFileByType( + self.selfieImageUrl = try LocalStorage.getFileByType( jobId: jobId, fileType: FileType.selfie, submitted: true @@ -204,7 +201,7 @@ final class SelfieSubmissionManager { do { let didMove = try LocalStorage.handleOfflineJobFailure(jobId: self.jobId, error: smileIDError) if didMove { - self.selfieImage = try LocalStorage.getFileByType(jobId: jobId, fileType: .selfie, submitted: true) + self.selfieImageUrl = try LocalStorage.getFileByType(jobId: jobId, fileType: .selfie, submitted: true) self.livenessImages = try LocalStorage.getFilesByType(jobId: jobId, fileType: .liveness, submitted: true) ?? [] } diff --git a/Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift b/Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift index c41e1270f..a3097ca03 100644 --- a/Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift +++ b/Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift @@ -82,7 +82,7 @@ public class SelfieViewModel: ObservableObject, ARKitSmileDelegate { .receive(on: DispatchQueue.main) .filter { $0 == .unauthorized } .map { _ in AlertState.cameraUnauthorized } - .sink { alert in self.unauthorizedAlert = alert } + .sink { [weak self] alert in self?.unauthorizedAlert = alert } .store(in: &subscribers) cameraManager.sampleBufferPublisher @@ -91,7 +91,9 @@ public class SelfieViewModel: ObservableObject, ARKitSmileDelegate { // Drop the first ~2 seconds to allow the user to settle in .dropFirst(5) .compactMap { $0 } - .sink(receiveValue: analyzeImage) + .sink { [weak self] imageBuffer in + self?.analyzeImage(image: imageBuffer) + } .store(in: &subscribers) localMetadata.addMetadata( @@ -116,7 +118,8 @@ public class SelfieViewModel: ObservableObject, ARKitSmileDelegate { } do { - try faceDetector.detect(imageBuffer: image) { [self] request, error in + try faceDetector.detect(imageBuffer: image) { [weak self] request, error in + guard let self else { return } if let error { print("Error analyzing image: \(error.localizedDescription)") self.error = error @@ -190,8 +193,8 @@ public class SelfieViewModel: ObservableObject, ARKitSmileDelegate { let userNeedsToSmile = livenessImages.count > numLivenessImages / 2 - DispatchQueue.main.async { [self] in - directive = userNeedsToSmile ? "Instructions.Smile" : "Instructions.Capturing" + DispatchQueue.main.async { + self.directive = userNeedsToSmile ? "Instructions.Smile" : "Instructions.Capturing" } // TODO: Use mouth deformation as an alternate signal for non-ARKit capture diff --git a/Sources/SmileID/Classes/SelfieCapture/SelfieViewModelAction.swift b/Sources/SmileID/Classes/SelfieCapture/SelfieViewModelAction.swift index 579803a9f..7b9c1c159 100644 --- a/Sources/SmileID/Classes/SelfieCapture/SelfieViewModelAction.swift +++ b/Sources/SmileID/Classes/SelfieCapture/SelfieViewModelAction.swift @@ -5,10 +5,7 @@ enum SelfieViewModelAction { case onViewAppear case windowSizeDetected(CGSize, EdgeInsets) - // Face Detection Actions - case activeLivenessCompleted - case activeLivenessTimeout - + // Job Submission Actions case jobProcessingDone case retryJobSubmission diff --git a/Sources/SmileID/Classes/SelfieCapture/SmartSelfieResultDelegate.swift b/Sources/SmileID/Classes/SelfieCapture/SmartSelfieResultDelegate.swift index afaeadbea..3e0680daa 100644 --- a/Sources/SmileID/Classes/SelfieCapture/SmartSelfieResultDelegate.swift +++ b/Sources/SmileID/Classes/SelfieCapture/SmartSelfieResultDelegate.swift @@ -19,3 +19,8 @@ public protocol SmartSelfieResultDelegate { /// - Parameter error: The error returned from a failed selfie capture func didError(error: Error) } + +extension SmartSelfieResultDelegate { + /// The selfie capture operation was canceled. + func didCancel() {} +} diff --git a/Sources/SmileID/Classes/SelfieCapture/View/CameraView.swift b/Sources/SmileID/Classes/SelfieCapture/View/CameraView.swift index aa04685b2..98da6d547 100644 --- a/Sources/SmileID/Classes/SelfieCapture/View/CameraView.swift +++ b/Sources/SmileID/Classes/SelfieCapture/View/CameraView.swift @@ -8,7 +8,7 @@ struct CameraView: UIViewControllerRepresentable { init( cameraManager: CameraManager, - selfieViewModel: SelfieViewModelV2? = nil + selfieViewModel: EnhancedSmartSelfieViewModel? = nil ) { let controller = CameraViewController(cameraManager: cameraManager) controller.faceDetector = selfieViewModel?.faceDetector diff --git a/Sources/SmileID/Classes/SelfieCapture/View/SelfieCaptureScreenV2.swift b/Sources/SmileID/Classes/SelfieCapture/View/EnhancedSelfieCaptureScreen.swift similarity index 90% rename from Sources/SmileID/Classes/SelfieCapture/View/SelfieCaptureScreenV2.swift rename to Sources/SmileID/Classes/SelfieCapture/View/EnhancedSelfieCaptureScreen.swift index 3822a43c1..734fe0968 100644 --- a/Sources/SmileID/Classes/SelfieCapture/View/SelfieCaptureScreenV2.swift +++ b/Sources/SmileID/Classes/SelfieCapture/View/EnhancedSelfieCaptureScreen.swift @@ -1,12 +1,14 @@ import SwiftUI -public struct SelfieCaptureScreenV2: View { - @ObservedObject var viewModel: SelfieViewModelV2 +public struct EnhancedSelfieCaptureScreen: View { + @Backport.StateObject var viewModel: EnhancedSmartSelfieViewModel let showAttribution: Bool private let faceShape = FaceShape() @Environment(\.modalMode) private var modalMode + private(set) var originalBrightness = UIScreen.main.brightness + public var body: some View { GeometryReader { proxy in VStack(spacing: 10) { @@ -34,7 +36,8 @@ public struct SelfieCaptureScreenV2: View { showGuideAnimation: viewModel.showGuideAnimation, guideAnimation: viewModel.userInstruction?.guideAnimation ) - if let currentLivenessTask = viewModel.livenessCheckManager.currentTask { + if let currentLivenessTask = viewModel.livenessCheckManager.currentTask, + viewModel.faceInBounds { LivenessGuidesView( currentLivenessTask: currentLivenessTask, topArcProgress: $viewModel.livenessCheckManager.lookUpProgress, @@ -72,10 +75,10 @@ public struct SelfieCaptureScreenV2: View { Spacer() UserInstructionsView( instruction: processingState.title, - message: getErrorSubtitle( + message: processingState == .error ? getErrorSubtitle( errorMessageRes: viewModel.errorMessageRes, errorMessage: viewModel.errorMessage - ) + ) : nil ) } SubmissionStatusView(processState: processingState) @@ -110,10 +113,14 @@ public struct SelfieCaptureScreenV2: View { } .navigationBarHidden(true) .onAppear { + UIScreen.main.brightness = 1 + UIApplication.shared.isIdleTimerDisabled = true viewModel.perform(action: .windowSizeDetected(proxy.size, proxy.safeAreaInsets)) viewModel.perform(action: .onViewAppear) } .onDisappear { + UIScreen.main.brightness = originalBrightness + UIApplication.shared.isIdleTimerDisabled = false viewModel.cameraManager.pauseSession() } .alert(item: $viewModel.unauthorizedAlert) { alert in diff --git a/Sources/SmileID/Classes/SelfieCapture/View/FaceBoundingArea.swift b/Sources/SmileID/Classes/SelfieCapture/View/FaceBoundingArea.swift index 440e255fd..cac02325b 100644 --- a/Sources/SmileID/Classes/SelfieCapture/View/FaceBoundingArea.swift +++ b/Sources/SmileID/Classes/SelfieCapture/View/FaceBoundingArea.swift @@ -22,24 +22,18 @@ struct FaceBoundingArea: View { if let guideAnimation = guideAnimation, showGuideAnimation { - faceShape - .fill(.black.opacity(0.5)) - .frame(width: 270, height: 370) - .overlay( - LottieView { - try await DotLottieFile - .named( - guideAnimation.fileName, - bundle: SmileIDResourcesHelper.bundle - ) - } - .playbackMode(playbackMode) - .frame(width: 224, height: 224) - ) - .clipShape(faceShape) - .onAppear { - playbackMode = getPlaybackMode(guideAnimation) - } + LottieView { + try await DotLottieFile + .named( + guideAnimation.fileName, + bundle: SmileIDResourcesHelper.bundle + ) + } + .playbackMode(playbackMode) + .frame(width: 224, height: 224) + .onAppear { + playbackMode = getPlaybackMode(guideAnimation) + } } } } diff --git a/Sources/SmileID/Classes/SelfieCapture/View/FaceShapedProgressIndicator.swift b/Sources/SmileID/Classes/SelfieCapture/View/FaceShapedProgressIndicator.swift index f139dda96..6c2aa9e96 100644 --- a/Sources/SmileID/Classes/SelfieCapture/View/FaceShapedProgressIndicator.swift +++ b/Sources/SmileID/Classes/SelfieCapture/View/FaceShapedProgressIndicator.swift @@ -4,7 +4,7 @@ import SwiftUI struct FaceShapedProgressIndicator: View { let progress: Double private let strokeWidth = 10 - private let faceShape = FaceShape().scale(x: 0.8, y: 0.6).offset(y: -50) + private let faceShape = FaceShape().scale(x: 0.8, y: 0.55).offset(y: -50) private let bgColor = Color.white.opacity(0.8) var body: some View { bgColor diff --git a/Sources/SmileID/Classes/SelfieCapture/View/LivenessCaptureInstructionsView.swift b/Sources/SmileID/Classes/SelfieCapture/View/LivenessCaptureInstructionsView.swift index 153c4a50b..81dccba45 100644 --- a/Sources/SmileID/Classes/SelfieCapture/View/LivenessCaptureInstructionsView.swift +++ b/Sources/SmileID/Classes/SelfieCapture/View/LivenessCaptureInstructionsView.swift @@ -2,32 +2,25 @@ import Lottie import SwiftUI public struct LivenessCaptureInstructionsView: View { - @Environment(\.modalMode) private var modalMode @State private var showSelfieCaptureView: Bool = false private let showAttribution: Bool - private let viewModel: SelfieViewModelV2 + private let viewModel: EnhancedSmartSelfieViewModel - public init(showAttribution: Bool, viewModel: SelfieViewModelV2) { + public init(showAttribution: Bool, viewModel: EnhancedSmartSelfieViewModel) { self.showAttribution = showAttribution self.viewModel = viewModel } public var body: some View { VStack { - HStack { - Button { - self.modalMode.wrappedValue = false - } label: { - Text(SmileIDResourcesHelper.localizedString(for: "Action.Cancel")) - .foregroundColor(SmileID.theme.accent) - } - Spacer() - } - ZStack { LottieView { - try await DotLottieFile.named("instructions_no_progress", bundle: SmileIDResourcesHelper.bundle) + try await DotLottieFile + .named( + "instruction_screen_with_side_bar", + bundle: SmileIDResourcesHelper.bundle + ) } .playing(loopMode: .loop) .frame(width: 235, height: 235) @@ -37,13 +30,14 @@ public struct LivenessCaptureInstructionsView: View { Text(SmileIDResourcesHelper.localizedString(for: "Instructions.SelfieCapture")) .multilineTextAlignment(.center) .font(SmileID.theme.header4) + .lineSpacing(4) .foregroundColor(SmileID.theme.tertiary) Spacer() VStack(spacing: 20) { NavigationLink( - destination: SelfieCaptureScreenV2( + destination: EnhancedSelfieCaptureScreen( viewModel: viewModel, showAttribution: showAttribution ), diff --git a/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreenV2.swift b/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedEnhancedSelfieCaptureScreen.swift similarity index 86% rename from Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreenV2.swift rename to Sources/SmileID/Classes/SelfieCapture/View/OrchestratedEnhancedSelfieCaptureScreen.swift index d78716078..6cd1b67cd 100644 --- a/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreenV2.swift +++ b/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedEnhancedSelfieCaptureScreen.swift @@ -3,14 +3,12 @@ import SwiftUI /// Orchestrates the selfie capture flow - navigates between instructions, requesting permissions, /// showing camera view, and displaying processing screen -public struct OrchestratedSelfieCaptureScreenV2: View { +public struct OrchestratedEnhancedSelfieCaptureScreen: View { public let allowAgentMode: Bool public let showAttribution: Bool public let showInstructions: Bool public let onResult: SmartSelfieResultDelegate - private let viewModel: SelfieViewModelV2 - - private var originalBrightness = UIScreen.main.brightness + private let viewModel: EnhancedSmartSelfieViewModel public init( userId: String, @@ -29,7 +27,7 @@ public struct OrchestratedSelfieCaptureScreenV2: View { self.showAttribution = showAttribution self.showInstructions = showInstructions self.onResult = onResult - self.viewModel = SelfieViewModelV2( + self.viewModel = EnhancedSmartSelfieViewModel( isEnroll: isEnroll, userId: userId, jobId: jobId, @@ -49,7 +47,7 @@ public struct OrchestratedSelfieCaptureScreenV2: View { viewModel: viewModel ) } else { - SelfieCaptureScreenV2( + EnhancedSelfieCaptureScreen( viewModel: viewModel, showAttribution: showAttribution ) diff --git a/Sources/SmileID/Classes/SmileID.swift b/Sources/SmileID/Classes/SmileID.swift index 07aaa38c0..3572650f2 100644 --- a/Sources/SmileID/Classes/SmileID.swift +++ b/Sources/SmileID/Classes/SmileID.swift @@ -309,7 +309,7 @@ public class SmileID { delegate: SmartSelfieResultDelegate ) -> some View { if useStrictMode { - OrchestratedSelfieCaptureScreenV2( + OrchestratedEnhancedSelfieCaptureScreen( userId: userId, jobId: jobId, isEnroll: true, @@ -371,7 +371,7 @@ public class SmileID { delegate: SmartSelfieResultDelegate ) -> some View { if useStrictMode { - OrchestratedSelfieCaptureScreenV2( + OrchestratedEnhancedSelfieCaptureScreen( userId: userId, jobId: jobId, isEnroll: true, diff --git a/Sources/SmileID/Classes/Util.swift b/Sources/SmileID/Classes/Util.swift index 64563c110..bb2c23b3a 100644 --- a/Sources/SmileID/Classes/Util.swift +++ b/Sources/SmileID/Classes/Util.swift @@ -79,6 +79,10 @@ func toErrorMessage(error: SmileIDError) -> (String, String?) { return (error.localizedDescription, nil) case let .httpError(_, message): return ("", message) + case let .fileNotFound(message): + return (message, nil) + case let .unknown(message): + return (message, nil) default: return ("Confirmation.FailureReason", nil) } diff --git a/Sources/SmileID/Resources/Fonts/DMSans-Bold.ttf b/Sources/SmileID/Resources/Fonts/DMSans-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4f5412dc8bb339e3a608597df2c4670d83dacc12 GIT binary patch literal 56268 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 Tk8=9|rSuuKq7Jm4R|ozdoXwx6 literal 0 HcmV?d00001 diff --git a/Sources/SmileID/Resources/Fonts/DMSans-Regular.ttf b/Sources/SmileID/Resources/Fonts/DMSans-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..07266ae18b44ea06b673b8abed76fc056864585f GIT binary patch literal 56348 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 literal 0 HcmV?d00001 diff --git a/Sources/SmileID/Resources/LottieFiles/instruction_screen_with_side_bar.lottie b/Sources/SmileID/Resources/LottieFiles/instruction_screen_with_side_bar.lottie new file mode 100644 index 0000000000000000000000000000000000000000..792d9345e3f2324565530d6fbe41913d8cdb5bc6 GIT binary patch literal 3318 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 QwT1FB3ofe;!(Y=s0W>-u^8f$< literal 0 HcmV?d00001 diff --git a/Sources/SmileID/Resources/LottieFiles/instructions_no_progress.lottie b/Sources/SmileID/Resources/LottieFiles/instructions_no_progress.lottie deleted file mode 100644 index 3207aaa70ce1fe8201f4faaf8e937c22245d99a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2808 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 diff --git a/Sources/SmileID/Resources/LottieFiles/light_animation_with_bg.lottie b/Sources/SmileID/Resources/LottieFiles/light_animation_with_bg.lottie new file mode 100644 index 0000000000000000000000000000000000000000..56784698cc7ad9a725d7bfb312e293b853312459 GIT binary patch literal 2061 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 literal 0 HcmV?d00001