Coverage Summary for Class: InAppMessageViewDisplayerImpl (cloud.mindbox.mobile_sdk.inapp.presentation)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| InAppMessageViewDisplayerImpl |
15%
(3/20)
|
1.2%
(1/86)
|
9.9%
(12/121)
|
15.9%
(106/665)
|
| InAppMessageViewDisplayerImpl$closeInApp$1 |
0%
(0/1)
|
0%
(0/4)
|
0%
(0/6)
|
0%
(0/31)
|
| InAppMessageViewDisplayerImpl$Companion |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/3)
|
| InAppMessageViewDisplayerImpl$createMindboxView$1 |
0%
(0/3)
|
0%
(0/2)
|
0%
(0/5)
|
0%
(0/38)
|
| InAppMessageViewDisplayerImpl$createMindboxView$1$backPressRegistrar$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/3)
|
| InAppMessageViewDisplayerImpl$dismissCurrentInApp$1 |
0%
(0/1)
|
0%
(0/12)
|
0%
(0/5)
|
0%
(0/44)
|
| InAppMessageViewDisplayerImpl$inAppFailureTracker$2 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/2)
|
| InAppMessageViewDisplayerImpl$mindboxNotificationManager$2 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/2)
|
| InAppMessageViewDisplayerImpl$registerCurrentActivity$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/2)
|
| InAppMessageViewDisplayerImpl$showInAppMessage$callbackWrapper$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/3)
|
| InAppMessageViewDisplayerImpl$showInAppMessage$callbackWrapper$2 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/5)
|
| Total |
9.4%
(3/32)
|
1%
(1/104)
|
8.3%
(12/144)
|
13.3%
(106/798)
|
package cloud.mindbox.mobile_sdk.inapp.presentation
import android.app.Activity
import android.view.ViewGroup
import cloud.mindbox.mobile_sdk.addUnique
import cloud.mindbox.mobile_sdk.di.mindboxInject
import cloud.mindbox.mobile_sdk.inapp.domain.extensions.executeWithFailureTracking
import cloud.mindbox.mobile_sdk.inapp.domain.extensions.sendPresentationFailure
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.InAppActionCallbacks
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.InAppImageSizeStorage
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.presentation.callbacks.*
import cloud.mindbox.mobile_sdk.inapp.presentation.view.ActivityBackPressRegistrar
import cloud.mindbox.mobile_sdk.inapp.presentation.view.BackPressRegistrar
import cloud.mindbox.mobile_sdk.inapp.presentation.view.InAppViewHolder
import cloud.mindbox.mobile_sdk.inapp.presentation.view.ModalWindowInAppViewHolder
import cloud.mindbox.mobile_sdk.inapp.presentation.view.SnackbarInAppViewHolder
import cloud.mindbox.mobile_sdk.inapp.presentation.view.WebViewInAppViewHolder
import cloud.mindbox.mobile_sdk.logger.mindboxLogI
import cloud.mindbox.mobile_sdk.logger.mindboxLogW
import cloud.mindbox.mobile_sdk.models.operation.request.FailureReason
import cloud.mindbox.mobile_sdk.postDelayedAnimation
import cloud.mindbox.mobile_sdk.root
import cloud.mindbox.mobile_sdk.utils.MindboxUtils.Stopwatch
import cloud.mindbox.mobile_sdk.utils.loggingRunCatching
import java.util.LinkedList
internal interface MindboxView {
val container: ViewGroup
val backPressRegistrar: BackPressRegistrar
fun requestPermission()
}
internal class InAppMessageViewDisplayerImpl(
private val inAppImageSizeStorage: InAppImageSizeStorage
) :
InAppMessageViewDisplayer {
companion object {
internal var isActionExecuted: Boolean = false
}
private var currentActivity: Activity? = null
private val defaultCallback: InAppCallback = ComposableInAppCallback(
DeepLinkInAppCallback(),
CopyPayloadInAppCallback(),
LoggingInAppCallback()
)
private var inAppCallback: InAppCallback = defaultCallback
private val inAppQueue = LinkedList<InAppTypeWrapper<InAppType>>()
private var currentHolder: InAppViewHolder<*>? = null
private var pausedHolder: InAppViewHolder<*>? = null
private val mindboxNotificationManager by mindboxInject { mindboxNotificationManager }
private val inAppFailureTracker: InAppFailureTracker by mindboxInject { inAppFailureTracker }
private fun isUiPresent(): Boolean = currentActivity?.isFinishing?.not() ?: false
override fun onResumeCurrentActivity(activity: Activity, isNeedToShow: () -> Boolean, onAppResumed: () -> Unit) {
mindboxLogI("onResumeCurrentActivity: ${activity.hashCode()}")
currentActivity = activity
val holder = pausedHolder ?: currentHolder
if (holder != null) {
pausedHolder?.wrapper?.let { wrapper ->
mindboxLogI("trying to restore in-app with id ${pausedHolder?.wrapper?.inAppType?.inAppId}")
showInAppMessage(
wrapper = wrapper.copy(
inAppActionCallbacks = wrapper.inAppActionCallbacks.copy(onInAppShown = {
mindboxLogI("Skip InApp.Show for restored inApp")
currentActivity?.postDelayedAnimation {
pausedHolder?.onClose()
}
})
),
isRestored = true
)
}
} else {
tryShowInAppFromQueue(isNeedToShow)
}
onAppResumed()
}
override fun registerCurrentActivity(activity: Activity) {
mindboxLogI("registerCurrentActivity: ${activity.hashCode()}")
currentActivity = activity
tryShowInAppFromQueue { true }
}
private fun tryShowInAppFromQueue(isSessionActive: () -> Boolean) {
if (inAppQueue.isNotEmpty() && !isInAppActive() && isSessionActive()) {
inAppQueue.pop().let {
val duration = Stopwatch.track(Stopwatch.INIT_SDK)
mindboxLogI("trying to show in-app with id ${it.inAppType.inAppId} from queue $duration after init")
showInAppMessage(it)
}
}
inAppQueue.clear()
}
override fun registerInAppCallback(inAppCallback: InAppCallback) {
this.inAppCallback = inAppCallback
}
override fun unregisterInAppCallback() {
this.inAppCallback = defaultCallback
}
override fun isInAppActive(): Boolean = currentHolder?.isActive ?: false
override fun onStopCurrentActivity(activity: Activity) {
mindboxLogI("onStopCurrentActivity: ${activity.hashCode()}")
pausedHolder?.onStop()
}
override fun onPauseCurrentActivity(activity: Activity) {
mindboxLogI("onPauseCurrentActivity: ${activity.hashCode()}")
if (currentActivity == activity) {
currentActivity = null
}
val holderToPause = currentHolder ?: return
pausedHolder?.onClose()
pausedHolder = holderToPause
currentHolder = null
}
override fun tryShowInAppMessage(
inAppType: InAppType,
inAppActionCallbacks: InAppActionCallbacks,
onRenderStart: () -> Unit,
) {
val wrapper = InAppTypeWrapper(inAppType, inAppActionCallbacks, onRenderStart)
if (isUiPresent() && currentHolder == null && pausedHolder == null) {
val duration = Stopwatch.track(Stopwatch.INIT_SDK)
mindboxLogI("In-app with id ${inAppType.inAppId} is going to be shown immediately $duration after init")
showInAppMessage(wrapper)
} else {
if (currentHolder?.wrapper?.inAppType?.inAppId == wrapper.inAppType.inAppId) {
mindboxLogI(
"In-app with id ${inAppType.inAppId} is not added to showing queue as duplicate"
)
} else if (inAppQueue.addUnique(wrapper) { it.inAppType.inAppId == wrapper.inAppType.inAppId }) {
mindboxLogI(
"In-app with id ${inAppType.inAppId} is added to showing queue and will be shown later"
)
} else {
mindboxLogW(
"In-app with id ${inAppType.inAppId} already exists in showing queue!"
)
}
}
}
private fun showInAppMessage(
wrapper: InAppTypeWrapper<InAppType>,
isRestored: Boolean = false,
) {
if (!isRestored) {
wrapper.onRenderStart()
isActionExecuted = false
}
if (isRestored && tryReattachRestoredInApp(wrapper.inAppType.inAppId)) return
if (isRestored) {
pausedHolder?.onClose()
pausedHolder = null
}
val callbackWrapper = InAppCallbackWrapper({ inAppCallback }) {
wrapper.inAppActionCallbacks.onInAppDismiss.onDismiss()
}
val controller = InAppViewHolder.InAppController { closeInApp() }
@Suppress("UNCHECKED_CAST")
currentHolder = when (wrapper.inAppType) {
is InAppType.WebView -> WebViewInAppViewHolder(
wrapper = wrapper as InAppTypeWrapper<InAppType.WebView>,
controller = controller,
inAppCallback = callbackWrapper
)
is InAppType.ModalWindow -> ModalWindowInAppViewHolder(
wrapper = wrapper as InAppTypeWrapper<InAppType.ModalWindow>,
controller = controller,
inAppCallback = callbackWrapper
)
is InAppType.Snackbar -> SnackbarInAppViewHolder(
wrapper = wrapper as InAppTypeWrapper<InAppType.Snackbar>,
controller = controller,
inAppCallback = callbackWrapper,
inAppImageSizeStorage = inAppImageSizeStorage,
isFirstShow = !isRestored
)
}
currentActivity?.root?.let { root ->
inAppFailureTracker.executeWithFailureTracking(
inAppId = wrapper.inAppType.inAppId,
failureReason = FailureReason.PRESENTATION_FAILED,
errorDescription = "Error when trying draw inapp",
onFailure = ::closeInApp
) {
currentHolder?.show(createMindboxView(root))
}
} ?: run {
inAppFailureTracker.sendPresentationFailure(
inAppId = wrapper.inAppType.inAppId,
errorDescription = "currentRoot is null"
)
}
}
private fun tryReattachRestoredInApp(inAppId: String): Boolean {
val restoredHolder: InAppViewHolder<*> = pausedHolder
?.takeIf { it.canReuseOnRestore(inAppId) }
?: return false
currentHolder = restoredHolder
pausedHolder = null
val root: ViewGroup = currentActivity?.root ?: run {
inAppFailureTracker.sendPresentationFailure(
inAppId = inAppId,
errorDescription = "failed to reattach inApp: currentRoot is null"
)
return true
}
inAppFailureTracker.executeWithFailureTracking(
inAppId = inAppId,
failureReason = FailureReason.PRESENTATION_FAILED,
errorDescription = "Error when trying reattach InApp",
onFailure = ::closeInApp,
) {
restoredHolder.reattach(createMindboxView(root))
}
return true
}
private fun createMindboxView(root: ViewGroup): MindboxView =
object : MindboxView {
override val container: ViewGroup = root
override val backPressRegistrar: BackPressRegistrar =
ActivityBackPressRegistrar(activityProvider = { currentActivity })
override fun requestPermission() {
currentActivity?.let { mindboxNotificationManager.requestPermission(activity = it) }
}
}
override fun dismissCurrentInApp() {
loggingRunCatching {
if (isInAppActive()) {
currentHolder?.wrapper?.inAppActionCallbacks
?.copy(onInAppDismiss = { mindboxLogI("Do not save the closing timestamp for in-app as it's restored automatically when the session is reopened") })
?.onInAppDismiss
?.onDismiss()
}
}
closeInApp()
}
private fun closeInApp() {
loggingRunCatching {
currentHolder?.onClose()
currentHolder = null
pausedHolder?.onClose()
pausedHolder = null
inAppQueue.clear()
isActionExecuted = false
}
}
}