Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Selfie and Liveness Image Sizes, Fix Layout for Small Screen Devices #266

Merged
merged 15 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
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
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
Loading