Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.provider.Settings
import android.util.Size
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.camera.core.CameraControl
import androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888
Expand All @@ -24,6 +25,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.simprints.core.domain.permission.PermissionStatus
import com.simprints.core.tools.extentions.hasCameraFlash
import com.simprints.core.tools.extentions.hasPermission
import com.simprints.core.tools.extentions.permissionFromResult
import com.simprints.face.capture.R
Expand Down Expand Up @@ -62,6 +64,8 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
private lateinit var screenSize: Size
private lateinit var targetResolution: Size

private var cameraControl: CameraControl? = null

private val launchPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestPermission(),
) { granted ->
Expand Down Expand Up @@ -103,6 +107,19 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
directions = LiveFeedbackFragmentDirections.actionFaceLiveFeedbackFragmentToFacePreparationFragment(),
)
}

with(binding.captureFlashButton) {
isSelected = false
setOnClickListener {
val torchEnabled = !binding.captureFlashButton.isSelected
toggleTorche(torchEnabled)
}
}
}

private fun toggleTorche(enabled: Boolean) {
cameraControl?.enableTorch(enabled)
binding.captureFlashButton.isSelected = enabled
}

/** Initialize CameraX, and prepare to bind the camera use cases */
Expand Down Expand Up @@ -132,12 +149,13 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
val preview = Preview.Builder().setTargetResolution(targetResolution).build()
val cameraProvider = ProcessCameraProvider.awaitInstance(requireContext())
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
val camera = cameraProvider.bindToLifecycle(
this@LiveFeedbackFragment,
DEFAULT_BACK_CAMERA,
preview,
imageAnalyzer,
)
cameraControl = camera.cameraControl
// Attach the view's surface provider to preview use case
preview.surfaceProvider = binding.faceCaptureCamera.surfaceProvider
Simber.i("Camera setup finished", tag = FACE_CAPTURE)
Expand All @@ -163,6 +181,7 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
}

override fun onStop() {
toggleTorche(false)
// Shut down our background executor
if (::cameraExecutor.isInitialized) {
cameraExecutor.shutdown()
Expand All @@ -171,6 +190,10 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
}

private fun bindViewModel() {
vm.displayCameraFlashControls.observe(viewLifecycleOwner) {
binding.captureFlashButton.isVisible = it && requireContext().hasCameraFlash
}

vm.currentDetection.observe(viewLifecycleOwner) {
renderCurrentDetection(it)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ internal class LiveFeedbackFragmentViewModel @Inject constructor(
var sortedQualifyingCaptures = listOf<FaceDetection>()
val currentDetection = MutableLiveData<FaceDetection>()
val capturingState = MutableLiveData(CapturingState.NOT_STARTED)

val displayCameraFlashControls = MutableLiveData(false)

private lateinit var faceDetector: FaceDetector

/**
Expand Down Expand Up @@ -97,6 +100,7 @@ internal class LiveFeedbackFragmentViewModel @Inject constructor(
val config = configManager.getProjectConfiguration()
qualityThreshold = config.face?.getSdkConfiguration(bioSdk)?.qualityThreshold ?: 0f
singleQualityFallbackCaptureRequired = config.experimental().singleQualityFallbackRequired
displayCameraFlashControls.postValue(config.experimental().displayCameraFlashToggle)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.provider.Settings
import android.util.Size
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.camera.core.CameraControl
import androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888
Expand All @@ -24,6 +25,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.simprints.core.domain.permission.PermissionStatus
import com.simprints.core.tools.extentions.hasCameraFlash
import com.simprints.core.tools.extentions.hasPermission
import com.simprints.core.tools.extentions.permissionFromResult
import com.simprints.face.capture.R
Expand Down Expand Up @@ -61,6 +63,8 @@ internal class LiveFeedbackAutoCaptureFragment : Fragment(R.layout.fragment_live
private lateinit var screenSize: Size
private lateinit var targetResolution: Size

private var cameraControl: CameraControl? = null

private val launchPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestPermission(),
) { granted ->
Expand Down Expand Up @@ -103,6 +107,19 @@ internal class LiveFeedbackAutoCaptureFragment : Fragment(R.layout.fragment_live
directions = LiveFeedbackAutoCaptureFragmentDirections.actionFaceLiveFeedbackFragmentToFacePreparationFragment(),
)
}

with(binding.captureFlashButton) {
isSelected = false
setOnClickListener {
val torchEnabled = !binding.captureFlashButton.isSelected
toggleTorche(torchEnabled)
}
}
}

private fun toggleTorche(enabled: Boolean) {
cameraControl?.enableTorch(enabled)
binding.captureFlashButton.isSelected = enabled
}

/** Initialize CameraX, and prepare to bind the camera use cases */
Expand Down Expand Up @@ -135,12 +152,13 @@ internal class LiveFeedbackAutoCaptureFragment : Fragment(R.layout.fragment_live
val preview = Preview.Builder().setTargetResolution(targetResolution).build()
val cameraProvider = ProcessCameraProvider.awaitInstance(requireContext())
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
val camera = cameraProvider.bindToLifecycle(
this@LiveFeedbackAutoCaptureFragment,
DEFAULT_BACK_CAMERA,
preview,
imageAnalyzer,
)
cameraControl = camera.cameraControl
// Attach the view's surface provider to preview use case
preview.surfaceProvider = binding.faceCaptureCamera.surfaceProvider
}
Expand All @@ -165,6 +183,7 @@ internal class LiveFeedbackAutoCaptureFragment : Fragment(R.layout.fragment_live
}

override fun onStop() {
toggleTorche(false)
// Shut down our background executor
if (::cameraExecutor.isInitialized) {
cameraExecutor.shutdown()
Expand All @@ -173,6 +192,10 @@ internal class LiveFeedbackAutoCaptureFragment : Fragment(R.layout.fragment_live
}

private fun bindViewModel() {
vm.displayCameraFlashControls.observe(viewLifecycleOwner) {
binding.captureFlashButton.isVisible = it && requireContext().hasCameraFlash
}

vm.currentDetection.observe(viewLifecycleOwner) {
renderCurrentDetection(it)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ internal class LiveFeedbackAutoCaptureFragmentViewModel @Inject constructor(
var sortedQualifyingCaptures = listOf<FaceDetection>()
val currentDetection = MutableLiveData<FaceDetection>()
val capturingState = MutableLiveData(CapturingState.NOT_STARTED)

val displayCameraFlashControls = MutableLiveData(false)

private var isAutoCaptureHeldOff = true
private var autoCaptureImagingTimeoutJob: Job? = null
private var autoCaptureImagingDurationMillis: Long = FACE_AUTO_CAPTURE_IMAGING_DURATION_MILLIS_DEFAULT
Expand Down Expand Up @@ -124,6 +127,7 @@ internal class LiveFeedbackAutoCaptureFragmentViewModel @Inject constructor(
qualityThreshold = config.face?.getSdkConfiguration(bioSdk)?.qualityThreshold ?: 0f
singleQualityFallbackCaptureRequired = config.experimental().singleQualityFallbackRequired
autoCaptureImagingDurationMillis = config.experimental().faceAutoCaptureImagingDurationMillis
displayCameraFlashControls.postValue(config.experimental().displayCameraFlashToggle)
}
}

Expand Down
84 changes: 46 additions & 38 deletions face/capture/src/main/res/layout-land/fragment_live_feedback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@
android:layout_height="36dp"
android:layout_margin="@dimen/margin_large"
android:background="@drawable/feedback_instructions_outline"
android:gravity="center"
android:includeFontPadding="false"
android:paddingHorizontal="@dimen/padding_default"
android:paddingVertical="0dp"
android:includeFontPadding="false"
android:gravity="center"
android:text="@string/face_capture_instructions_title"
android:textColor="@color/feedback_instructions_text"
app:backgroundTint="@null"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.camera.view.PreviewView
android:id="@+id/face_capture_camera"
Expand All @@ -47,49 +47,57 @@
app:dcp_max="10"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintHeight_percent="@dimen/capture_target_size_percent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="@dimen/capture_target_size_percent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin_default"
android:layout_marginHorizontal="@dimen/margin_default"
android:gravity="center|bottom"
android:orientation="vertical"
<com.google.android.material.button.MaterialButton
android:id="@+id/capture_feedback_btn"
style="@style/Text.Headline6.Bold"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_large"
android:layout_marginVertical="@dimen/margin_default"
android:background="@drawable/feedback_chip_white"
android:checkable="true"
android:gravity="center"
android:padding="@dimen/padding_default"
android:text="@string/face_capture_title_previewing"
android:textAlignment="center"
android:textColor="@color/feedback_chip_text"
app:backgroundTint="@null"
app:iconGravity="textStart"
app:iconTint="@null"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/capture_progress">
app:layout_constraintStart_toStartOf="parent" />

<TextView
android:id="@+id/capture_feedback_txt_explanation"
style="@style/Text.Headline5.White"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_default"
android:gravity="center"
tools:text="Move closer" />
<TextView
android:id="@+id/capture_feedback_txt_explanation"
style="@style/Text.Headline5.White"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_default"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@id/capture_feedback_btn"
app:layout_constraintEnd_toEndOf="@id/capture_feedback_btn"
app:layout_constraintStart_toStartOf="@id/capture_feedback_btn"
tools:text="Move closer" />

<com.google.android.material.button.MaterialButton
android:id="@+id/capture_feedback_btn"
style="@style/Text.Headline6.Bold"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_default"
android:background="@drawable/feedback_chip_white"
android:checkable="true"
android:gravity="center"
android:padding="@dimen/padding_default"
android:text="@string/face_capture_title_previewing"
android:textAlignment="center"
android:textColor="@color/feedback_chip_text"
app:backgroundTint="@null"
app:iconGravity="textStart"
app:iconTint="@null" />
</LinearLayout>
<ImageButton
android:id="@+id/capture_flash_button"
style="@style/Widget.Simprints.Button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginEnd="8dp"
android:elevation="8dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_flash"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/capture_feedback_btn"
app:layout_constraintEnd_toEndOf="parent" />

<com.google.android.material.button.MaterialButton
android:id="@+id/capture_feedback_permission_button"
Expand Down
Loading