Coverage Summary for Class: InAppInteractorImpl (cloud.mindbox.mobile_sdk.inapp.domain)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| InAppInteractorImpl |
33.3%
(4/12)
|
72.2%
(13/18)
|
74.4%
(58/78)
|
67.3%
(290/431)
|
| InAppInteractorImpl$listenToTargetingEvents$1 |
|
| InAppInteractorImpl$listenToTargetingEvents$2 |
0%
(0/1)
|
0%
(0/8)
|
0%
(0/5)
|
0%
(0/69)
|
| InAppInteractorImpl$listenToTargetingEvents$2$emit$1 |
|
| InAppInteractorImpl$processEventAndConfig$$inlined$filter$1 |
0%
(0/2)
|
|
| InAppInteractorImpl$processEventAndConfig$$inlined$filter$1$2 |
0%
(0/1)
|
|
| InAppInteractorImpl$processEventAndConfig$$inlined$filter$1$2$1 |
|
| InAppInteractorImpl$processEventAndConfig$$inlined$map$1 |
0%
(0/2)
|
|
| InAppInteractorImpl$processEventAndConfig$$inlined$map$1$2 |
0%
(0/1)
|
|
| InAppInteractorImpl$processEventAndConfig$$inlined$map$1$2$1 |
|
| InAppInteractorImpl$processEventAndConfig$1 |
|
| InAppInteractorImpl$processEventAndConfig$3 |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(6/6)
|
| InAppInteractorImpl$processEventAndConfig$5 |
100%
(1/1)
|
50%
(2/4)
|
50%
(1/2)
|
82.9%
(34/41)
|
| Total |
28.6%
(6/21)
|
50%
(15/30)
|
69.8%
(60/86)
|
60.3%
(330/547)
|
package cloud.mindbox.mobile_sdk.inapp.domain
import cloud.mindbox.mobile_sdk.InitializeLock
import cloud.mindbox.mobile_sdk.abtests.InAppABTestLogic
import cloud.mindbox.mobile_sdk.inapp.data.managers.SessionStorageManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.checkers.Checker
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.interactors.InAppInteractor
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppEventManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppFilteringManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppFrequencyManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppProcessingManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.InAppRepository
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.MobileConfigRepository
import cloud.mindbox.mobile_sdk.inapp.domain.models.InApp
import cloud.mindbox.mobile_sdk.logger.MindboxLog
import cloud.mindbox.mobile_sdk.models.Milliseconds
import cloud.mindbox.mobile_sdk.logger.mindboxLogD
import cloud.mindbox.mobile_sdk.logger.mindboxLogI
import cloud.mindbox.mobile_sdk.models.InAppEventType
import cloud.mindbox.mobile_sdk.models.toTimestamp
import cloud.mindbox.mobile_sdk.sortByPriority
import cloud.mindbox.mobile_sdk.utils.TimeProvider
import cloud.mindbox.mobile_sdk.utils.allAllow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
internal class InAppInteractorImpl(
private val mobileConfigRepository: MobileConfigRepository,
private val inAppRepository: InAppRepository,
private val inAppFilteringManager: InAppFilteringManager,
private val inAppEventManager: InAppEventManager,
private val inAppProcessingManager: InAppProcessingManager,
private val inAppABTestLogic: InAppABTestLogic,
private val inAppFrequencyManager: InAppFrequencyManager,
private val maxInappsPerSessionLimitChecker: Checker,
private val maxInappsPerDayLimitChecker: Checker,
private val minIntervalBetweenShowsLimitChecker: Checker,
private val timeProvider: TimeProvider,
private val sessionStorageManager: SessionStorageManager
) : InAppInteractor, MindboxLog {
private val inAppTargetingChannel = Channel<InAppEventType>(Channel.UNLIMITED)
override suspend fun processEventAndConfig(): Flow<Pair<InApp, Milliseconds>> {
val inApps: List<InApp> = mobileConfigRepository.getInAppsSection()
.let { inApps ->
inAppRepository.saveCurrentSessionInApps(inApps)
for (inApp in inApps) {
for (operation in inApp.targeting.getOperationsSet()) {
inAppRepository.saveOperationalInApp(operation.lowercase(), inApp)
}
}
val inAppIds = inAppABTestLogic.getInAppsPool(inApps.map { it.id })
inAppFilteringManager.filterABTestsInApps(inApps, inAppIds).also { filteredInApps ->
logI("InApps after abtest logic ${filteredInApps.map { it.id }}")
}
}.also { unShownInApps ->
logI("Filtered config has ${unShownInApps.size} inapps")
for (inApp in unShownInApps) {
for (operation in inApp.targeting.getOperationsSet()) {
inAppRepository.saveUnShownOperationalInApp(operation.lowercase(), inApp)
}
}
}
return inAppRepository.listenInAppEvents()
.filter { event -> inAppEventManager.isValidInAppEvent(event) }
.onEach { event ->
mindboxLogD("Event triggered: ${event.name}")
}.map { event ->
val triggerTimeMillis = timeProvider.currentTimestamp()
val filteredInApps = inAppFilteringManager.filterUnShownInAppsByEvent(inApps, event).let {
inAppFrequencyManager.filterInAppsFrequency(it)
}
mindboxLogI("Event: ${event.name} combined with $filteredInApps")
val prioritySortedInApps = filteredInApps.sortByPriority()
val inApp: InApp? = inAppProcessingManager.chooseInAppToShow(
prioritySortedInApps,
event
).also {
inAppTargetingChannel.send(event)
if (event == InAppEventType.AppStartup) {
InitializeLock.complete(InitializeLock.State.APP_STARTED)
}
}
inApp?.let {
sessionStorageManager.inAppTriggerEvent = event
}
inApp?.let { inapp -> inapp to timeProvider.elapsedSince(triggerTimeMillis) }
}
.onEach { pair ->
pair?.let { (inApp, preparedTime) -> mindboxLogI("InApp ${inApp.id} isPriority=${inApp.isPriority}, delayTime=${inApp.delayTime}, skipLimitChecks=${inApp.isPriority}, preparedTime = ${preparedTime.interval} ms") }
?: mindboxLogI("No inapps to show found")
}
.filterNotNull()
}
override fun areShowAndFrequencyLimitsAllowed(inApp: InApp): Boolean {
val isAllowedByFrequency = inAppFrequencyManager.filterInAppsFrequency(listOf(inApp)).isNotEmpty()
if (!isAllowedByFrequency) {
return false
}
return inApp.isPriority || allAllow(
maxInappsPerSessionLimitChecker,
maxInappsPerDayLimitChecker,
minIntervalBetweenShowsLimitChecker
)
}
override fun saveShownInApp(
id: String,
timeStamp: Long,
timeToDisplay: String,
tags: Map<String, String>?
) {
inAppRepository.setInAppShown(id)
inAppRepository.sendInAppShown(id, timeToDisplay, tags)
inAppRepository.saveShownInApp(id, timeStamp)
inAppRepository.saveInAppStateChangeTime(timeStamp.toTimestamp())
}
override fun sendInAppClicked(inAppId: String) {
inAppRepository.sendInAppClicked(inAppId)
}
override suspend fun listenToTargetingEvents() {
val inApps = mobileConfigRepository.getInAppsSection()
val inAppsMap = inAppRepository.getTargetedInApps()
logI("Whole InApp list = $inApps")
logI("InApps that has already sent targeting ${inAppsMap.entries}")
inAppTargetingChannel.receiveAsFlow().collect { event ->
val filteredInApps = inAppFilteringManager.filterInAppsByEvent(inApps, event)
logI("inapps for event $event are = $filteredInApps")
for (inApp in filteredInApps) {
if (inAppsMap[inApp.id]?.contains(event.hashCode()) != true) {
inAppProcessingManager.sendTargetedInApp(inApp, event)
}
}
}
}
override fun setInAppShown(inAppId: String) {
inAppRepository.setInAppShown(inAppId)
}
override suspend fun fetchMobileConfig() {
mobileConfigRepository.fetchMobileConfig()
}
override fun resetInAppConfigAndEvents() {
mobileConfigRepository.resetCurrentConfig()
inAppRepository.clearInAppEvents()
}
override fun isTimeDelayInapp(inAppId: String): Boolean {
return inAppRepository.isTimeDelayInapp(inAppId)
}
override fun saveInAppDismissTime() {
val timeStamp = timeProvider.currentTimestamp()
mindboxLogI("Last in-app display duration ${(timeStamp - inAppRepository.getLastInappDismissTime()).ms} ms")
inAppRepository.saveInAppStateChangeTime(timeStamp = timeStamp)
}
}