Skip to content

Commit

Permalink
Fix Selfie and Liveness Image Sizes, Fix Layout for Small Screen Devi…
Browse files Browse the repository at this point in the history
…ces (#266)

* Fixed Missing Document Type (#264)

* added missing document type in document jobs

* Update CHANGELOG.md

* add new submission status icons for success and failed states.

* remove setting initial value for user instruction.

* handle multiple faces detected and throw an error

* change order of face validator so that luminance is checked before head position.

* reduce the stroke size of the face in bounds indicator.

* remove done button and hide cancel button if submission was successful.

* use new status icons in status view.

* add a placeholder for api error 2214

* update selfie actions view parameters

* restore camera manager to have accessible initializer.
add camera session size preset for selfie capture image

* code formatting and set line limit of 2 to user instructions title.

* change capture screen layout to accommodate small screen devices.
remove cancel delegate method from selfie result.

* dismiss the selfie capture when submission is successful.

---------

Co-authored-by: Juma Allan <[email protected]>
  • Loading branch information
tobitech and jumaallan authored Dec 13, 2024
1 parent 8b7279e commit 819d2ff
Show file tree
Hide file tree
Showing 27 changed files with 149 additions and 73 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# Release Notes

## Unreleased

* Fixed missing idType on Document Verification Jobs

## 10.2.17
### Added skipApiSubmission: Whether to skip api submission to SmileID and return only captured images on SmartSelfie enrollment, SmartSelfie authentic , Document verification and Enhanced DocV

* Added skipApiSubmission: Whether to skip api submission to SmileID and return only captured images on SmartSelfie enrollment, SmartSelfie authentic , Document verification and Enhanced DocV

## 10.2.16

### Fixed
* Clear images on retry or start capture with the same jobId

Expand Down
1 change: 0 additions & 1 deletion Example/SmileID/Home/ProductCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ struct ProductCell<Content: View>: View {
}
}
}
.environment(\.modalMode, $isPresented)
}
)
}
Expand Down
4 changes: 1 addition & 3 deletions Sources/SmileID/Classes/Camera/CameraManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ class CameraManager: NSObject, ObservableObject {
@Published private(set) var status = Status.unconfigured
private var orientation: Orientation

static let shared: CameraManager = CameraManager(orientation: .portrait)

private init(orientation: Orientation) {
init(orientation: Orientation) {
self.orientation = orientation
super.init()
sessionQueue.async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class DocumentCaptureViewModel: ObservableObject {
@Published var documentImageToConfirm: Data?
@Published var captureError: Error?
@Published var isCapturing = false
var cameraManager = CameraManager.shared
@Published var cameraManager = CameraManager(orientation: .portrait)

init(
knownAspectRatio: Double? = nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ class IOrchestratedDocumentVerificationViewModel<T, U: JobResult>: ObservableObj
}
let info = try LocalStorage.createInfoJsonFile(
jobId: jobId,
idInfo: IdInfo(country: countryCode),
idInfo: IdInfo(country: countryCode, idType: documentType),
documentFront: frontDocumentUrl,
documentBack: backDocumentUrl,
selfie: selfieFile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ enum FaceDetectorError: Error {
case unableToLoadSelfieModel
case invalidSelfieModelOutput
case noFaceDetected
case multipleFacesDetected
case unableToCropImage
}

Expand Down Expand Up @@ -71,6 +72,11 @@ class EnhancedFaceDetector: NSObject {
return
}

guard faceDetections.count == 1 else {
self.resultDelegate?.faceDetector(self, didFailWithError: FaceDetectorError.multipleFacesDetected)
return
}

let convertedBoundingBox =
self.viewDelegate?.convertFromMetadataToPreviewRect(
rect: faceObservation.boundingBox) ?? .zero
Expand Down
4 changes: 2 additions & 2 deletions Sources/SmileID/Classes/FaceDetector/FaceValidator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ final class FaceValidator {
}
}
return nil
} else if !isAcceptableFaceQuality || !isAcceptableBrightness {
return .goodLight
} else if faceBoundsState == .detectedFaceOffCentre
|| faceBoundsState == .detectedFaceNotWithinFrame {
return .headInFrame
} else if faceBoundsState == .detectedFaceTooSmall {
return .moveCloser
} else if faceBoundsState == .detectedFaceTooLarge {
return .moveBack
} else if !isAcceptableFaceQuality || !isAcceptableBrightness {
return .goodLight
}
return nil
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/SmileID/Classes/Helpers/SmileIDResourcesHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ public class SmileIDResourcesHelper {
public static var ConsentDocumentInfo = SmileIDResourcesHelper.image("ConsentDocumentInfo")!
public static var ConsentPersonalInfo = SmileIDResourcesHelper.image("ConsentPersonalInfo")!
public static var Loader = SmileIDResourcesHelper.image("Loader")!
public static var Checkmark = SmileIDResourcesHelper.image("Checkmark")!
public static var Xmark = SmileIDResourcesHelper.image("Xmark")!

/// Size of font.
public static let pointSize: CGFloat = 16
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import SwiftUI
public class EnhancedSmartSelfieViewModel: ObservableObject {
// MARK: Dependencies
private let motionManager = CMMotionManager()
let cameraManager = CameraManager.shared
let cameraManager = CameraManager(orientation: .portrait)
let faceDetector = EnhancedFaceDetector()
private let faceValidator = FaceValidator()
var livenessCheckManager = LivenessCheckManager()
Expand Down Expand Up @@ -121,7 +121,6 @@ public class EnhancedSmartSelfieViewModel: ObservableObject {
self.livenessCheckManager.delegate = self

self.faceValidator.setLayoutGuideFrame(with: faceLayoutGuideFrame)
self.userInstruction = .headInFrame

livenessCheckManager.$lookLeftProgress
.merge(
Expand All @@ -136,6 +135,9 @@ public class EnhancedSmartSelfieViewModel: ObservableObject {
}
.store(in: &subscribers)

if cameraManager.session.canSetSessionPreset(.vga640x480) {
cameraManager.session.sessionPreset = .vga640x480
}
cameraManager.$status
.receive(on: DispatchQueue.main)
.filter { $0 == .unauthorized }
Expand Down Expand Up @@ -200,8 +202,8 @@ public class EnhancedSmartSelfieViewModel: ObservableObject {
handleWindowSizeChanged(to: windowRect, edgeInsets: safeAreaInsets)
case .onViewAppear:
handleViewAppeared()
case .jobProcessingDone:
onFinished(callback: onResult)
case .cancelSelfieCapture:
handleCancelSelfieCapture()
case .retryJobSubmission:
handleSubmission()
case .openApplicationSettings:
Expand Down Expand Up @@ -353,6 +355,21 @@ extension EnhancedSmartSelfieViewModel {
guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(settingsURL)
}

private func handleCancelSelfieCapture() {
invalidateSubmissionTask()
UIApplication.shared.windows.first?.rootViewController?.dismiss(animated: true)
}

private func dismissSelfieCapture() {
UIApplication.shared.windows.first?.rootViewController?.dismiss(
animated: true,
completion: { [weak self] in
guard let self else { return }
self.onFinished(callback: self.onResult)
}
)
}
}

// MARK: FaceDetectorResultDelegate Methods
Expand Down Expand Up @@ -488,8 +505,6 @@ extension EnhancedSmartSelfieViewModel: SelfieSubmissionDelegate {
)
} else if let error = error {
callback.didError(error: error)
} else {
callback.didCancel()
}
}

Expand All @@ -502,6 +517,10 @@ extension EnhancedSmartSelfieViewModel: SelfieSubmissionDelegate {
self.apiResponse = apiResponse
self.selfieCaptureState = .processing(.success)
}

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.dismissSelfieCapture()
}
}

func submissionDidFail(
Expand Down
5 changes: 4 additions & 1 deletion Sources/SmileID/Classes/SelfieCapture/SelfieViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class SelfieViewModel: ObservableObject, ARKitSmileDelegate {
private var localMetadata: LocalMetadata
private let faceDetector = FaceDetector()

var cameraManager = CameraManager.shared
var cameraManager = CameraManager(orientation: .portrait)
var shouldAnalyzeImages = true
var lastAutoCaptureTime = Date()
var previousHeadRoll = Double.infinity
Expand Down Expand Up @@ -78,6 +78,9 @@ public class SelfieViewModel: ObservableObject, ARKitSmileDelegate {
self.extraPartnerParams = extraPartnerParams
self.localMetadata = localMetadata

if cameraManager.session.canSetSessionPreset(.vga640x480) {
cameraManager.session.sessionPreset = .vga640x480
}
cameraManager.$status
.receive(on: DispatchQueue.main)
.filter { $0 == .unauthorized }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ enum SelfieViewModelAction {
case windowSizeDetected(CGSize, EdgeInsets)

// Job Submission Actions
case jobProcessingDone
case cancelSelfieCapture
case retryJobSubmission

// Others
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,3 @@ 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() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ public struct EnhancedSelfieCaptureScreen: View {
let showAttribution: Bool

private let faceShape = FaceShape()
@Environment(\.modalMode) private var modalMode

private(set) var originalBrightness = UIScreen.main.brightness
private let cameraContainerHeight: CGFloat = 480

public var body: some View {
GeometryReader { proxy in
Expand All @@ -20,14 +19,16 @@ public struct EnhancedSelfieCaptureScreen: View {
selfieViewModel: viewModel
)
.cornerRadius(40)
.frame(height: cameraContainerHeight)

RoundedRectangle(cornerRadius: 40)
.fill(SmileID.theme.tertiary.opacity(0.8))
.reverseMask(alignment: .top) {
faceShape
.frame(width: 250, height: 350)
.padding(.top, 60)
.padding(.top, 50)
}
.frame(height: cameraContainerHeight)
VStack {
ZStack {
FaceBoundingArea(
Expand Down Expand Up @@ -55,7 +56,7 @@ public struct EnhancedSelfieCaptureScreen: View {
}
}
}
.selfieCaptureFrameBackground()
.selfieCaptureFrameBackground(cameraContainerHeight)
if showAttribution {
Image(uiImage: SmileIDResourcesHelper.SmileEmblem)
}
Expand All @@ -69,8 +70,9 @@ public struct EnhancedSelfieCaptureScreen: View {
.reverseMask(alignment: .top) {
faceShape
.frame(width: 250, height: 350)
.padding(.top, 60)
.padding(.top, 50)
}
.frame(height: cameraContainerHeight)
VStack {
Spacer()
UserInstructionsView(
Expand All @@ -84,33 +86,21 @@ public struct EnhancedSelfieCaptureScreen: View {
SubmissionStatusView(processState: processingState)
.padding(.bottom, 40)
}
.selfieCaptureFrameBackground()
.selfieCaptureFrameBackground(cameraContainerHeight)
if showAttribution {
Image(uiImage: SmileIDResourcesHelper.SmileEmblem)
}

Spacer()
SelfieActionsView(
processingState: processingState,
retryAction: { viewModel.perform(action: .retryJobSubmission) },
doneAction: {
modalMode.wrappedValue = false
viewModel.perform(action: .jobProcessingDone)
}
)
}

Spacer()

Button {
modalMode.wrappedValue = false
viewModel.perform(action: .jobProcessingDone)
} label: {
Text(SmileIDResourcesHelper.localizedString(for: "Action.Cancel"))
.font(SmileID.theme.button)
.foregroundColor(SmileID.theme.error)
}
SelfieActionsView(
captureState: viewModel.selfieCaptureState,
retryAction: { viewModel.perform(action: .retryJobSubmission) },
cancelAction: {
viewModel.perform(action: .cancelSelfieCapture)
}
)
}
.padding(.vertical, 20)
.navigationBarHidden(true)
.onAppear {
UIScreen.main.brightness = 1
Expand Down Expand Up @@ -143,11 +133,10 @@ public struct EnhancedSelfieCaptureScreen: View {
}

extension View {
func selfieCaptureFrameBackground() -> some View {
func selfieCaptureFrameBackground(_ containerHeight: CGFloat) -> some View {
self
.shadow(color: .black.opacity(0.25), radius: 4, x: 0, y: 4)
.frame(height: 520)
.frame(height: containerHeight)
.padding(.horizontal)
.padding(.top, 40)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ struct FaceBoundingArea: View {
faceShape
.stroke(
faceInBounds ? selfieCaptured ? .clear : SmileID.theme.success : SmileID.theme.error,
style: StrokeStyle(lineWidth: 10)
style: StrokeStyle(lineWidth: 8)
)
.frame(width: 270, height: 370)
.frame(width: 260, height: 360)

if let guideAnimation = guideAnimation,
showGuideAnimation {
Expand Down
39 changes: 26 additions & 13 deletions Sources/SmileID/Classes/SelfieCapture/View/SelfieActionsView.swift
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
import SwiftUI

struct SelfieActionsView: View {
var processingState: ProcessingState
var captureState: EnhancedSmartSelfieViewModel.SelfieCaptureState
var retryAction: () -> Void
var doneAction: () -> Void
var cancelAction: () -> Void

var body: some View {
VStack {
Spacer()
switch processingState {
case .inProgress:
EmptyView()
case .success:
SmileButton(title: "Action.Done") {
doneAction()
}
case .error:
SmileButton(title: "Confirmation.Retry") {
retryAction()
switch captureState {
case .capturingSelfie:
cancelButton
case .processing(let processingState):
switch processingState {
case .inProgress:
cancelButton
case .success:
EmptyView()
case .error:
SmileButton(title: "Confirmation.Retry") {
retryAction()
}
cancelButton
}
}
}
.padding(.horizontal, 65)
}

var cancelButton: some View {
Button {
cancelAction()
} label: {
Text(SmileIDResourcesHelper.localizedString(for: "Action.Cancel"))
.font(SmileID.theme.button)
.foregroundColor(SmileID.theme.error)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ struct SelfiePreviewView: View {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 520)
.frame(height: 480)
.clipShape(.rect(cornerRadius: 40))
}
}
Loading

0 comments on commit 819d2ff

Please sign in to comment.