diff --git a/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt b/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt
index 178a7b573f..610574bd57 100644
--- a/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt
+++ b/face/capture/src/main/java/com/simprints/face/capture/screens/FaceCaptureViewModel.kt
@@ -13,6 +13,7 @@ import com.simprints.core.tools.time.Timestamp
import com.simprints.face.capture.FaceCaptureResult
import com.simprints.face.capture.models.FaceDetection
import com.simprints.face.capture.usecases.BitmapToByteArrayUseCase
+import com.simprints.face.capture.usecases.ShouldShowInstructionsScreenUseCase
import com.simprints.face.capture.usecases.IsUsingAutoCaptureUseCase
import com.simprints.face.capture.usecases.SaveFaceImageUseCase
import com.simprints.face.capture.usecases.SimpleCaptureEventReporter
@@ -51,6 +52,7 @@ internal class FaceCaptureViewModel @Inject constructor(
private val resolveFaceBioSdk: ResolveFaceBioSdkUseCase,
private val saveLicenseCheckEvent: SaveLicenseCheckEventUseCase,
private val isUsingAutoCapture: IsUsingAutoCaptureUseCase,
+ private val shouldShowInstructions: ShouldShowInstructionsScreenUseCase,
@DeviceID private val deviceID: String,
) : ViewModel() {
// Updated in live feedback screen
@@ -134,6 +136,9 @@ internal class FaceCaptureViewModel @Inject constructor(
_isAutoCaptureEnabled.postValue(isUsingAutoCapture())
}
+ fun shouldShowInstructionsScreen(): Boolean =
+ shouldShowInstructions()
+
private suspend fun initialize(
activity: Activity,
license: License,
diff --git a/face/capture/src/main/java/com/simprints/face/capture/screens/controller/FaceCaptureControllerFragment.kt b/face/capture/src/main/java/com/simprints/face/capture/screens/controller/FaceCaptureControllerFragment.kt
index fffe77c9c1..011f403c81 100644
--- a/face/capture/src/main/java/com/simprints/face/capture/screens/controller/FaceCaptureControllerFragment.kt
+++ b/face/capture/src/main/java/com/simprints/face/capture/screens/controller/FaceCaptureControllerFragment.kt
@@ -119,13 +119,23 @@ internal class FaceCaptureControllerFragment : Fragment(R.layout.fragment_face_c
viewModel.setupAutoCapture()
viewModel.isAutoCaptureEnabled.observe(viewLifecycleOwner) { isAutoCaptureEnabled ->
- internalNavController?.setGraph(
+ val graph = internalNavController?.navInflater?.inflate(
if (isAutoCaptureEnabled) {
R.navigation.graph_face_capture_auto_internal
} else {
R.navigation.graph_face_capture_internal
},
)
+ graph?.setStartDestination(
+ if (viewModel.shouldShowInstructionsScreen()) {
+ R.id.facePreparationFragment
+ } else {
+ R.id.faceLiveFeedbackFragment
+ }
+ )
+ graph?.let {
+ internalNavController?.setGraph(graph, null)
+ }
}
}
diff --git a/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/LiveFeedbackFragment.kt b/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/LiveFeedbackFragment.kt
index 55691b6269..3b75d0684f 100644
--- a/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/LiveFeedbackFragment.kt
+++ b/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/LiveFeedbackFragment.kt
@@ -96,6 +96,13 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback)
vm.initCapture(mainVm.samplesToCapture, mainVm.attemptNumber)
}
}
+
+ binding.captureInstructionsBtn.setOnClickListener {
+ findNavController().navigateSafely(
+ currentFragment = this,
+ directions = LiveFeedbackFragmentDirections.actionFaceLiveFeedbackFragmentToFacePreparationFragment(),
+ )
+ }
}
/** Initialize CameraX, and prepare to bind the camera use cases */
diff --git a/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedbackautocapture/LiveFeedbackAutoCaptureFragment.kt b/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedbackautocapture/LiveFeedbackAutoCaptureFragment.kt
index 4cefc2a5eb..4ed667846a 100644
--- a/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedbackautocapture/LiveFeedbackAutoCaptureFragment.kt
+++ b/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedbackautocapture/LiveFeedbackAutoCaptureFragment.kt
@@ -27,7 +27,7 @@ import com.simprints.core.domain.permission.PermissionStatus
import com.simprints.core.tools.extentions.hasPermission
import com.simprints.core.tools.extentions.permissionFromResult
import com.simprints.face.capture.R
-import com.simprints.face.capture.databinding.FragmentLiveFeedbackBinding
+import com.simprints.face.capture.databinding.FragmentLiveFeedbackAutoCaptureBinding
import com.simprints.face.capture.models.FaceDetection
import com.simprints.face.capture.screens.FaceCaptureViewModel
import com.simprints.face.capture.screens.livefeedback.CropToTargetOverlayAnalyzer
@@ -56,7 +56,7 @@ internal class LiveFeedbackAutoCaptureFragment : Fragment(R.layout.fragment_live
private val mainVm: FaceCaptureViewModel by activityViewModels()
private val vm: LiveFeedbackAutoCaptureFragmentViewModel by viewModels()
- private val binding by viewBinding(FragmentLiveFeedbackBinding::bind)
+ private val binding by viewBinding(FragmentLiveFeedbackAutoCaptureBinding::bind)
private lateinit var screenSize: Size
private lateinit var targetResolution: Size
@@ -96,6 +96,13 @@ internal class LiveFeedbackAutoCaptureFragment : Fragment(R.layout.fragment_live
vm.initCapture(mainVm.samplesToCapture, mainVm.attemptNumber)
}
}
+
+ binding.captureInstructionsBtn.setOnClickListener {
+ findNavController().navigateSafely(
+ currentFragment = this,
+ directions = LiveFeedbackAutoCaptureFragmentDirections.actionFaceLiveFeedbackFragmentToFacePreparationFragment(),
+ )
+ }
}
/** Initialize CameraX, and prepare to bind the camera use cases */
diff --git a/face/capture/src/main/java/com/simprints/face/capture/usecases/ShouldShowInstructionsScreenUseCase.kt b/face/capture/src/main/java/com/simprints/face/capture/usecases/ShouldShowInstructionsScreenUseCase.kt
new file mode 100644
index 0000000000..1e29419e62
--- /dev/null
+++ b/face/capture/src/main/java/com/simprints/face/capture/usecases/ShouldShowInstructionsScreenUseCase.kt
@@ -0,0 +1,29 @@
+package com.simprints.face.capture.usecases
+
+import androidx.annotation.VisibleForTesting
+import com.simprints.infra.security.SecurityManager
+import javax.inject.Inject
+import javax.inject.Singleton
+import androidx.core.content.edit
+
+@Singleton
+class ShouldShowInstructionsScreenUseCase @Inject constructor(
+ private val securityManager: SecurityManager,
+) {
+ operator fun invoke(): Boolean {
+ val sharedPrefs = securityManager.buildEncryptedSharedPreferences(FILENAME_FOR_INSTRUCTIONS_SHOWING_SHARED_PREFS)
+ val areInstructionsShowing = sharedPrefs.getBoolean(INSTRUCTIONS_SHOWING_PREFERENCE_KEY, true)
+ if (areInstructionsShowing) {
+ sharedPrefs.edit {
+ putBoolean(INSTRUCTIONS_SHOWING_PREFERENCE_KEY, false)
+ }
+ }
+ return areInstructionsShowing
+ }
+
+ companion object {
+ private const val FILENAME_FOR_INSTRUCTIONS_SHOWING_SHARED_PREFS = "INSTRUCTIONS_SHOWING"
+ @VisibleForTesting
+ const val INSTRUCTIONS_SHOWING_PREFERENCE_KEY = "preference_instructions_showing"
+ }
+}
diff --git a/face/capture/src/main/res/color/feedback_instructions_text.xml b/face/capture/src/main/res/color/feedback_instructions_text.xml
new file mode 100644
index 0000000000..302c39c06c
--- /dev/null
+++ b/face/capture/src/main/res/color/feedback_instructions_text.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/face/capture/src/main/res/drawable/feedback_instructions_outline.xml b/face/capture/src/main/res/drawable/feedback_instructions_outline.xml
new file mode 100644
index 0000000000..7ee0ad9dfc
--- /dev/null
+++ b/face/capture/src/main/res/drawable/feedback_instructions_outline.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
diff --git a/face/capture/src/main/res/layout-land/fragment_live_feedback.xml b/face/capture/src/main/res/layout-land/fragment_live_feedback.xml
index 1f95b141c0..78bff808de 100644
--- a/face/capture/src/main/res/layout-land/fragment_live_feedback.xml
+++ b/face/capture/src/main/res/layout-land/fragment_live_feedback.xml
@@ -8,6 +8,24 @@
tools:background="#66000000"
tools:ignore="ContentDescription">
+
+
+
+
+
+
diff --git a/face/capture/src/main/res/layout/fragment_live_feedback_auto_capture.xml b/face/capture/src/main/res/layout/fragment_live_feedback_auto_capture.xml
index 179d0a180b..6f748f7d76 100644
--- a/face/capture/src/main/res/layout/fragment_live_feedback_auto_capture.xml
+++ b/face/capture/src/main/res/layout/fragment_live_feedback_auto_capture.xml
@@ -87,4 +87,22 @@
app:layout_constraintTop_toBottomOf="@id/capture_feedback_txt_explanation"
tools:visibility="visible" />
+
+
diff --git a/face/capture/src/main/res/navigation/graph_face_capture_auto_internal.xml b/face/capture/src/main/res/navigation/graph_face_capture_auto_internal.xml
index e01cb708a7..129be9d9cd 100644
--- a/face/capture/src/main/res/navigation/graph_face_capture_auto_internal.xml
+++ b/face/capture/src/main/res/navigation/graph_face_capture_auto_internal.xml
@@ -27,6 +27,11 @@
app:destination="@id/faceConfirmationFragment"
app:popUpTo="@+id/graph_face_capture_auto_internal"
app:popUpToInclusive="true" />
+
+
(
@@ -100,6 +104,7 @@ class FaceCaptureViewModelTest {
},
saveLicenseCheckEvent,
isUsingAutoCapture,
+ shouldShowInstructionsScreen,
"deviceId",
)
}
@@ -302,6 +307,18 @@ class FaceCaptureViewModelTest {
assertThat(viewModel.isAutoCaptureEnabled.getOrAwaitValue()).isFalse()
}
+ @Test
+ fun `preparation instructions screen should be set to showing according to its use case`() {
+ // Given
+ coEvery { shouldShowInstructionsScreen() } returns true
+
+ // When
+ val isShowing = viewModel.shouldShowInstructionsScreen()
+
+ // Then
+ assertThat(isShowing).isTrue()
+ }
+
@Test
fun `test initFaceBioSdk should return error when re-download fails`() {
// Given
diff --git a/face/capture/src/test/java/com/simprints/face/capture/usecases/ShouldShowInstructionsScreenUseCaseTest.kt b/face/capture/src/test/java/com/simprints/face/capture/usecases/ShouldShowInstructionsScreenUseCaseTest.kt
new file mode 100644
index 0000000000..cbc932ec1b
--- /dev/null
+++ b/face/capture/src/test/java/com/simprints/face/capture/usecases/ShouldShowInstructionsScreenUseCaseTest.kt
@@ -0,0 +1,53 @@
+package com.simprints.face.capture.usecases
+
+import android.content.SharedPreferences
+import com.simprints.face.capture.usecases.ShouldShowInstructionsScreenUseCase.Companion.INSTRUCTIONS_SHOWING_PREFERENCE_KEY
+import com.simprints.infra.security.SecurityManager
+import io.mockk.*
+import io.mockk.impl.annotations.MockK
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+
+class ShouldShowInstructionsScreenUseCaseTest {
+
+ @MockK
+ private lateinit var securityManager: SecurityManager
+
+ @MockK
+ private lateinit var prefs: SharedPreferences
+
+ private lateinit var shouldShowInstructionsScreen: ShouldShowInstructionsScreenUseCase
+
+ @Before
+ fun setup() {
+ MockKAnnotations.init(this, relaxed = true)
+ every { securityManager.buildEncryptedSharedPreferences(any()) } returns prefs
+
+ shouldShowInstructionsScreen = ShouldShowInstructionsScreenUseCase(securityManager)
+ }
+
+ @Test
+ fun `should return true and write false to preferences when instructions are showing`() = runTest {
+ every { prefs.getBoolean(INSTRUCTIONS_SHOWING_PREFERENCE_KEY, any()) } returns true
+ val editor = mockk(relaxed = true)
+ every { prefs.edit() } returns editor
+
+ val result = shouldShowInstructionsScreen()
+
+ assertTrue(result)
+ verify { editor.putBoolean(INSTRUCTIONS_SHOWING_PREFERENCE_KEY, false) }
+ }
+
+ @Test
+ fun `should return false and not write to preferences when instructions are not showing`() = runTest {
+ every { prefs.getBoolean(INSTRUCTIONS_SHOWING_PREFERENCE_KEY, any()) } returns false
+
+ val result = shouldShowInstructionsScreen()
+
+ assertFalse(result)
+ verify(exactly = 0) { prefs.edit() }
+ }
+}
diff --git a/infra/resources/src/main/res/values/strings.xml b/infra/resources/src/main/res/values/strings.xml
index cf4981f5d7..3164eb6c24 100644
--- a/infra/resources/src/main/res/values/strings.xml
+++ b/infra/resources/src/main/res/values/strings.xml
@@ -172,6 +172,7 @@
Live Capture
Preparation
Start capture
+ Instructions
Preparing to scan
No face detected
Make sure the face fills the circle