From 312f0c13657cdc4df76bac4da3828573e30058d9 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Tue, 23 Apr 2024 15:15:39 +0300 Subject: [PATCH 1/2] MS-154 Enable frame processor to handle delayed initialisation of crop rect --- .../screens/livefeedback/FrameProcessor.kt | 25 +++++++++---------- .../LiveFeedbackFragmentViewModel.kt | 5 ++++ .../usecases/ImageProxyToBitmapUseCase.kt | 7 +++++- .../usecases/ImageProxyToBitmapUseCaseTest.kt | 19 +++++++++++--- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/FrameProcessor.kt b/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/FrameProcessor.kt index 2479252786..b50f87da58 100644 --- a/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/FrameProcessor.kt +++ b/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/FrameProcessor.kt @@ -13,14 +13,14 @@ import javax.inject.Inject internal class FrameProcessor @Inject constructor( - private val imageProxyToBitmap: ImageProxyToBitmapUseCase + private val imageProxyToBitmap: ImageProxyToBitmapUseCase, ) { private var previewViewWidth: Int = 0 private var previewViewHeight: Int = 0 private lateinit var boxOnTheScreen: RectF - private lateinit var cropRect: Rect + private var cropRect: Rect? = null /** * Init the frame processor @@ -42,15 +42,14 @@ internal class FrameProcessor @Inject constructor( * @param image * @return Bitmap */ - fun cropRotateFrame(image: ImageProxy): Bitmap { - if (!this::cropRect.isInitialized) { - // The cropRect should be calculated once as its value will be the same for all images. - calcRotatedCropRect(image) - } - return imageProxyToBitmap(image, cropRect) + fun cropRotateFrame(image: ImageProxy): Bitmap? { + val rect = cropRect?.takeUnless { it.isEmpty } + ?: calcRotatedCropRect(image).also { cropRect = it } + + return imageProxyToBitmap(image, rect) } - private fun calcRotatedCropRect(image: ImageProxy) { + private fun calcRotatedCropRect(image: ImageProxy): Rect { val cameraWidth = image.width val cameraHeight = image.height @@ -67,7 +66,7 @@ internal class FrameProcessor @Inject constructor( val newBoundingBox = CameraTargetOverlay.rectForPlane(rotatedCameraWidth, rotatedCameraHeight, newRectSize) - cropRect = getRotatedBoundingBox( + return getRotatedBoundingBox( image.imageInfo.rotationDegrees, newBoundingBox, cameraWidth, @@ -79,7 +78,7 @@ internal class FrameProcessor @Inject constructor( rotation: Int, newBoundingBox: RectF, cameraWidth: Int, - cameraHeight: Int + cameraHeight: Int, ): RectF { return when (360 - rotation) { 0, 360 -> newBoundingBox @@ -113,7 +112,7 @@ internal class FrameProcessor @Inject constructor( screenHeight: Int, cameraWidth: Int, cameraHeight: Int, - boxOnTheScreen: RectF + boxOnTheScreen: RectF, ): Float { return if (screenWidth == cameraWidth || screenHeight == cameraHeight) { val cameraArea = cameraHeight * cameraWidth @@ -157,7 +156,7 @@ internal class FrameProcessor @Inject constructor( screenWidth: Int, cameraHeight: Int, screenHeight: Int, - currentWidth: Float + currentWidth: Float, ): Float { val widthRatio = cameraWidth / screenWidth.toFloat() val heightRatio = cameraHeight / screenHeight.toFloat() diff --git a/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/LiveFeedbackFragmentViewModel.kt b/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/LiveFeedbackFragmentViewModel.kt index 1f1f400550..abf23157a5 100644 --- a/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/LiveFeedbackFragmentViewModel.kt +++ b/face/capture/src/main/java/com/simprints/face/capture/screens/livefeedback/LiveFeedbackFragmentViewModel.kt @@ -58,6 +58,11 @@ internal class LiveFeedbackFragmentViewModel @Inject constructor( fun process(image: ImageProxy) { val captureStartTime = timeHelper.now() val croppedBitmap = frameProcessor.cropRotateFrame(image) + + if (croppedBitmap == null) { + image.close() + return + } val potentialFace = faceDetector.analyze(croppedBitmap) val faceDetection = getFaceDetectionFromPotentialFace(croppedBitmap, potentialFace) diff --git a/face/capture/src/main/java/com/simprints/face/capture/usecases/ImageProxyToBitmapUseCase.kt b/face/capture/src/main/java/com/simprints/face/capture/usecases/ImageProxyToBitmapUseCase.kt index 95599fa3cf..ae4ddb7277 100644 --- a/face/capture/src/main/java/com/simprints/face/capture/usecases/ImageProxyToBitmapUseCase.kt +++ b/face/capture/src/main/java/com/simprints/face/capture/usecases/ImageProxyToBitmapUseCase.kt @@ -13,7 +13,7 @@ import javax.inject.Inject */ internal class ImageProxyToBitmapUseCase @Inject constructor() { - operator fun invoke(imageProxy: ImageProxy, cropRect: Rect): Bitmap { + operator fun invoke(imageProxy: ImageProxy, cropRect: Rect): Bitmap? { require(imageProxy.format == PixelFormat.RGBA_8888) { "${imageProxy.format} is not supported. RGBA_8888 is the only supported image format" } @@ -27,6 +27,11 @@ internal class ImageProxyToBitmapUseCase @Inject constructor() { bitmap.copyPixelsFromBuffer(buffer) val rotationMatrix = Matrix() rotationMatrix.postRotate(imageProxy.imageInfo.rotationDegrees.toFloat()) + + if (cropRect.isEmpty) { + return null + } + val croppedRotatedBitmap = Bitmap.createBitmap( bitmap, cropRect.left, diff --git a/face/capture/src/test/java/com/simprints/face/capture/usecases/ImageProxyToBitmapUseCaseTest.kt b/face/capture/src/test/java/com/simprints/face/capture/usecases/ImageProxyToBitmapUseCaseTest.kt index 8d5a636139..94e0eacde8 100644 --- a/face/capture/src/test/java/com/simprints/face/capture/usecases/ImageProxyToBitmapUseCaseTest.kt +++ b/face/capture/src/test/java/com/simprints/face/capture/usecases/ImageProxyToBitmapUseCaseTest.kt @@ -4,7 +4,8 @@ import android.graphics.Bitmap import android.graphics.ImageFormat import android.graphics.Rect import androidx.camera.core.ImageProxy -import com.google.common.truth.Truth +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat import io.mockk.every import io.mockk.justRun import io.mockk.mockk @@ -13,7 +14,9 @@ import io.mockk.unmockkStatic import org.junit.After import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4::class) class ImageProxyToBitmapUseCaseTest { private var imageBytes: ByteArray = ByteArray(BYTEARRAY_SIZE) @@ -34,7 +37,6 @@ class ImageProxyToBitmapUseCaseTest { private val bitmapMock = mockk { justRun { copyPixelsFromBuffer(any()) } justRun { recycle() } - every { config } returns Bitmap.Config.ARGB_8888 } @Before @@ -59,7 +61,17 @@ class ImageProxyToBitmapUseCaseTest { Rect(CROP_RECT_LEFT, CROP_RECT_TOP, CROP_RECT_RIGHT, CROP_RECT_BOTTOM) ) // Then - Truth.assertThat(bitmap.config).isEqualTo(Bitmap.Config.ARGB_8888) + assertThat(bitmap).isNotNull() + } + + @Test + fun `Should return a null if cropRect is empty`() { + + val rect = Rect(CROP_RECT_LEFT, CROP_RECT_TOP, CROP_RECT_LEFT, CROP_RECT_TOP) + // When + val bitmap = useCase.invoke(imageProxy, rect) + // Then + assertThat(bitmap).isNull() } @Test(expected = IllegalArgumentException::class) @@ -76,6 +88,7 @@ class ImageProxyToBitmapUseCaseTest { } companion object { + const val IMAGE_WIDTH = 150 const val IMAGE_HEIGHT = 150 From c78164c4f583ccbbefdccc8c2043152b22a9c6b6 Mon Sep 17 00:00:00 2001 From: Sergejs Luhmirins Date: Wed, 5 Jun 2024 16:10:18 +0300 Subject: [PATCH 2/2] MS-154 Add simple "grant permission" flow if no camera permission --- .../capture/screens/FaceCaptureViewModel.kt | 3 + .../livefeedback/LiveFeedbackFragment.kt | 126 ++++++++++++------ .../res/layout/fragment_live_feedback.xml | 14 +- .../src/main/res/values-am-rET/strings.xml | 2 +- .../src/main/res/values-am/strings.xml | 2 +- .../src/main/res/values-bn/strings.xml | 2 +- .../src/main/res/values-fr/strings.xml | 2 +- .../src/main/res/values-hi/strings.xml | 2 +- .../resources/src/main/res/values/strings.xml | 3 +- 9 files changed, 108 insertions(+), 48 deletions(-) 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 ae7f00a9b4..aae56e5a12 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 @@ -26,6 +26,7 @@ import com.simprints.infra.logging.Simber import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject @HiltViewModel @@ -43,6 +44,8 @@ internal class FaceCaptureViewModel @Inject constructor( var attemptNumber: Int = 0 var samplesToCapture = 1 + var shouldCheckCameraPermissions = AtomicBoolean(true) + private var faceDetections = listOf() val recaptureEvent: LiveData 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 03b8a1bfea..8f14e201d5 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 @@ -1,10 +1,12 @@ package com.simprints.face.capture.screens.livefeedback import android.Manifest +import android.content.Intent +import android.net.Uri import android.os.Bundle +import android.provider.Settings import android.util.Size import android.view.View -import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA import androidx.camera.core.ImageAnalysis @@ -13,6 +15,8 @@ import androidx.camera.core.ImageProxy import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.content.ContextCompat +import androidx.core.view.isGone +import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -20,7 +24,9 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.work.await +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.models.FaceDetection @@ -55,22 +61,17 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) private lateinit var screenSize: Size - private val launchPermissionRequest = registerForActivityResult( ActivityResultContracts.RequestPermission(), ) { granted -> - if (!granted) { - Simber.i("Camera Permission not granted") - Toast.makeText( - requireContext(), - IDR.string.face_capturing_permission_denied, - Toast.LENGTH_LONG - ).show() - } else { - setUpCamera() + when (requireActivity().permissionFromResult(Manifest.permission.CAMERA, granted)) { + PermissionStatus.Granted -> setUpCamera() + PermissionStatus.Denied -> renderNoPermission(false) + PermissionStatus.DeniedNeverAskAgain -> renderNoPermission(true) } } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initFragment() @@ -78,7 +79,6 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) private fun initFragment() { screenSize = with(resources.displayMetrics) { Size(widthPixels, widthPixels) } - bindViewModel() binding.captureFeedbackTxtTitle.setOnClickListener { vm.startCapture() } @@ -119,24 +119,28 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) preview.setSurfaceProvider(binding.faceCaptureCamera.surfaceProvider) } - override fun onStart() { - super.onStart() + override fun onResume() { + super.onResume() - // Check permission in onStart() so that if user left the app to go to Settings - // and give the permission, it's reflected when they come back to SID - if (requireActivity().hasPermission(Manifest.permission.CAMERA)) { - setUpCamera() + if (mainVm.shouldCheckCameraPermissions.getAndSet(false)) { + // Check permission in onResume() so that if user left the app to go to Settings + // and give the permission, it's reflected when they come back to SID + if (requireActivity().hasPermission(Manifest.permission.CAMERA)) { + setUpCamera() + } else { + launchPermissionRequest.launch(Manifest.permission.CAMERA) + } } else { - launchPermissionRequest.launch(Manifest.permission.CAMERA) + mainVm.shouldCheckCameraPermissions.set(true) } } - override fun onStop() { + override fun onPause() { // Shut down our background executor - if(::cameraExecutor.isInitialized) { + if (::cameraExecutor.isInitialized) { cameraExecutor.shutdown() } - super.onStop() + super.onPause() } private fun bindViewModel() { @@ -152,7 +156,10 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) LiveFeedbackFragmentViewModel.CapturingState.FINISHED -> { mainVm.captureFinished(vm.sortedQualifyingCaptures) - findNavController().navigateSafely(this, R.id.action_faceLiveFeedbackFragment_to_faceConfirmationFragment) + findNavController().navigateSafely( + this, + R.id.action_faceLiveFeedbackFragment_to_faceConfirmationFragment + ) } } @@ -199,8 +206,10 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) private fun renderCapturingNotStarted() { binding.apply { captureOverlay.drawSemiTransparentTarget() - captureTitle.text = getString(IDR.string.face_capture_preparation_title) - captureFeedbackTxtTitle.text = getString(IDR.string.face_capture_title_previewing) + captureTitle.setText(IDR.string.face_capture_preparation_title) + captureFeedbackTxtTitle.isVisible = true + captureFeedbackTxtTitle.setText(IDR.string.face_capture_title_previewing) + captureFeedbackPermissionButton.isGone = true } toggleCaptureButtons(false) } @@ -209,17 +218,20 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) renderCapturingStateColors() binding.apply { captureProgress.isVisible = true - captureTitle.text = getString(IDR.string.face_capture_capturing_title) - captureFeedbackTxtTitle.text = - getString(IDR.string.face_capture_prep_begin_button_capturing) + captureTitle.setText(IDR.string.face_capture_capturing_title) + captureFeedbackTxtTitle.isVisible = true + captureFeedbackTxtTitle.setText(IDR.string.face_capture_prep_begin_button_capturing) + captureFeedbackPermissionButton.isGone = true } toggleCaptureButtons(false) } private fun renderValidFace() { binding.apply { - captureFeedbackTxtTitle.text = getString(IDR.string.face_capture_begin_button) + captureFeedbackTxtTitle.isVisible = true + captureFeedbackTxtTitle.setText(IDR.string.face_capture_begin_button) captureFeedbackTxtExplanation.text = null + captureFeedbackPermissionButton.isGone = true captureFeedbackTxtTitle.setCheckedWithLeftDrawable( true, ContextCompat.getDrawable(requireContext(), R.drawable.ic_checked_white_18dp) @@ -230,9 +242,10 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) private fun renderValidCapturingFace() { binding.apply { - captureFeedbackTxtTitle.text = - getString(IDR.string.face_capture_prep_begin_button_capturing) - captureFeedbackTxtExplanation.text = getString(IDR.string.face_capture_hold) + captureFeedbackTxtTitle.isVisible = true + captureFeedbackTxtTitle.setText(IDR.string.face_capture_prep_begin_button_capturing) + captureFeedbackTxtExplanation.setText(IDR.string.face_capture_hold) + captureFeedbackPermissionButton.isGone = true captureFeedbackTxtTitle.setCheckedWithLeftDrawable( true, ContextCompat.getDrawable(requireContext(), R.drawable.ic_checked_white_18dp) @@ -244,8 +257,10 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) private fun renderFaceTooFar() { binding.apply { - captureFeedbackTxtTitle.text = getString(IDR.string.face_capture_title_too_far) - captureFeedbackTxtExplanation.text = getString(IDR.string.face_capture_error_too_far) + captureFeedbackTxtTitle.isVisible = true + captureFeedbackTxtTitle.setText(IDR.string.face_capture_title_too_far) + captureFeedbackTxtExplanation.setText(IDR.string.face_capture_error_too_far) + captureFeedbackPermissionButton.isGone = true captureFeedbackTxtTitle.setCheckedWithLeftDrawable(false) } @@ -256,8 +271,10 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) private fun renderFaceTooClose() { binding.apply { - captureFeedbackTxtTitle.text = getString(IDR.string.face_capture_title_too_close) - captureFeedbackTxtExplanation.text = getString(IDR.string.face_capture_error_too_close) + captureFeedbackTxtTitle.isVisible = true + captureFeedbackTxtTitle.setText(IDR.string.face_capture_title_too_close) + captureFeedbackTxtExplanation.setText(IDR.string.face_capture_error_too_close) + captureFeedbackPermissionButton.isInvisible = true captureFeedbackTxtTitle.setCheckedWithLeftDrawable(false) } @@ -268,8 +285,10 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) private fun renderNoFace() { binding.apply { - captureFeedbackTxtTitle.text = getString(IDR.string.face_capture_title_no_face) - captureFeedbackTxtExplanation.text = getString(IDR.string.face_capture_error_no_face) + captureFeedbackTxtTitle.isVisible = true + captureFeedbackTxtTitle.setText(IDR.string.face_capture_title_no_face) + captureFeedbackTxtExplanation.setText(IDR.string.face_capture_error_no_face) + captureFeedbackPermissionButton.isGone = true captureFeedbackTxtTitle.setCheckedWithLeftDrawable(false) } @@ -280,9 +299,10 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) private fun renderFaceNotStraight() { binding.apply { - captureFeedbackTxtTitle.text = getString(IDR.string.face_capture_title_look_straight) - captureFeedbackTxtExplanation.text = - getString(IDR.string.face_capture_error_look_straight) + captureFeedbackTxtTitle.isVisible = true + captureFeedbackTxtTitle.setText(IDR.string.face_capture_title_look_straight) + captureFeedbackTxtExplanation.setText(IDR.string.face_capture_error_look_straight) + captureFeedbackPermissionButton.isGone = true captureFeedbackTxtTitle.setCheckedWithLeftDrawable(false) } @@ -307,6 +327,30 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) private fun toggleCaptureButtons(valid: Boolean) { binding.captureFeedbackTxtTitle.isClickable = valid } + + private fun renderNoPermission(shouldOpenSettings: Boolean) { + binding.apply { + captureOverlay.drawSemiTransparentTarget() + captureFeedbackTxtTitle.isInvisible = true + captureFeedbackTxtExplanation.setText(IDR.string.face_capture_permission_denied) + + captureFeedbackPermissionButton.isVisible = true + captureFeedbackPermissionButton.setOnClickListener { + if (shouldOpenSettings) { + requireActivity().startActivity( + Intent( + Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.parse("package:${requireActivity().packageName}") + ) + ) + } else { + launchPermissionRequest.launch(Manifest.permission.CAMERA) + } + } + } + toggleCaptureButtons(false) + } + } diff --git a/face/capture/src/main/res/layout/fragment_live_feedback.xml b/face/capture/src/main/res/layout/fragment_live_feedback.xml index 0e708ddd71..6b7e55d411 100644 --- a/face/capture/src/main/res/layout/fragment_live_feedback.xml +++ b/face/capture/src/main/res/layout/fragment_live_feedback.xml @@ -58,7 +58,6 @@ android:layout_height="40dp" android:layout_marginTop="190dp" android:background="@drawable/feedback_chip_white" - android:textColor="@color/feedback_chip_text" android:drawablePadding="4dp" android:gravity="center" android:paddingStart="12dp" @@ -66,6 +65,7 @@ android:paddingEnd="12dp" android:paddingBottom="4dp" android:text="@string/face_capture_title_previewing" + android:textColor="@color/feedback_chip_text" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/capture_guide_frame" /> @@ -84,4 +84,16 @@ app:layout_constraintTop_toBottomOf="@id/capture_feedback_txt_title" tools:text="Move closer" /> + + diff --git a/infra/resources/src/main/res/values-am-rET/strings.xml b/infra/resources/src/main/res/values-am-rET/strings.xml index 9cff19410d..f04a90d154 100644 --- a/infra/resources/src/main/res/values-am-rET/strings.xml +++ b/infra/resources/src/main/res/values-am-rET/strings.xml @@ -180,7 +180,7 @@ ሰዉየዉ ቀጥታ ካሜራዉን እንዲያይ አድርግ ለማንሳት ተጫን በማንሳት ላይ.... - የካሜራ ፈቃድ አብራ + የካሜራ ፈቃድ አብራ ባለበት ፖዚሽኑ ላይ አቆይ.... የፊት ምስል በስኬት ተመዝግቧል ማረጋገጥ diff --git a/infra/resources/src/main/res/values-am/strings.xml b/infra/resources/src/main/res/values-am/strings.xml index b5599e66f7..4490bbab6d 100644 --- a/infra/resources/src/main/res/values-am/strings.xml +++ b/infra/resources/src/main/res/values-am/strings.xml @@ -183,7 +183,7 @@ ሰዉየዉ ቀጥታ ካሜራዉን እንዲያይ አድርግ ለማንሳት ተጫን በማንሳት ላይ.... - እባክዎ የካሜራ ፍቃድ ያብሩ + እባክዎ የካሜራ ፍቃድ ያብሩ ባለበት ፖዚሽኑ ላይ አቆይ.... የፊት ምስል በስኬት ተመዝግቧል ማረጋገጥ diff --git a/infra/resources/src/main/res/values-bn/strings.xml b/infra/resources/src/main/res/values-bn/strings.xml index 856441c259..a4755a15a7 100644 --- a/infra/resources/src/main/res/values-bn/strings.xml +++ b/infra/resources/src/main/res/values-bn/strings.xml @@ -180,7 +180,7 @@ ব্যক্তি ক্যামেরার দিকে সোজা তাকাচ্ছে কিনা নিশ্চিত করুন ছবি তুলতে এখানে চাপুন ছবি তোলা হচ্ছে - অনুগ্রহ করে ক্যামেরা ব্যাবহারের অনুমতি দিন + অনুগ্রহ করে ক্যামেরা ব্যাবহারের অনুমতি দিন স্থির থাকুন ব্যক্তির মুখের ছবি তোলা সফল হয়েছে নিশ্চিত করা হচ্ছে diff --git a/infra/resources/src/main/res/values-fr/strings.xml b/infra/resources/src/main/res/values-fr/strings.xml index 02d71a19d5..4906505e35 100644 --- a/infra/resources/src/main/res/values-fr/strings.xml +++ b/infra/resources/src/main/res/values-fr/strings.xml @@ -185,7 +185,7 @@ Assurez-vous que la personne regarde droit vers la caméra. Touchez pour capturer Capture en cours... - Veuillez autoriser les permissions de l\'appareil photo + Veuillez autoriser les permissions de l\'appareil photo Maintenez la position... Le visage a été capturé avec succès. Confirmation diff --git a/infra/resources/src/main/res/values-hi/strings.xml b/infra/resources/src/main/res/values-hi/strings.xml index bcd30a90c8..8c34d09746 100644 --- a/infra/resources/src/main/res/values-hi/strings.xml +++ b/infra/resources/src/main/res/values-hi/strings.xml @@ -180,7 +180,7 @@ सुनिश्चित करें कि व्यक्ति सीधे कैमरे में देख रहा है कैप्चर करने के लिए टैप करें कैप्चर हो रहा है....... - कृपया कैमरे की अनुमति दें + कृपया कैमरे की अनुमति दें अपनी जगह पर रहें चेहरे को सफलतापूर्वक कैप्चर कर लिया गया है। पुष्टीकरण diff --git a/infra/resources/src/main/res/values/strings.xml b/infra/resources/src/main/res/values/strings.xml index ce71180c4f..5da39923ba 100644 --- a/infra/resources/src/main/res/values/strings.xml +++ b/infra/resources/src/main/res/values/strings.xml @@ -180,7 +180,8 @@ Make sure the person is looking straight at the camera Tap to capture Capturing… - Please allow camera permission + Please grant the camera permission + Grant permission Hold the position… Face has been successfully captured. Confirmation