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 61f5fa1200..db9f7b8d5e 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 @@ -13,6 +13,8 @@ import androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888 import androidx.camera.core.Preview +import androidx.camera.core.resolutionselector.ResolutionSelector +import androidx.camera.core.resolutionselector.ResolutionStrategy import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.awaitInstance import androidx.core.content.ContextCompat @@ -37,6 +39,7 @@ import com.simprints.infra.logging.LoggingConstants.CrashReportTag.ORCHESTRATION import com.simprints.infra.logging.Simber import com.simprints.infra.uibase.navigation.navigateSafely import com.simprints.infra.uibase.view.applySystemBarInsets +import com.simprints.infra.uibase.view.awaitLayout import com.simprints.infra.uibase.view.setCheckedWithLeftDrawable import com.simprints.infra.uibase.viewbinding.viewBinding import dagger.hilt.android.AndroidEntryPoint @@ -145,6 +148,8 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) if (::cameraExecutor.isInitialized && !cameraExecutor.isShutdown) { return@launch } + // Wait for the views to be properly laid out + binding.captureOverlay.awaitLayout() // Initialize our background executor cameraExecutor = Executors.newSingleThreadExecutor() // ImageAnalysis @@ -152,10 +157,18 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) if (!::targetResolution.isInitialized) { targetResolution = Size(binding.captureOverlay.width, binding.captureOverlay.height) } + val resolutionSelector = ResolutionSelector + .Builder() + .setResolutionStrategy( + ResolutionStrategy( + targetResolution, + ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER, + ), + ).build() val imageAnalyzer = ImageAnalysis .Builder() - .setTargetResolution(targetResolution) + .setResolutionSelector(resolutionSelector) .setOutputImageRotationEnabled(true) .setOutputImageFormat(OUTPUT_IMAGE_FORMAT_RGBA_8888) .build() @@ -164,7 +177,10 @@ internal class LiveFeedbackFragment : Fragment(R.layout.fragment_live_feedback) imageAnalyzer.setAnalyzer(cameraExecutor, cropAnalyzer) // Preview - val preview = Preview.Builder().setTargetResolution(targetResolution).build() + val preview = Preview + .Builder() + .setResolutionSelector(resolutionSelector) + .build() val cameraProvider = ProcessCameraProvider.awaitInstance(requireContext()) cameraProvider.unbindAll() val camera = cameraProvider.bindToLifecycle( diff --git a/infra/ui-base/src/main/java/com/simprints/infra/uibase/view/View.ext.kt b/infra/ui-base/src/main/java/com/simprints/infra/uibase/view/View.ext.kt index 444027fd3d..abcce566ed 100644 --- a/infra/ui-base/src/main/java/com/simprints/infra/uibase/view/View.ext.kt +++ b/infra/ui-base/src/main/java/com/simprints/infra/uibase/view/View.ext.kt @@ -2,13 +2,14 @@ package com.simprints.infra.uibase.view import android.animation.ObjectAnimator import android.view.View -import android.view.ViewPropertyAnimator import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import androidx.core.view.isVisible import androidx.fragment.app.Fragment import com.simprints.infra.uibase.annotations.ExcludedFromGeneratedTestCoverageReports +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume @ExcludedFromGeneratedTestCoverageReports("View animation") fun View.setPulseAnimation(isEnabled: Boolean) { @@ -75,3 +76,38 @@ private const val PULSE_ANIMATION_ALPHA_FULL = 1.0f private const val PULSE_ANIMATION_ALPHA_INTERMEDIATE = 0.9f private const val PULSE_ANIMATION_ALPHA_MIN = 0.6f private const val PULSE_ANIMATION_DURATION_MILLIS = 2000L + +@ExcludedFromGeneratedTestCoverageReports("View extension") +suspend fun View.awaitLayout() = suspendCancellableCoroutine { continuation -> + if (isLaidOut) { + continuation.resume(Unit) + return@suspendCancellableCoroutine + } + + val listener = object : View.OnLayoutChangeListener { + @ExcludedFromGeneratedTestCoverageReports("View extension") + override fun onLayoutChange( + v: View, + l: Int, + t: Int, + r: Int, + b: Int, + ol: Int, + ot: Int, + or: Int, + ob: Int, + ) { + v.removeOnLayoutChangeListener(this) + + if (continuation.isActive) { + continuation.resume(Unit) + } + } + } + + addOnLayoutChangeListener(listener) + + continuation.invokeOnCancellation { + removeOnLayoutChangeListener(listener) + } +}