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
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ internal fun PresentationModule(
monitoringInteractor = monitoringInteractor,
sessionStorageManager = sessionStorageManager,
userVisitManager = userVisitManager,
inAppMessageDelayedManager = inAppMessageDelayedManager
inAppMessageDelayedManager = inAppMessageDelayedManager,
timeProvider = timeProvider
)
}
override val clipboardManager: ClipboardManager by lazy {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package cloud.mindbox.mobile_sdk.inapp.data.dto.deserializers

import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import java.lang.reflect.Type

internal class InAppTagsDeserializer : JsonDeserializer<Map<String, String>?> {

override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext,
): Map<String, String>? {
if (json.isJsonNull) return null
if (!json.isJsonObject) return null
return json.asJsonObject.entrySet().mapNotNull { (key, value) ->
if (value.isJsonPrimitive && value.asJsonPrimitive.isString) {
key to value.asString
} else {
null
}
}.toMap()
}

companion object {
const val TAGS = "tags"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import cloud.mindbox.mobile_sdk.fromJsonTyped
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppSerializationManager
import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppFailuresWrapper
import cloud.mindbox.mobile_sdk.models.operation.request.InAppHandleRequest
import cloud.mindbox.mobile_sdk.models.operation.request.InAppShowRequest
import cloud.mindbox.mobile_sdk.models.operation.request.InAppShowFailure
import cloud.mindbox.mobile_sdk.toJsonTyped
import cloud.mindbox.mobile_sdk.utils.LoggingExceptionHandler
Expand All @@ -13,9 +14,25 @@ import com.google.gson.reflect.TypeToken

internal class InAppSerializationManagerImpl(private val gson: Gson) : InAppSerializationManager {

override fun serializeToInAppHandledString(inAppId: String): String {
return LoggingExceptionHandler.runCatching("") {
gson.toJson(InAppHandleRequest(inAppId), InAppHandleRequest::class.java)
override fun serializeToInAppShownActionString(
inAppId: String,
timeToDisplay: String,
tags: Map<String, String>?,
): String {
return loggingRunCatching("") {
gson.toJsonTyped<InAppShowRequest>(
InAppShowRequest(
inAppId = inAppId,
timeToDisplay = timeToDisplay,
tags = tags,
)
)
}
}

override fun serializeToInAppActionString(inAppId: String): String {
return loggingRunCatching("") {
gson.toJsonTyped<InAppHandleRequest>(InAppHandleRequest(inAppId = inAppId))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ internal class InAppMapper {
sdkVersion = inApp.sdkVersion,
targeting = targetingDto,
frequency = frequencyDto,
form = formDto
form = formDto,
tags = inApp.tags,
)
}
}
Expand Down Expand Up @@ -303,7 +304,8 @@ internal class InAppMapper {
),
minVersion = inAppDto.sdkVersion?.minVersion,
maxVersion = inAppDto.sdkVersion?.maxVersion,
frequency = Frequency(getDelay(inAppDto.frequency))
frequency = Frequency(getDelay(inAppDto.frequency)),
tags = inAppDto.tags?.takeIf { it.isNotEmpty() }
)
} ?: emptyList(),
monitoring = inAppConfigResponse.monitoring?.map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ internal class InAppRepositoryImpl(
mindboxLogI("Increase count of shown inapp per day")
}

override fun sendInAppShown(inAppId: String) {
inAppSerializationManager.serializeToInAppHandledString(inAppId).apply {
override fun sendInAppShown(inAppId: String, timeToDisplay: String, tags: Map<String, String>?) {
inAppSerializationManager.serializeToInAppShownActionString(inAppId, timeToDisplay, tags).apply {
if (isNotBlank()) {
MindboxEventManager.inAppShown(
context,
Expand All @@ -110,7 +110,7 @@ internal class InAppRepositoryImpl(
}

override fun sendInAppClicked(inAppId: String) {
inAppSerializationManager.serializeToInAppHandledString(inAppId).apply {
inAppSerializationManager.serializeToInAppActionString(inAppId).apply {
if (isNotBlank()) {
MindboxEventManager.inAppClicked(
context,
Expand All @@ -121,7 +121,7 @@ internal class InAppRepositoryImpl(
}

override fun sendUserTargeted(inAppId: String) {
inAppSerializationManager.serializeToInAppHandledString(inAppId).apply {
inAppSerializationManager.serializeToInAppActionString(inAppId).apply {
if (isNotBlank()) {
MindboxEventManager.sendUserTargeted(
context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.InAppReposi
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
Expand Down Expand Up @@ -40,7 +41,7 @@ internal class InAppInteractorImpl(

private val inAppTargetingChannel = Channel<InAppEventType>(Channel.UNLIMITED)

override suspend fun processEventAndConfig(): Flow<InApp> {
override suspend fun processEventAndConfig(): Flow<Pair<InApp, Milliseconds>> {
val inApps: List<InApp> = mobileConfigRepository.getInAppsSection()
.let { inApps ->
inAppRepository.saveCurrentSessionInApps(inApps)
Expand All @@ -63,9 +64,10 @@ internal class InAppInteractorImpl(
}
return inAppRepository.listenInAppEvents()
.filter { event -> inAppEventManager.isValidInAppEvent(event) }
.onEach {
mindboxLogD("Event triggered: ${it.name}")
.onEach { event ->
mindboxLogD("Event triggered: ${event.name}")
}.map { event ->
val triggerTimeMillis = timeProvider.currentTimestamp()
val filteredInApps = inAppFilteringManager.filterUnShownInAppsByEvent(inApps, event).let {
inAppFrequencyManager.filterInAppsFrequency(it)
}
Expand All @@ -83,10 +85,10 @@ internal class InAppInteractorImpl(
inApp?.let {
sessionStorageManager.inAppTriggerEvent = event
}
inApp
inApp?.let { inapp -> inapp to timeProvider.elapsedSince(triggerTimeMillis) }
}
.onEach { inApp ->
inApp?.let { mindboxLogI("InApp ${inApp.id} isPriority=${inApp.isPriority}, delayTime=${inApp.delayTime}, skipLimitChecks=${inApp.isPriority}") }
.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()
Expand All @@ -104,9 +106,14 @@ internal class InAppInteractorImpl(
)
}

override fun saveShownInApp(id: String, timeStamp: Long) {
override fun saveShownInApp(
id: String,
timeStamp: Long,
timeToDisplay: String,
tags: Map<String, String>?
) {
inAppRepository.setInAppShown(id)
inAppRepository.sendInAppShown(id)
inAppRepository.sendInAppShown(id, timeToDisplay, tags)
inAppRepository.saveShownInApp(id, timeStamp)
inAppRepository.saveInAppStateChangeTime(timeStamp.toTimestamp())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cloud.mindbox.mobile_sdk.inapp.domain.interfaces.interactors

import cloud.mindbox.mobile_sdk.inapp.domain.models.InApp
import cloud.mindbox.mobile_sdk.models.Milliseconds
import kotlinx.coroutines.flow.Flow

internal interface InAppInteractor {
Expand All @@ -9,9 +10,14 @@ internal interface InAppInteractor {

fun setInAppShown(inAppId: String)

suspend fun processEventAndConfig(): Flow<InApp>
suspend fun processEventAndConfig(): Flow<Pair<InApp, Milliseconds>>

fun saveShownInApp(id: String, timeStamp: Long)
fun saveShownInApp(
id: String,
timeStamp: Long,
timeToDisplay: String,
tags: Map<String, String>?
)

fun sendInAppClicked(inAppId: String)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ internal interface InAppSerializationManager {

fun deserializeToShownInAppsMap(shownInApps: String): Map<String, List<Long>>

fun serializeToInAppHandledString(inAppId: String): String
fun serializeToInAppShownActionString(inAppId: String, timeToDisplay: String, tags: Map<String, String>?): String

fun serializeToInAppActionString(inAppId: String): String

fun serializeToInAppShowFailuresString(inAppShowFailures: List<InAppShowFailure>): String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ internal interface InAppRepository {

fun saveShownInApp(id: String, timeStamp: Long)

fun sendInAppShown(inAppId: String)
fun sendInAppShown(inAppId: String, timeToDisplay: String, tags: Map<String, String>?)

fun sendInAppClicked(inAppId: String)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal data class InApp(
val frequency: Frequency,
val targeting: TreeTargeting,
val form: Form,
val tags: Map<String, String>?,
)

internal data class Frequency(val delay: Delay) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.InAppActionCallbacks

internal data class InAppTypeWrapper<out T : InAppType>(
val inAppType: T,
val inAppActionCallbacks: InAppActionCallbacks
val inAppActionCallbacks: InAppActionCallbacks,
val onRenderStart: () -> Unit,
)

internal fun interface OnInAppClick {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cloud.mindbox.mobile_sdk.inapp.presentation
import cloud.mindbox.mobile_sdk.Mindbox
import cloud.mindbox.mobile_sdk.inapp.domain.models.InApp
import cloud.mindbox.mobile_sdk.logger.mindboxLogD
import cloud.mindbox.mobile_sdk.models.Milliseconds
import cloud.mindbox.mobile_sdk.logger.mindboxLogI
import cloud.mindbox.mobile_sdk.pollIf
import cloud.mindbox.mobile_sdk.utils.TimeProvider
Expand Down Expand Up @@ -33,16 +34,17 @@ internal class InAppMessageDelayedManager(private val timeProvider: TimeProvider
pendingInAppComparator
)

private val _inAppToShowFlow = MutableSharedFlow<InApp>()
private val _inAppToShowFlow = MutableSharedFlow<Pair<InApp, Milliseconds>>()
val inAppToShowFlow = _inAppToShowFlow.asSharedFlow()

private data class PendingInApp(
val inApp: InApp,
val showTimeMillis: Long,
val sequenceNumber: Long
val sequenceNumber: Long,
val preparedTimeMs: Milliseconds,
)

internal fun process(inApp: InApp) {
internal fun process(inApp: InApp, preparedTimeMs: Milliseconds) {
coroutineScope.launchWithLock(processingMutex) {
mindboxLogD("Processing In-App: ${inApp.id}, Priority: ${inApp.isPriority}, Delay: ${inApp.delayTime}")
val delay = inApp.delayTime?.interval ?: 0L
Expand All @@ -52,7 +54,8 @@ internal class InAppMessageDelayedManager(private val timeProvider: TimeProvider
PendingInApp(
inApp = inApp,
showTimeMillis = showTime,
sequenceNumber = sequenceNumber.getAndIncrement()
sequenceNumber = sequenceNumber.getAndIncrement(),
preparedTimeMs = preparedTimeMs,
)
)
processQueue()
Expand All @@ -73,7 +76,7 @@ internal class InAppMessageDelayedManager(private val timeProvider: TimeProvider

pendingInApps.pollIf { it.showTimeMillis <= now }?.let { showCandidate ->
mindboxLogI("Winner found: ${showCandidate.inApp.id}. Emitting to show.")
_inAppToShowFlow.emit(showCandidate.inApp)
_inAppToShowFlow.emit(showCandidate.inApp to showCandidate.preparedTimeMs)

do {
val inApp = pendingInApps.pollIf { it.showTimeMillis <= now }.also { discarded ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ import cloud.mindbox.mobile_sdk.Mindbox
import cloud.mindbox.mobile_sdk.inapp.data.managers.SessionStorageManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.interactors.InAppInteractor
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.InAppActionCallbacks
import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppType
import cloud.mindbox.mobile_sdk.inapp.domain.models.OnInAppClick
import cloud.mindbox.mobile_sdk.inapp.domain.models.OnInAppDismiss
import cloud.mindbox.mobile_sdk.inapp.domain.models.OnInAppShown
import cloud.mindbox.mobile_sdk.logger.MindboxLoggerImpl
import cloud.mindbox.mobile_sdk.logger.mindboxLogI
import cloud.mindbox.mobile_sdk.millisToTimeSpan
import cloud.mindbox.mobile_sdk.models.Milliseconds
import cloud.mindbox.mobile_sdk.models.Timestamp
import cloud.mindbox.mobile_sdk.managers.MindboxEventManager
import cloud.mindbox.mobile_sdk.managers.UserVisitManager
import cloud.mindbox.mobile_sdk.monitoring.domain.interfaces.MonitoringInteractor
import cloud.mindbox.mobile_sdk.repository.MindboxPreferences
import cloud.mindbox.mobile_sdk.utils.LoggingExceptionHandler
import cloud.mindbox.mobile_sdk.utils.TimeProvider
import com.android.volley.VolleyError
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
Expand All @@ -28,7 +33,8 @@ internal class InAppMessageManagerImpl(
private val monitoringInteractor: MonitoringInteractor,
private val sessionStorageManager: SessionStorageManager,
private val userVisitManager: UserVisitManager,
private val inAppMessageDelayedManager: InAppMessageDelayedManager
private val inAppMessageDelayedManager: InAppMessageDelayedManager,
private val timeProvider: TimeProvider
) : InAppMessageManager {

init {
Expand Down Expand Up @@ -65,15 +71,15 @@ internal class InAppMessageManagerImpl(

private suspend fun handleInAppFromInteractor() {
inAppInteractor.processEventAndConfig()
.onEach { inApp ->
.onEach { (inApp, preparedTimeMs) ->
mindboxLogI("Got in-app from interactor: ${inApp.id}. Processing with DelayedManager.")
inAppMessageDelayedManager.process(inApp)
inAppMessageDelayedManager.process(inApp, preparedTimeMs)
}
.collect()
}

private suspend fun handleInAppFromDelayedManager() {
inAppMessageDelayedManager.inAppToShowFlow.collect { inApp ->
inAppMessageDelayedManager.inAppToShowFlow.collect { (inApp, preparedTimeMs) ->
mindboxLogI("Got in-app from DelayedManager: ${inApp.id}")
withContext(Dispatchers.Main) {
if (inAppMessageViewDisplayer.isInAppActive()) {
Expand All @@ -92,14 +98,18 @@ internal class InAppMessageManagerImpl(
return@withContext
}

var renderStartTime = Timestamp(0L)
val tags = inApp.tags?.takeIf { it.isNotEmpty() }

inAppMessageViewDisplayer.tryShowInAppMessage(
inAppType = inAppMessage,
onRenderStart = { renderStartTime = timeProvider.currentTimestamp() },
inAppActionCallbacks = object : InAppActionCallbacks {
override val onInAppClick = OnInAppClick {
inAppInteractor.sendInAppClicked(inAppMessage.inAppId)
}
override val onInAppShown = OnInAppShown {
inAppInteractor.saveShownInApp(inAppMessage.inAppId, System.currentTimeMillis())
handleInAppShown(renderStartTime, preparedTimeMs, inAppMessage, tags)
}
override val onInAppDismiss = OnInAppDismiss {
inAppInteractor.saveInAppDismissTime()
Expand Down Expand Up @@ -194,6 +204,19 @@ internal class InAppMessageManagerImpl(
}
}

private fun handleInAppShown(
renderStartTime: Timestamp,
preparedTimeMs: Milliseconds,
inAppMessage: InAppType,
tags: Map<String, String>?
) {
val shownTime = timeProvider.currentTimestamp()
val renderTime = shownTime - renderStartTime
mindboxLogI("Render time is ${renderTime.ms}ms, prepared time is ${preparedTimeMs.interval}ms")
val timeToDisplay = (preparedTimeMs.interval + renderTime.ms).millisToTimeSpan()
inAppInteractor.saveShownInApp(inAppMessage.inAppId, shownTime.ms, timeToDisplay, tags)
}

companion object {
const val CONFIG_NOT_FOUND = 404
}
Expand Down
Loading