From 9229db9f21dd7b6cdcbf35d6a4ed74b205a5b9b4 Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Tue, 10 Dec 2024 14:39:13 +0100 Subject: [PATCH 1/5] set the right flag for isEnroll in orchestrated enhanced selfie capture screen. update the location of selfie and livenessimages when submission fails. --- .../EnhancedSmartSelfieViewModel.swift | 6 ++++- .../SelfieSubmissionManager.swift | 25 ++++++++++++++++--- .../OrchestratedSelfieCaptureScreen.swift | 8 +++--- .../View/SelfieCaptureScreen.swift | 7 +----- Sources/SmileID/Classes/SmileID.swift | 2 +- 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/Sources/SmileID/Classes/SelfieCapture/EnhancedSmartSelfieViewModel.swift b/Sources/SmileID/Classes/SelfieCapture/EnhancedSmartSelfieViewModel.swift index b0cf63d0..e02f2104 100644 --- a/Sources/SmileID/Classes/SelfieCapture/EnhancedSmartSelfieViewModel.swift +++ b/Sources/SmileID/Classes/SelfieCapture/EnhancedSmartSelfieViewModel.swift @@ -484,7 +484,9 @@ extension EnhancedSmartSelfieViewModel: SelfieSubmissionDelegate { func submissionDidFail( with error: Error, errorMessage: String?, - errorMessageRes: String? + errorMessageRes: String?, + updatedSelfieImageUrl: URL?, + updatedLivenessImages: [URL] ) { invalidateSubmissionTask() HapticManager.shared.notification(type: .error) @@ -493,6 +495,8 @@ extension EnhancedSmartSelfieViewModel: SelfieSubmissionDelegate { self.errorMessage = errorMessage self.errorMessageRes = errorMessageRes self.selfieCaptureState = .processing(.error) + self.selfieImageURL = updatedSelfieImageUrl + self.livenessImages = updatedLivenessImages } } diff --git a/Sources/SmileID/Classes/SelfieCapture/SelfieSubmissionManager.swift b/Sources/SmileID/Classes/SelfieCapture/SelfieSubmissionManager.swift index e470dbfa..c25217b0 100644 --- a/Sources/SmileID/Classes/SelfieCapture/SelfieSubmissionManager.swift +++ b/Sources/SmileID/Classes/SelfieCapture/SelfieSubmissionManager.swift @@ -2,7 +2,13 @@ import SwiftUI protocol SelfieSubmissionDelegate: AnyObject { func submissionDidSucceed(_ apiResponse: SmartSelfieResponse) - func submissionDidFail(with error: Error, errorMessage: String?, errorMessageRes: String?) + func submissionDidFail( + with error: Error, + errorMessage: String?, + errorMessageRes: String?, + updatedSelfieImageUrl: URL?, + updatedLivenessImages: [URL] + ) } final class SelfieSubmissionManager { @@ -211,20 +217,31 @@ final class SelfieSubmissionManager { .submissionDidFail( with: error, errorMessage: errorMessageRes, - errorMessageRes: errorMessage + errorMessageRes: errorMessage, + updatedSelfieImageUrl: selfieImageUrl, + updatedLivenessImages: livenessImages ) return } if SmileID.allowOfflineMode, SmileIDError.isNetworkFailure(error: smileIDError) { - self.delegate?.submissionDidFail(with: smileIDError, errorMessage: nil, errorMessageRes: "Offline.Message") + self.delegate? + .submissionDidFail( + with: smileIDError, + errorMessage: nil, + errorMessageRes: "Offline.Message", + updatedSelfieImageUrl: selfieImageUrl, + updatedLivenessImages: livenessImages + ) } else { let (errorMessageRes, errorMessage) = toErrorMessage(error: smileIDError) self.delegate? .submissionDidFail( with: smileIDError, errorMessage: errorMessage, - errorMessageRes: errorMessageRes + errorMessageRes: errorMessageRes, + updatedSelfieImageUrl: selfieImageUrl, + updatedLivenessImages: livenessImages ) } } diff --git a/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreen.swift b/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreen.swift index fd608cb4..ff098185 100644 --- a/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreen.swift +++ b/Sources/SmileID/Classes/SelfieCapture/View/OrchestratedSelfieCaptureScreen.swift @@ -67,8 +67,8 @@ public struct OrchestratedSelfieCaptureScreen: View { for: "Confirmation.Failure" ), errorSubtitle: getErrorSubtitle( - errorMessageRes: $viewModel.errorMessageRes.wrappedValue, - errorMessage: $viewModel.errorMessage.wrappedValue + errorMessageRes: viewModel.errorMessageRes, + errorMessage: viewModel.errorMessage ), errorIcon: SmileIDResourcesHelper.Scan, continueButtonText: SmileIDResourcesHelper.localizedString( @@ -105,8 +105,8 @@ public struct OrchestratedSelfieCaptureScreen: View { ) } else { SelfieCaptureScreen( - allowAgentMode: allowAgentMode, - viewModel: viewModel + viewModel: viewModel, + allowAgentMode: allowAgentMode ) .onAppear { viewModel.updateLocalMetadata(localMetadata) diff --git a/Sources/SmileID/Classes/SelfieCapture/View/SelfieCaptureScreen.swift b/Sources/SmileID/Classes/SelfieCapture/View/SelfieCaptureScreen.swift index 11c8bfad..2e4f29ea 100644 --- a/Sources/SmileID/Classes/SelfieCapture/View/SelfieCaptureScreen.swift +++ b/Sources/SmileID/Classes/SelfieCapture/View/SelfieCaptureScreen.swift @@ -4,13 +4,8 @@ import SwiftUI /// The actual selfie capture screen, which shows the camera preview and the progress indicator public struct SelfieCaptureScreen: View { + @Backport.StateObject var viewModel: SelfieViewModel let allowAgentMode: Bool - @ObservedObject var viewModel: SelfieViewModel - - public init(allowAgentMode: Bool, viewModel: SelfieViewModel) { - self.allowAgentMode = allowAgentMode - self.viewModel = viewModel - } public var body: some View { ZStack { diff --git a/Sources/SmileID/Classes/SmileID.swift b/Sources/SmileID/Classes/SmileID.swift index 3572650f..074b826b 100644 --- a/Sources/SmileID/Classes/SmileID.swift +++ b/Sources/SmileID/Classes/SmileID.swift @@ -374,7 +374,7 @@ public class SmileID { OrchestratedEnhancedSelfieCaptureScreen( userId: userId, jobId: jobId, - isEnroll: true, + isEnroll: false, allowNewEnroll: allowNewEnroll, allowAgentMode: allowAgentMode, showAttribution: showAttribution, From c58f557ec027aaec30bf708f0864ee913d647ba7 Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Tue, 10 Dec 2024 15:22:10 +0100 Subject: [PATCH 2/5] grab last selfie enrollment userid from pasteboard, use new generated user id for selfie authentication --- Example/SmileID/Home/HomeView.swift | 12 ++++++------ Example/SmileID/Home/HomeViewModel.swift | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Example/SmileID/Home/HomeView.swift b/Example/SmileID/Home/HomeView.swift index 86cc62d5..086b7fb4 100644 --- a/Example/SmileID/Home/HomeView.swift +++ b/Example/SmileID/Home/HomeView.swift @@ -28,11 +28,11 @@ struct HomeView: View { }, content: { SmileID.smartSelfieEnrollmentScreen( - userId: viewModel.smartSelfieEnrollmentUserId, + userId: viewModel.newUserId, jobId: viewModel.newJobId, allowAgentMode: true, delegate: SmartSelfieEnrollmentDelegate( - userId: viewModel.smartSelfieEnrollmentUserId, + userId: viewModel.newUserId, onEnrollmentSuccess: viewModel.onSmartSelfieEnrollment, onError: viewModel.didError ) @@ -47,7 +47,7 @@ struct HomeView: View { }, content: { SmartSelfieAuthWithUserIdEntry( - initialUserId: viewModel.smartSelfieEnrollmentUserId, + initialUserId: viewModel.lastSelfieEnrollmentUserId ?? "", delegate: viewModel ) } @@ -60,12 +60,12 @@ struct HomeView: View { }, content: { SmileID.smartSelfieEnrollmentScreen( - userId: viewModel.smartSelfieEnrollmentUserId, + userId: viewModel.newUserId, jobId: viewModel.newJobId, allowAgentMode: true, useStrictMode: true, delegate: SmartSelfieEnrollmentDelegate( - userId: viewModel.smartSelfieEnrollmentUserId, + userId: viewModel.newUserId, onEnrollmentSuccess: viewModel.onSmartSelfieEnrollment, onError: viewModel.didError ) @@ -80,7 +80,7 @@ struct HomeView: View { }, content: { SmartSelfieAuthWithUserIdEntry( - initialUserId: viewModel.smartSelfieEnrollmentUserId, + initialUserId: viewModel.lastSelfieEnrollmentUserId ?? "", useStrictMode: true, delegate: viewModel ) diff --git a/Example/SmileID/Home/HomeViewModel.swift b/Example/SmileID/Home/HomeViewModel.swift index 709b464c..6828faf8 100644 --- a/Example/SmileID/Home/HomeViewModel.swift +++ b/Example/SmileID/Home/HomeViewModel.swift @@ -19,7 +19,10 @@ class HomeViewModel: ObservableObject, @Published var partnerId: String var networkMonitor = NetworkMonitor.shared - @Published private(set) var smartSelfieEnrollmentUserId = generateUserId() + var lastSelfieEnrollmentUserId: String? { + guard let value = UIPasteboard.general.string else { return nil } + return value.hasPrefix("user-") ? value : nil + } @Published private(set) var newUserId: String = generateUserId() @Published private(set) var newJobId: String = generateJobId() From 62a424834fc725c45baa0563c621864a102a82c5 Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Tue, 10 Dec 2024 15:50:27 +0100 Subject: [PATCH 3/5] fix the layout issues with biometric kyc and enhanced kyc country picker. --- .../BiometricKycWithIdInputScreen.swift | 40 ++++++++++--------- .../EnhancedKycWithIdInputScreen.swift | 38 +++++++++--------- Example/SmileID/Home/ProductCell.swift | 3 ++ .../Consent/OrchestratedConsentScreen.swift | 38 +++++++++--------- .../Views/SearchableDropdownSelector.swift | 3 +- 5 files changed, 66 insertions(+), 56 deletions(-) diff --git a/Example/SmileID/BiometricKYC/BiometricKycWithIdInputScreen.swift b/Example/SmileID/BiometricKYC/BiometricKycWithIdInputScreen.swift index 2e52e489..ecdf4ab2 100644 --- a/Example/SmileID/BiometricKYC/BiometricKycWithIdInputScreen.swift +++ b/Example/SmileID/BiometricKYC/BiometricKycWithIdInputScreen.swift @@ -6,7 +6,7 @@ struct BiometricKycWithIdInputScreen: View { let delegate: BiometricKycResultDelegate @State private var selectedCountry: CountryInfo? - @ObservedObject var viewModel: BiometricKycWithIdInputScreenViewModel + @StateObject var viewModel: BiometricKycWithIdInputScreenViewModel var body: some View { switch viewModel.step { @@ -19,25 +19,27 @@ struct BiometricKycWithIdInputScreen: View { } .frame(maxWidth: .infinity) case .idTypeSelection(let countryList): - SearchableDropdownSelector( - items: countryList, - selectedItem: selectedCountry, - itemDisplayName: { $0.name }, - onItemSelected: { selectedCountry = $0 } - ) - if let selectedCountry = selectedCountry { - RadioGroupSelector( - title: "Select ID Type", - items: selectedCountry.availableIdTypes, - itemDisplayName: { $0.label }, - onItemSelected: { idType in - viewModel.onIdTypeSelected( - country: selectedCountry.countryCode, - idType: idType.idTypeKey, - requiredFields: idType.requiredFields ?? [] - ) - } + VStack { + SearchableDropdownSelector( + items: countryList, + selectedItem: selectedCountry, + itemDisplayName: { $0.name }, + onItemSelected: { selectedCountry = $0 } ) + if let selectedCountry = selectedCountry { + RadioGroupSelector( + title: "Select ID Type", + items: selectedCountry.availableIdTypes, + itemDisplayName: { $0.label }, + onItemSelected: { idType in + viewModel.onIdTypeSelected( + country: selectedCountry.countryCode, + idType: idType.idTypeKey, + requiredFields: idType.requiredFields ?? [] + ) + } + ) + } } case .consent(let country, let idType, let requiredFields): SmileID.consentScreen( diff --git a/Example/SmileID/EnhancedKYC/EnhancedKycWithIdInputScreen.swift b/Example/SmileID/EnhancedKYC/EnhancedKycWithIdInputScreen.swift index b72daadf..c0f1ff2d 100644 --- a/Example/SmileID/EnhancedKYC/EnhancedKycWithIdInputScreen.swift +++ b/Example/SmileID/EnhancedKYC/EnhancedKycWithIdInputScreen.swift @@ -18,25 +18,27 @@ struct EnhancedKycWithIdInputScreen: View { } .frame(maxWidth: .infinity) case .idTypeSelection(let countryList): - SearchableDropdownSelector( - items: countryList, - selectedItem: selectedCountry, - itemDisplayName: { $0.name }, - onItemSelected: { selectedCountry = $0 } - ) - if let selectedCountry = selectedCountry { - RadioGroupSelector( - title: "Select ID Type", - items: selectedCountry.availableIdTypes, - itemDisplayName: { $0.label }, - onItemSelected: { idType in - viewModel.onIdTypeSelected( - country: selectedCountry.countryCode, - idType: idType.idTypeKey, - requiredFields: idType.requiredFields ?? [] - ) - } + VStack { + SearchableDropdownSelector( + items: countryList, + selectedItem: selectedCountry, + itemDisplayName: { $0.name }, + onItemSelected: { selectedCountry = $0 } ) + if let selectedCountry = selectedCountry { + RadioGroupSelector( + title: "Select ID Type", + items: selectedCountry.availableIdTypes, + itemDisplayName: { $0.label }, + onItemSelected: { idType in + viewModel.onIdTypeSelected( + country: selectedCountry.countryCode, + idType: idType.idTypeKey, + requiredFields: idType.requiredFields ?? [] + ) + } + ) + } } case .consent(let country, let idType, let requiredFields): SmileID.consentScreen( diff --git a/Example/SmileID/Home/ProductCell.swift b/Example/SmileID/Home/ProductCell.swift index d8283b8f..3f7053e0 100644 --- a/Example/SmileID/Home/ProductCell.swift +++ b/Example/SmileID/Home/ProductCell.swift @@ -41,6 +41,9 @@ struct ProductCell: View { .frame(maxWidth: .infinity) .background(SmileID.theme.accent) .cornerRadius(8) +// .sheet(isPresented: $isPresented) { +// content() +// } .fullScreenCover( isPresented: $isPresented, content: { diff --git a/Sources/SmileID/Classes/Consent/OrchestratedConsentScreen.swift b/Sources/SmileID/Classes/Consent/OrchestratedConsentScreen.swift index 9224c27d..430276f7 100644 --- a/Sources/SmileID/Classes/Consent/OrchestratedConsentScreen.swift +++ b/Sources/SmileID/Classes/Consent/OrchestratedConsentScreen.swift @@ -74,7 +74,7 @@ public struct ConsentScreen: View { .padding(16) VStack(spacing: 16) { - ForEach(0..: View { Spacer() Text(itemDisplayName(selectedItem)) .foregroundColor(SmileID.theme.accent) - .onTapGesture { onItemSelected(nil) } Spacer() Image(systemName: "arrowtriangle.down.circle.fill") .foregroundColor(SmileID.theme.accent) } + .contentShape(.rect) + .onTapGesture { onItemSelected(nil) } } else { ZStack(alignment: .leading) { Image(systemName: "magnifyingglass") From 99bfb71074f3c3f7e3d17c4208ed2e027a44349b Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Tue, 10 Dec 2024 16:58:12 +0100 Subject: [PATCH 4/5] bump version number. remove comments. --- Example/SmileID.xcodeproj/project.pbxproj | 12 ++++++------ Example/SmileID/Home/ProductCell.swift | 3 --- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Example/SmileID.xcodeproj/project.pbxproj b/Example/SmileID.xcodeproj/project.pbxproj index 01757d56..8f4c6dec 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 /* BuildFile in Sources */ = {isa = PBXBuildFile; }; + 20F3D6F32C25F4D700B32751 /* (null) 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,7 +48,7 @@ 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 /* BuildFile in Resources */ = {isa = PBXBuildFile; }; + 620F1E9A2B691ABB00185CD2 /* (null) 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 */; }; @@ -549,7 +549,7 @@ buildActionMask = 2147483647; files = ( 1EFAB3172A375265008E3C13 /* Images.xcassets in Resources */, - 620F1E9A2B691ABB00185CD2 /* BuildFile in Resources */, + 620F1E9A2B691ABB00185CD2 /* (null) in Resources */, 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, 5829A8C02BC7429A001C1E7E /* PrivacyInfo.xcprivacy in Resources */, 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, @@ -698,7 +698,7 @@ 1ED53F6D2A2F28590020BEFB /* SmileTextField.swift in Sources */, 91CB21A52AC10C61005AEBF5 /* NavigationBar.swift in Sources */, 1ED53F6B2A2F28590020BEFB /* ProductCell.swift in Sources */, - 20F3D6F32C25F4D700B32751 /* BuildFile in Sources */, + 20F3D6F32C25F4D700B32751 /* (null) in Sources */, 1E60ED382A29C306002695FF /* Constants.swift in Sources */, 624777D02B0CDC9F00952842 /* EnhancedKycWithIdInputScreen.swift in Sources */, 1ED53F712A2F28590020BEFB /* EnterUserIDView.swift in Sources */, @@ -891,7 +891,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 39; + CURRENT_PROJECT_VERSION = 40; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 99P7YGX9Q6; @@ -924,7 +924,7 @@ CODE_SIGN_IDENTITY = "Apple Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 39; + CURRENT_PROJECT_VERSION = 40; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 99P7YGX9Q6; diff --git a/Example/SmileID/Home/ProductCell.swift b/Example/SmileID/Home/ProductCell.swift index 3f7053e0..d8283b8f 100644 --- a/Example/SmileID/Home/ProductCell.swift +++ b/Example/SmileID/Home/ProductCell.swift @@ -41,9 +41,6 @@ struct ProductCell: View { .frame(maxWidth: .infinity) .background(SmileID.theme.accent) .cornerRadius(8) -// .sheet(isPresented: $isPresented) { -// content() -// } .fullScreenCover( isPresented: $isPresented, content: { From 2adfb9c9ff2fb8b35f6579e9ac24d93e2eaa88f5 Mon Sep 17 00:00:00 2001 From: Tobi Omotayo Date: Tue, 10 Dec 2024 16:58:59 +0100 Subject: [PATCH 5/5] add encryption compliance key and value for non exempt encryption --- Example/SmileID/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Example/SmileID/Info.plist b/Example/SmileID/Info.plist index 09bc02c4..b42aec4c 100644 --- a/Example/SmileID/Info.plist +++ b/Example/SmileID/Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + CFBundleDevelopmentRegion en CFBundleExecutable