Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions sdk/src/main/java/cloud/mindbox/mobile_sdk/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ internal val Int.dp: Int
internal val Int.px: Int
get() = (this * Resources.getSystem().displayMetrics.density).roundToInt()

internal fun Context.maxScreenDimension(): Int {
val displayMetrics = resources.displayMetrics
return maxOf(displayMetrics.widthPixels, displayMetrics.heightPixels)
}

internal fun Animation.setOnAnimationEnd(runnable: Runnable) {
setAnimationListener(object : AnimationListener {
override fun onAnimationStart(animation: Animation?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@ 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.mindboxLogD
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

Expand All @@ -23,56 +29,78 @@ internal class InAppGlideImageLoaderImpl(
private val inAppImageSizeStorage: InAppImageSizeStorage
) : InAppImageLoader {

private val requests = HashMap<String, Target<Drawable>>()
private val requests = ConcurrentHashMap<String, Target<Drawable>>()

override suspend fun loadImage(inAppId: String, url: String): Boolean {
mindboxLogD("loading image for inapp with id $inAppId started")
return suspendCancellableCoroutine { cancellableContinuation ->
val target = Glide.with(context).load(url)
.timeout(context.getString(R.string.mindbox_inapp_fetching_timeout).toInt())
.listener(object :
RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return runCatching {
mindboxLogD("loading image with url = $url for inapp with id $inAppId failed")
cancellableContinuation.resumeWithException(InAppContentFetchingError(e))
true
}.getOrElse {
mindboxLogE(
"Unknown error when loading image from network failed",
exception = it
)
true
}
}
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) {
suspendCancellableCoroutine { continuation ->
requests[inAppId] = startPreload(inAppId, url, maxDim, timeoutMs.toInt(), continuation)
continuation.invokeOnCancellation { cancelLoading(inAppId) }
}
Comment thread
enotniy marked this conversation as resolved.
}
} catch (e: TimeoutCancellationException) {
mindboxLogE("Image loading timed out after ${timeoutMs}ms for inapp $inAppId", e)
throw InAppContentFetchingError(null)
Comment thread
enotniy marked this conversation as resolved.
}
}

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 {
return runCatching {
mindboxLogD("loading image with url = $url for inapp with id $inAppId succeeded")
inAppImageSizeStorage.addSize(inAppId, url, resource.toBitmap().width, resource.toBitmap().height)
cancellableContinuation.resume(true)
true
}.getOrElse {
mindboxLogE(
"Unknown error when loading image from network failed",
exception = it
)
true
}
}
}).preload()
requests[inAppId] = target
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
Comment thread
enotniy marked this conversation as resolved.
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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
Expand All @@ -22,6 +23,7 @@ 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
Expand Down Expand Up @@ -122,64 +124,71 @@ internal abstract class AbstractInAppViewHolder<T : InAppType>(
}

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.ALL)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return runCatching {
inAppFailureTracker.sendPresentationFailure(
inAppId = wrapper.inAppType.inAppId,
errorDescription = "Failed to load in-app image with url = $url",
throwable = e
)
inAppController.close()
false
}.getOrElse { throwable ->
inAppFailureTracker.sendPresentationFailure(
inAppId = wrapper.inAppType.inAppId,
errorDescription = "Unknown error after loading image from cache succeeded",
throwable = throwable
)
false
}
}
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.override(maxDim, maxDim)
.timeout(timeout)
.centerInside()
.listener(buildCacheRequestListener(url, imageView))
.into(imageView)
}

override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
return runCatching {
bind()
preparedImages[imageView] = true
if (!preparedImages.values.contains(false)) {
this@AbstractInAppViewHolder.mindboxLogI("In-app shown")
wrapper.inAppActionCallbacks.onInAppShown.onShown()
for (image in preparedImages.keys) {
image.visibility = View.VISIBLE
}
}
false
}.getOrElse { throwable ->
inAppFailureTracker.sendPresentationFailure(
inAppId = wrapper.inAppType.inAppId,
errorDescription = "Unknown error in onResourceReady callback",
throwable = throwable
)
false
}
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)) {
mindboxLogI("In-app ${wrapper.inAppType.inAppId} shown")
wrapper.inAppActionCallbacks.onInAppShown.onShown()
preparedImages.keys.forEach { it.isVisible = true }
}
})
.into(imageView)
}.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) {
Expand Down
Loading
Loading