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

feat: add back camera support #180

Draft
wants to merge 16 commits into
base: feat/no-light-support
Choose a base branch
from
Draft
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
10 changes: 10 additions & 0 deletions HostApp/HostApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
9070FFBD285112B5009867D5 /* HostAppUITests */,
9070FFA1285112B4009867D5 /* Products */,
90215EED291E9FB60050F2AD /* Frameworks */,
A5A9AF5054D0FF13505B212A /* AmplifyConfig */,
);
sourceTree = "<group>";
};
Expand Down Expand Up @@ -213,6 +214,15 @@
path = Model;
sourceTree = "<group>";
};
A5A9AF5054D0FF13505B212A /* AmplifyConfig */ = {
isa = PBXGroup;
children = (
973619242BA378690003A590 /* awsconfiguration.json */,
973619232BA378690003A590 /* amplifyconfiguration.json */,
);
name = AmplifyConfig;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/aws-amplify/amplify-swift",
"state" : {
"branch" : "feat/no-light-support",
"revision" : "22e02fa21399122aac1d8b4f6ab23c242c79dae6"
"branch" : "test/no-light-support",
"revision" : "cdee9437c8bae4be8198a9860d09cd79fdb044ba"
}
},
{
Expand Down Expand Up @@ -50,8 +50,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/stephencelis/SQLite.swift.git",
"state" : {
"revision" : "5f5ad81ac0d0a0f3e56e39e646e8423c617df523",
"version" : "0.13.2"
"revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8",
"version" : "0.15.3"
}
},
{
Expand Down
38 changes: 24 additions & 14 deletions HostApp/HostApp/Views/ExampleLivenessView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,28 @@ import SwiftUI
import FaceLiveness

struct ExampleLivenessView: View {
@Binding var isPresented: Bool
@Binding var containerViewState: ContainerViewState
@ObservedObject var viewModel: ExampleLivenessViewModel

init(sessionID: String, isPresented: Binding<Bool>) {
self.viewModel = .init(sessionID: sessionID)
self._isPresented = isPresented
init(sessionID: String, containerViewState: Binding<ContainerViewState>) {
self._containerViewState = containerViewState
if case let .liveness(selectedCamera) = _containerViewState.wrappedValue {
self.viewModel = .init(sessionID: sessionID, presentationState: .liveness(selectedCamera))
} else {
self.viewModel = .init(sessionID: sessionID)
}
}

var body: some View {
switch viewModel.presentationState {
case .liveness:
case .liveness(let camera):
FaceLivenessDetectorView(
sessionID: viewModel.sessionID,
region: "us-east-1",
challengeOptions: .init(faceMovementChallengeOption: FaceMovementChallengeOption(camera: camera),
faceMovementAndLightChallengeOption: FaceMovementAndLightChallengeOption()),
isPresented: Binding(
get: { viewModel.presentationState == .liveness },
get: { viewModel.presentationState == .liveness(camera) },
set: { _ in }
),
onCompletion: { result in
Expand All @@ -33,11 +39,11 @@ struct ExampleLivenessView: View {
case .success:
withAnimation { viewModel.presentationState = .result }
case .failure(.sessionNotFound), .failure(.cameraPermissionDenied), .failure(.accessDenied):
viewModel.presentationState = .liveness
isPresented = false
viewModel.presentationState = .liveness(camera)
containerViewState = .startSession
case .failure(.userCancelled):
viewModel.presentationState = .liveness
isPresented = false
viewModel.presentationState = .liveness(camera)
containerViewState = .startSession
case .failure(.sessionTimedOut):
viewModel.presentationState = .error(.sessionTimedOut)
case .failure(.socketClosed):
Expand All @@ -46,19 +52,23 @@ struct ExampleLivenessView: View {
viewModel.presentationState = .error(.countdownFaceTooClose)
case .failure(.invalidSignature):
viewModel.presentationState = .error(.invalidSignature)
case .failure(.faceInOvalMatchExceededTimeLimitError):
viewModel.presentationState = .error(.faceInOvalMatchExceededTimeLimitError)
case .failure(.internalServer):
viewModel.presentationState = .error(.internalServer)
case .failure(.cameraNotAvailable):
viewModel.presentationState = .error(.cameraNotAvailable)
default:
viewModel.presentationState = .liveness
viewModel.presentationState = .liveness(camera)
}
}
}
)
.id(isPresented)
.id(containerViewState)
case .result:
LivenessResultView(
sessionID: viewModel.sessionID,
onTryAgain: { isPresented = false },
onTryAgain: { containerViewState = .startSession },
content: {
LivenessResultContentView(fetchResults: viewModel.fetchLivenessResult)
}
Expand All @@ -67,7 +77,7 @@ struct ExampleLivenessView: View {
case .error(let detectionError):
LivenessResultView(
sessionID: viewModel.sessionID,
onTryAgain: { isPresented = false },
onTryAgain: { containerViewState = .startSession },
content: {
switch detectionError {
case .socketClosed:
Expand Down
7 changes: 4 additions & 3 deletions HostApp/HostApp/Views/ExampleLivenessViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import FaceLiveness
import Amplify

class ExampleLivenessViewModel: ObservableObject {
@Published var presentationState = PresentationState.liveness
@Published var presentationState: PresentationState = .liveness(.front)
let sessionID: String

init(sessionID: String) {
init(sessionID: String, presentationState: PresentationState = .liveness(.front)) {
self.sessionID = sessionID
self.presentationState = presentationState
}

func fetchLivenessResult() async throws -> LivenessResultContentView.Result {
Expand All @@ -30,6 +31,6 @@ class ExampleLivenessViewModel: ObservableObject {
}

enum PresentationState: Equatable {
case liveness, result, error(FaceLivenessDetectionError)
case liveness(LivenessCamera), result, error(FaceLivenessDetectionError)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ extension LivenessCheckErrorContentView {
name: "The camera could not be started.",
description: "There might be a hardware issue with the camera."
)

}

struct LivenessCheckErrorContentView_Previews: PreviewProvider {
Expand Down
17 changes: 12 additions & 5 deletions HostApp/HostApp/Views/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,32 @@
//

import SwiftUI
import FaceLiveness

struct RootView: View {
@EnvironmentObject var sceneDelegate: SceneDelegate
@State var sessionID = ""
@State var isPresentingContainerView = false
@State var containerViewState = ContainerViewState.startSession

var body: some View {
if isPresentingContainerView {
switch containerViewState {
case .liveness:
ExampleLivenessView(
sessionID: sessionID,
isPresented: $isPresentingContainerView
containerViewState: $containerViewState
)
} else {
case .startSession:
StartSessionView(
sessionID: $sessionID,
isPresentingContainerView: $isPresentingContainerView
containerViewState: $containerViewState
)
.background(Color.dynamicColors(light: .white, dark: .secondarySystemBackground))
.edgesIgnoringSafeArea(.all)
}
}
}

enum ContainerViewState: Hashable {
case liveness(LivenessCamera)
case startSession
}
39 changes: 35 additions & 4 deletions HostApp/HostApp/Views/StartSessionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ struct StartSessionView: View {
@EnvironmentObject var sceneDelegate: SceneDelegate
@ObservedObject var viewModel = StartSessionViewModel()
@Binding var sessionID: String
@Binding var isPresentingContainerView: Bool
@Binding var containerViewState: ContainerViewState
@State private var showAlert = false

var body: some View {
Expand All @@ -26,7 +26,7 @@ struct StartSessionView: View {
)

button(
text: "Create Liveness Session",
text: "Create Liveness Session (front camera)",
backgroundColor: .dynamicColors(
light: .hex("#047D95"),
dark: .hex("#7dd6e8")
Expand All @@ -35,7 +35,7 @@ struct StartSessionView: View {
viewModel.createSession { sessionId, err in
if let sessionId = sessionId {
sessionID = sessionId
isPresentingContainerView = true
containerViewState = .liveness(.front)
}

showAlert = err != nil
Expand All @@ -50,7 +50,38 @@ struct StartSessionView: View {
dismissButton: .default(
Text("OK"),
action: {
isPresentingContainerView = false
containerViewState = .startSession
}
)
)
}

button(
text: "Create Liveness Session (back camera)",
backgroundColor: .dynamicColors(
light: .hex("#047D95"),
dark: .hex("#7dd6e8")
),
action: {
viewModel.createSession { sessionId, err in
if let sessionId = sessionId {
sessionID = sessionId
containerViewState = .liveness(.back)
}

showAlert = err != nil
}
},
enabled: viewModel.isSignedIn
)
.alert(isPresented: $showAlert) {
Alert(
title: Text("Error Creating Liveness Session"),
message: Text("Unable to create a liveness session id. Please try again."),
dismissButton: .default(
Text("OK"),
action: {
containerViewState = .startSession
}
)
)
Expand Down
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/aws-amplify/amplify-swift",
"state" : {
"branch" : "feat/no-light-support",
"revision" : "614be628cb01188e519bb0e9e4d90bd83703d139"
"branch" : "test/no-light-support",
"revision" : "cdee9437c8bae4be8198a9860d09cd79fdb044ba"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let package = Package(
],
dependencies: [
// TODO: Change this before merge to main
.package(url: "https://github.com/aws-amplify/amplify-swift", branch: "feat/no-light-support")
.package(url: "https://github.com/aws-amplify/amplify-swift", branch: "test/no-light-support")
],
targets: [
.target(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct CameraPreviewView: View {

@StateObject var model: CameraPreviewViewModel

init(model: CameraPreviewViewModel = CameraPreviewViewModel()) {
init(model: CameraPreviewViewModel = CameraPreviewViewModel(cameraPosition: .front)) {
self._model = StateObject(wrappedValue: model)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ class CameraPreviewViewModel: NSObject, ObservableObject {
@Published var buffer: CVPixelBuffer?

var previewCaptureSession: LivenessCaptureSession?
let cameraPosition: LivenessCamera

override init() {
init(cameraPosition: LivenessCamera) {
self.cameraPosition = cameraPosition

super.init()
setupSubscriptions()

let avCaptureDevice = AVCaptureDevice.DiscoverySession(
deviceTypes: [.builtInWideAngleCamera],
mediaType: .video,
position: .front
position: cameraPosition == .front ? .front : .back
).devices.first

let outputDelegate = CameraPreviewOutputSampleBufferDelegate { [weak self] buffer in
Expand Down
10 changes: 7 additions & 3 deletions Sources/FaceLiveness/Views/GetReadyPage/GetReadyPageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@ struct GetReadyPageView: View {
let beginCheckButtonDisabled: Bool
let onBegin: () -> Void
let challenge: Challenge
let cameraPosition: LivenessCamera

init(
onBegin: @escaping () -> Void,
beginCheckButtonDisabled: Bool = false,
challenge: Challenge
challenge: Challenge,
cameraPosition: LivenessCamera
) {
self.onBegin = onBegin
self.beginCheckButtonDisabled = beginCheckButtonDisabled
self.challenge = challenge
self.cameraPosition = cameraPosition
}

var body: some View {
VStack {
ZStack {
CameraPreviewView()
CameraPreviewView(model: CameraPreviewViewModel(cameraPosition: cameraPosition))
VStack {
WarningBox(
titleText: LocalizedStrings.get_ready_photosensitivity_title,
Expand Down Expand Up @@ -79,6 +82,7 @@ struct GetReadyPageView_Previews: PreviewProvider {
static var previews: some View {
GetReadyPageView(onBegin: {},
challenge: .init(version: "2.0.0",
type: .faceMovementAndLightChallenge))
type: .faceMovementAndLightChallenge),
cameraPosition: .front)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ struct InstructionContainerView: View {
)
}
case .faceMatched:
if let challenge = viewModel.challenge,
if let challenge = viewModel.challengeReceived,
case .faceMovementAndLightChallenge = challenge.type {
InstructionView(
text: LocalizedStrings.challenge_instruction_hold_still,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public struct FaceLivenessDetectionError: Error, Equatable {
message: "The signature on the request is invalid.",
recoverySuggestion: "Ensure the device time is correct and try again."
)

public static let cameraNotAvailable = FaceLivenessDetectionError(
code: 18,
message: "The camera is not available.",
Expand Down
Loading
Loading