package idsdk.overlay import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import android.widget.Toast import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.getcapacitor.plugin.luna.idsdk.R import com.getcapacitor.plugin.luna.idsdk.databinding.FragmentOverlayBinding import idsdk.Settings import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import ru.visionlabs.sdk.lunacore.faceengine.DetectionError import ru.visionlabs.sdk.lunacore.Interaction import ru.visionlabs.sdk.lunacore.LunaID import ru.visionlabs.sdk.lunacore.LunaInteractionType class OverlayFragment : Fragment() { private var _binding: FragmentOverlayBinding? = null private val binding get() = _binding!! private var interactionAnimation: AnimatorSet? = null companion object{ private const val TAG = "OverlayFragment" } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentOverlayBinding.inflate(inflater, container, false) with (binding) { overlayClose.setOnClickListener { requireActivity().finish() } } return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.overlayViewport.isVisible = true binding.overlayDetection.isVisible = Settings.overlayShowDetection LunaID.errorFlow .onEach { setTextVisibility(binding.overlayError, getErrorMessage(it.error)) delay(1000) setTextVisibility(binding.overlayError, null) } .launchIn(this.lifecycleScope) LunaID.currentInteractionType .filterNotNull() .onEach { Log.d(TAG,"onViewCreated: collected interaction $it") setTextVisibility(_binding?.overlayInteraction, getInteractionTip(it)) updateInteractionAnimation(it) }.flowOn(Dispatchers.Main) .launchIn(lifecycleScope) LunaID.eventChannel.receiveAsFlow() .onEach { when(it){ is LunaID.Event.SecurityCheck.Success -> { Log.d(TAG, "onViewCreated() collect security SUCCESS") } is LunaID.Event.SecurityCheck.Failure -> { activity?.finish() Log.d(TAG, "onViewCreated() collect security FAILURE") } is LunaID.Event.FaceFound -> { Log.d(TAG, "onViewCreated() face found") } is LunaID.Event.InteractionEnded -> { Log.d(TAG, "onViewCreated() interaction ended") setTextVisibility(_binding?.overlayInteraction, null) stopHeadTurnAnimation() } is LunaID.Event.InteractionFailed -> { Log.d(TAG, "onViewCreated() interaction failed") setTextVisibility(_binding?.overlayInteraction, null) stopHeadTurnAnimation() } is LunaID.Event.InteractionTimeout -> { Log.d(TAG, "onViewCreated() interaction timeout") setTextVisibility(_binding?.overlayInteraction, null) stopHeadTurnAnimation() Toast.makeText(this.activity, "Interaction timeout", Toast.LENGTH_LONG).show() activity?.finish(); } is LunaID.Event.LivenessCheckError -> { Log.d(TAG, "onViewCreated() liveness check error ${it.cause}") } is LunaID.Event.LivenessCheckFailed -> { Log.d(TAG, "onViewCreated() Liveness Check Failed") // todo fix several events incoming activity?.finish() Toast.makeText(this.activity, "liveness check error", Toast.LENGTH_LONG).show() } is LunaID.Event.LivenessCheckStarted -> { Log.d(TAG, "onViewCreated() liveness check started") } is LunaID.Event.Started -> { Log.d(TAG, "onViewCreated() started") } is LunaID.Event.UnknownError -> { Log.d(TAG, "onViewCreated() unknown error ${it.cause}") } else -> { Log.d(TAG,"onViewCreated() collected unknown event") } } } .launchIn(this.lifecycleScope) } private fun getErrorMessage(err: DetectionError?): String { Log.d(TAG, err.toString()) return when (err) { DetectionError.PrimaryFaceLostCritical, DetectionError.PrimaryFaceLost, DetectionError.FaceLost, DetectionError.FaceOutOfFrame, DetectionError.FaceDetectSmall -> { "Поместите лицо в центр рамки" } DetectionError.TooDark -> { "Плохие условия съемки: Слишком темно" } DetectionError.TooMuchLight -> { "Плохие условия съемки: Лицо засвечено" } DetectionError.TooManyFaces -> { "Убедитесь, что в кадре только Вы" } DetectionError.BadHeadPose -> { "Неправильный ракурс головы. Поверните голову к камере и держите её прямо" } DetectionError.BadEyesStatus -> { "Держите глаза открытыми" } DetectionError.BadQuality -> { "Низкое качество изображения. Проверьте условия съёмки" } DetectionError.BlurredFace -> { "Плохие условия съемки или лицо размыто" } DetectionError.OccludedFace -> { "Лицо перекрыто. Проверьте, что посторонние предметы не перекрывают лицо" } DetectionError.GlassesOn -> { "Пожалуйста, снимите очки" } DetectionError.FaceWithMask -> { "Пожалуйста, снимите маску" } else -> { "Неизвестная ошибка" } } } private fun getInteractionTip(@LunaInteractionType interactionType: Int): String { return when (interactionType) { Interaction.BLINK -> { "Моргните глазами" } Interaction.YAW_LEFT -> { "Поверните голову налево и вернитесь в исходное положение" } Interaction.YAW_RIGHT -> { "Поверните голову направо и вернитесь в исходное положение" } Interaction.PITCH_UP -> { "Поднимите голову вверх и вернитесь в исходное положение" } Interaction.PITCH_DOWN -> { "Опустите голову вниз и вернитесь в исходное положение" } else -> { "Ошибка при идентификации: $interactionType" } } } private fun setTextVisibility(textView: TextView?, text: String?) { textView ?: return textView.text = text.orEmpty() textView.isVisible = !text.isNullOrBlank() } private fun updateInteractionAnimation(@LunaInteractionType interactionType: Int) { when (interactionType) { Interaction.YAW_LEFT -> startHeadTurnAnimation( drawableRes = R.drawable.ic_head_turn_left, direction = -1f ) Interaction.YAW_RIGHT -> startHeadTurnAnimation( drawableRes = R.drawable.ic_head_turn_right, direction = 1f ) else -> stopHeadTurnAnimation() } } private fun startHeadTurnAnimation(drawableRes: Int, direction: Float) { val icon = _binding?.overlayInteractionAnimation ?: return icon.setImageResource(drawableRes) icon.isVisible = true icon.alpha = 1f icon.translationX = 0f interactionAnimation?.cancel() val distancePx = 20f * resources.displayMetrics.density * direction val moveAnimator = ObjectAnimator.ofFloat(icon, View.TRANSLATION_X, 0f, distancePx).apply { duration = 500 repeatCount = ValueAnimator.INFINITE repeatMode = ValueAnimator.REVERSE } val alphaAnimator = ObjectAnimator.ofFloat(icon, View.ALPHA, 1f, 0.35f).apply { duration = 500 repeatCount = ValueAnimator.INFINITE repeatMode = ValueAnimator.REVERSE } interactionAnimation = AnimatorSet().apply { playTogether(moveAnimator, alphaAnimator) start() } } private fun stopHeadTurnAnimation() { interactionAnimation?.cancel() interactionAnimation = null _binding?.overlayInteractionAnimation?.apply { clearAnimation() alpha = 1f translationX = 0f isVisible = false } } override fun onDestroyView() { stopHeadTurnAnimation() super.onDestroyView() _binding = null } }