Coverage Summary for Class: InAppGlideImageLoaderImpl (cloud.mindbox.mobile_sdk.inapp.data.managers)

Class Method, % Branch, % Line, % Instruction, %
InAppGlideImageLoaderImpl 100% (5/5) 100% (21/21) 100% (136/136)
InAppGlideImageLoaderImpl$buildRequestListener$1 100% (3/3) 66.7% (4/6) 100% (17/17) 99.4% (157/158)
InAppGlideImageLoaderImpl$loadImage$1
InAppGlideImageLoaderImpl$loadImage$2 100% (1/1) 100% (4/4) 100% (40/40)
InAppGlideImageLoaderImpl$loadImage$2$1$1 100% (1/1) 100% (1/1) 100% (5/5)
Total 100% (10/10) 66.7% (4/6) 100% (43/43) 99.7% (338/339)


 package cloud.mindbox.mobile_sdk.inapp.data.managers
 
 import android.content.Context
 import android.graphics.drawable.Drawable
 import androidx.core.graphics.drawable.toBitmap
 import cloud.mindbox.mobile_sdk.R
 import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.InAppImageLoader
 import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.InAppImageSizeStorage
 import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppContentFetchingError
 import cloud.mindbox.mobile_sdk.logger.mindboxLogE
 import cloud.mindbox.mobile_sdk.logger.mindboxLogI
 import cloud.mindbox.mobile_sdk.maxScreenDimension
 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
 import java.util.concurrent.ConcurrentHashMap
 import kotlinx.coroutines.CancellableContinuation
 import kotlinx.coroutines.TimeoutCancellationException
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withTimeout
 import kotlin.coroutines.resume
 import kotlin.coroutines.resumeWithException
 import kotlin.time.Duration.Companion.milliseconds
 
 internal class InAppGlideImageLoaderImpl(
     private val context: Context,
     private val inAppImageSizeStorage: InAppImageSizeStorage
 ) : InAppImageLoader {
 
     private val requests = ConcurrentHashMap<String, Target<Drawable>>()
 
     override suspend fun loadImage(inAppId: String, url: String): Boolean {
         mindboxLogI("Loading image for inapp with id $inAppId started")
         val timeoutMs = context.getString(R.string.mindbox_inapp_fetching_timeout).toLong()
         val maxDim = context.maxScreenDimension()
         return try {
             withTimeout(timeoutMs.milliseconds) {
                 suspendCancellableCoroutine { continuation ->
                     requests[inAppId] = startPreload(inAppId, url, maxDim, timeoutMs.toInt(), continuation)
                     continuation.invokeOnCancellation { cancelLoading(inAppId) }
                 }
             }
         } catch (e: TimeoutCancellationException) {
             mindboxLogE("Image loading timed out after ${timeoutMs}ms for inapp $inAppId", e)
             throw InAppContentFetchingError(e)
         }
     }
 
     private fun startPreload(
         inAppId: String,
         url: String,
         maxDim: Int,
         timeoutMs: Int,
         continuation: CancellableContinuation<Boolean>,
     ): Target<Drawable> = Glide.with(context)
         .load(url)
         .timeout(timeoutMs)
         .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
         .override(maxDim, maxDim)
         .centerInside()
         .listener(buildRequestListener(inAppId, url, continuation))
         .preload(maxDim, maxDim)
 
     private fun buildRequestListener(
         inAppId: String,
         url: String,
         continuation: CancellableContinuation<Boolean>,
     ): RequestListener<Drawable> = object : RequestListener<Drawable> {
 
         override fun onLoadFailed(
             e: GlideException?,
             model: Any?,
             target: Target<Drawable>?,
             isFirstResource: Boolean,
         ): Boolean {
             mindboxLogI("Image loading failed for inapp $inAppId, url = $url")
             requests.remove(inAppId)
             if (continuation.isActive) {
                 continuation.resumeWithException(InAppContentFetchingError(e))
             }
             return true
         }
 
         override fun onResourceReady(
             resource: Drawable,
             model: Any?,
             target: Target<Drawable>?,
             dataSource: DataSource?,
             isFirstResource: Boolean,
         ): Boolean {
             mindboxLogI("Image loading succeeded for inapp $inAppId, url = $url")
             if (!continuation.isActive) return true
             requests.remove(inAppId)
             return runCatching {
                 val bitmap = resource.toBitmap()
                 inAppImageSizeStorage.addSize(inAppId, url, bitmap.width, bitmap.height)
                 continuation.resume(true)
             }.onFailure { e ->
                 mindboxLogE("Failed to process loaded image for inapp $inAppId", e)
                 continuation.resumeWithException(InAppContentFetchingError(null))
             }.isSuccess
         }
     }
 
     override fun cancelLoading(inAppId: String) {
         Glide.with(context).clear(requests[inAppId])
         requests.remove(inAppId)
     }
 }