Coverage Summary for Class: AbstractInAppViewHolder (cloud.mindbox.mobile_sdk.inapp.presentation.view)

Class Method, % Branch, % Line, % Instruction, %
AbstractInAppViewHolder 0% (0/23) 0% (0/38) 0% (0/105) 0% (0/537)
AbstractInAppViewHolder$buildCacheRequestListener$1 0% (0/3) 0% (0/6) 0% (0/28) 0% (0/177)
AbstractInAppViewHolder$inAppFailureTracker$2 0% (0/1) 0% (0/1) 0% (0/2)
AbstractInAppViewHolder$inAppLayout$2 0% (0/1) 0% (0/1) 0% (0/8)
Total 0% (0/28) 0% (0/44) 0% (0/135) 0% (0/724)


 package cloud.mindbox.mobile_sdk.inapp.presentation.view
 
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.view.inputmethod.InputMethodManager
 import android.widget.FrameLayout
 import android.widget.ImageView
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.isVisible
 import cloud.mindbox.mobile_sdk.R
 import cloud.mindbox.mobile_sdk.di.mindboxInject
 import cloud.mindbox.mobile_sdk.inapp.domain.extensions.sendPresentationFailure
 import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppFailureTracker
 import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppType
 import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppTypeWrapper
 import cloud.mindbox.mobile_sdk.inapp.domain.models.Layer
 import cloud.mindbox.mobile_sdk.inapp.presentation.InAppCallback
 import cloud.mindbox.mobile_sdk.inapp.presentation.InAppMessageViewDisplayerImpl
 import cloud.mindbox.mobile_sdk.inapp.presentation.MindboxView
 import cloud.mindbox.mobile_sdk.inapp.presentation.actions.InAppActionHandler
 import cloud.mindbox.mobile_sdk.logger.mindboxLogI
 import cloud.mindbox.mobile_sdk.maxScreenDimension
 import cloud.mindbox.mobile_sdk.removeChildById
 import cloud.mindbox.mobile_sdk.safeAs
 import cloud.mindbox.mobile_sdk.setSingleClickListener
 import com.bumptech.glide.Glide
 import com.bumptech.glide.load.DataSource
 import com.bumptech.glide.load.engine.DiskCacheStrategy
 import com.bumptech.glide.load.engine.GlideException
 import com.bumptech.glide.request.RequestListener
 import com.bumptech.glide.request.target.Target
 
 internal abstract class AbstractInAppViewHolder<T : InAppType>(
     final override val wrapper: InAppTypeWrapper<T>,
     final override val inAppController: InAppViewHolder.InAppController,
     final override val inAppCallback: InAppCallback,
 ) : InAppViewHolder<T> {
 
     protected open var isInAppMessageActive = false
     override val isActive: Boolean
         get() = isInAppMessageActive
 
     private var positionController: InAppPositionController? = null
 
     private var _currentDialog: FrameLayout? = null
     protected val currentDialog: FrameLayout
         get() = _currentDialog!!
 
     protected val inAppLayout: InAppConstraintLayout by lazy {
         currentDialog.findViewById(R.id.inapp_layout)!!
     }
 
     private var typingView: View? = null
     private var shouldRestoreKeyboard: Boolean = false
 
     protected val preparedImages: MutableMap<ImageView, Boolean> = mutableMapOf()
 
     internal val inAppFailureTracker: InAppFailureTracker by mindboxInject { inAppFailureTracker }
 
     private var inAppActionHandler = InAppActionHandler()
     private var backRegistration: BackRegistration? = null
 
     private fun isKeyboardVisible(root: View): Boolean =
         ViewCompat.getRootWindowInsets(root)?.isVisible(WindowInsetsCompat.Type.ime()) == true
 
     protected fun hideKeyboard(currentRoot: ViewGroup) {
         typingView = currentRoot.rootView.findFocus()
         if (isKeyboardVisible(currentRoot)) {
             shouldRestoreKeyboard = true
             val context = currentRoot.context
             val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
             imm?.hideSoftInputFromWindow(
                 currentRoot.windowToken,
                 0
             )
         }
     }
 
     protected open fun onBeforeShow(currentRoot: MindboxView) {
         hideKeyboard(currentRoot.container)
     }
 
     abstract fun bind()
 
     protected open fun addUrlSource(layer: Layer.ImageLayer, inAppCallback: InAppCallback) {
         if (InAppMessageViewDisplayerImpl.isActionExecuted) return
         var redirectUrl: String
         var payload: String
         var shouldDismiss: Boolean
 
         inAppLayout.setSingleClickListener {
             val inAppData = inAppActionHandler.handle(
                 layer.action,
                 inAppActionHandler.mindboxView
             )
 
             with(inAppData) {
                 redirectUrl = this.redirectUrl
                 payload = this.payload
                 shouldDismiss = this.shouldDismiss
             }
 
             wrapper.inAppActionCallbacks.onInAppClick.onClick()
             inAppCallback.onInAppClick(
                 wrapper.inAppType.inAppId,
                 redirectUrl,
                 payload
             )
 
             if (shouldDismiss) {
                 inAppCallback.onInAppDismissed(wrapper.inAppType.inAppId)
                 mindboxLogI("In-app dismissed by click")
                 inAppController.close()
             }
 
             inAppData.onCompleted?.invoke()
 
             InAppMessageViewDisplayerImpl.isActionExecuted = true
         }
     }
 
     protected fun getImageFromCache(url: String, imageView: InAppImageView) {
         val maxDim = currentDialog.context.maxScreenDimension()
         val timeout = currentDialog.context.getString(R.string.mindbox_inapp_fetching_timeout).toInt()
         Glide
             .with(currentDialog.context)
             .load(url)
             .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
             .override(maxDim, maxDim)
             .timeout(timeout)
             .centerInside()
             .listener(buildCacheRequestListener(url, imageView))
             .into(imageView)
     }
 
     private fun buildCacheRequestListener(
         url: String,
         imageView: InAppImageView,
     ): RequestListener<Drawable> = object : RequestListener<Drawable> {
 
         override fun onLoadFailed(
             e: GlideException?,
             model: Any?,
             target: Target<Drawable>?,
             isFirstResource: Boolean,
         ): Boolean {
             runCatching {
                 inAppFailureTracker.sendPresentationFailure(
                     inAppId = wrapper.inAppType.inAppId,
                     errorDescription = "Failed to load in-app image with url = $url",
                     throwable = e
                 )
                 inAppController.close()
             }.onFailure { throwable ->
                 inAppFailureTracker.sendPresentationFailure(
                     inAppId = wrapper.inAppType.inAppId,
                     errorDescription = "Unknown error in onLoadFailed callback for url = $url",
                     throwable = throwable
                 )
             }
             return false
         }
 
         override fun onResourceReady(
             resource: Drawable?,
             model: Any?,
             target: Target<Drawable>?,
             dataSource: DataSource?,
             isFirstResource: Boolean,
         ): Boolean {
             runCatching {
                 bind()
                 preparedImages[imageView] = true
                 if (!preparedImages.values.contains(false)) {
                     this@AbstractInAppViewHolder.mindboxLogI("In-app ${wrapper.inAppType.inAppId} shown")
                     wrapper.inAppActionCallbacks.onInAppShown.onShown()
                     preparedImages.keys.forEach { it.isVisible = true }
                 }
             }.onFailure { throwable ->
                 inAppFailureTracker.sendPresentationFailure(
                     inAppId = wrapper.inAppType.inAppId,
                     errorDescription = "Unknown error in onResourceReady callback for url = $url",
                     throwable = throwable
                 )
             }
             return false
         }
     }
 
     protected open fun initView(currentRoot: ViewGroup) {
         currentRoot.removeChildById(R.id.inapp_layout_container)
         _currentDialog = LayoutInflater.from(currentRoot.context)
             .inflate(R.layout.mindbox_inapp_layout, currentRoot, false) as FrameLayout
         currentRoot.addView(currentDialog)
         inAppLayout.prepareLayoutForInApp(wrapper.inAppType)
     }
 
     protected fun bindBackAction(currentRoot: MindboxView, onBackPress: () -> Unit) {
         clearBackRegistration()
         backRegistration = currentRoot.backPressRegistrar.register(inAppLayout, onBackPress)
     }
 
     protected fun clearBackRegistration() {
         backRegistration?.unregister()
         backRegistration = null
     }
 
     private fun attachToRoot(currentRoot: ViewGroup) {
         if (_currentDialog == null) {
             initView(currentRoot)
             return
         }
         currentRoot.removeChildById(R.id.inapp_layout_container)
         _currentDialog?.parent.safeAs<ViewGroup>()?.removeView(_currentDialog)
         currentRoot.addView(currentDialog)
     }
 
     private fun startPositionController(currentRoot: ViewGroup) {
         positionController?.stop()
         positionController = null
         val isRepositioningEnabled = currentRoot.context.resources.getBoolean(R.bool.mindbox_support_inapp_on_fragment)
         positionController = isRepositioningEnabled.takeIf { it }?.run {
             InAppPositionController().apply { start(currentDialog) }
         }
     }
 
     protected fun restoreKeyboard() {
         val view: View = typingView ?: return
         val shouldShowKeyboard: Boolean = shouldRestoreKeyboard
         typingView = null
         shouldRestoreKeyboard = false
         view.post {
             view.requestFocus()
             if (shouldShowKeyboard) {
                 val imm =
                     (view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?)
                 imm?.showSoftInput(
                     view,
                     InputMethodManager.SHOW_IMPLICIT
                 )
             }
         }
     }
 
     override fun show(currentRoot: MindboxView) {
         isInAppMessageActive = true
         attachToRoot(currentRoot.container)
         startPositionController(currentRoot.container)
         onBeforeShow(currentRoot)
         inAppActionHandler.mindboxView = currentRoot
     }
 
     override fun reattach(currentRoot: MindboxView) {
         isInAppMessageActive = true
         attachToRoot(currentRoot.container)
         startPositionController(currentRoot.container)
         hideKeyboard(currentRoot.container)
         inAppActionHandler.mindboxView = currentRoot
     }
 
     override fun onClose() {
         clearBackRegistration()
         positionController?.stop()
         positionController = null
         currentDialog.parent.safeAs<ViewGroup>()?.removeView(_currentDialog)
         mindboxLogI("Close ${wrapper.inAppType.inAppId} on ${this.hashCode()}")
         restoreKeyboard()
     }
 
     override fun onStop() {
         onClose()
     }
 }