Skip to content

Commit 16d7b59

Browse files
authored
Merge pull request #287 from mindbox-moscow/release-2.3.0
Release 2.3.0
2 parents 1ec79f3 + 60f7118 commit 16d7b59

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3401
-512
lines changed

dependencies.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ext {
1414
volley: 'com.android.volley:volley:1.2.1',
1515
gson: 'com.google.code.gson:gson:2.8.7',
1616
room: 'androidx.room:room-runtime:2.4.1',
17+
room_ktx: 'androidx.room:room-ktx:2.4.1',
1718
room_compiler: 'androidx.room:room-compiler:2.4.1',
1819
work_manager: 'androidx.work:work-runtime-ktx:2.7.1',
1920
androidx_lifecycle: 'androidx.lifecycle:lifecycle-process:2.3.1',

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ android.enableJetifier=true
2121
kotlin.code.style=official
2222

2323
# SDK version property
24-
SDK_VERSION_NAME=2.2.3
24+
SDK_VERSION_NAME=2.3.0

sdk/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ dependencies {
5252

5353
// DB
5454
implementation sdkDependencies.room
55+
implementation sdkDependencies.room_ktx
5556
kapt sdkDependencies.room_compiler
5657

5758
//work manager

sdk/src/main/java/cloud/mindbox/mobile_sdk/Mindbox.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ object Mindbox {
400400
)
401401
}
402402

403+
403404
lifecycleManager = LifecycleManager(
404405
currentActivityName = activity?.javaClass?.name,
405406
currentIntent = activity?.intent,
@@ -440,7 +441,7 @@ object Mindbox {
440441
if (activity != null && lifecycleManager.isCurrentActivityResumed) {
441442
inAppMessageManager.registerCurrentActivity(activity)
442443
}
443-
inAppMessageManager.initInAppMessages(configuration)
444+
inAppMessageManager.initInAppMessages()
444445
mindboxScope.launch {
445446
MindboxEventManager.eventFlow.emit(MindboxEventManager.appStarted())
446447
}

sdk/src/main/java/cloud/mindbox/mobile_sdk/data/ConfigurationsDao.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import androidx.room.OnConflictStrategy
66
import androidx.room.Query
77
import cloud.mindbox.mobile_sdk.managers.DbManager.CONFIGURATION_TABLE_NAME
88
import cloud.mindbox.mobile_sdk.models.Configuration
9+
import kotlinx.coroutines.flow.Flow
910

1011
@Dao
1112
internal interface ConfigurationsDao {
@@ -16,4 +17,6 @@ internal interface ConfigurationsDao {
1617
@Query("SELECT * FROM $CONFIGURATION_TABLE_NAME ORDER BY configurationId DESC LIMIT 1")
1718
fun get(): Configuration
1819

20+
@Query("SELECT * FROM $CONFIGURATION_TABLE_NAME ORDER BY configurationId DESC LIMIT 1")
21+
fun listenConfiguration(): Flow<Configuration>
1922
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package cloud.mindbox.mobile_sdk.inapp.data
2+
3+
import android.content.Context
4+
import cloud.mindbox.mobile_sdk.inapp.domain.InAppGeoRepository
5+
import cloud.mindbox.mobile_sdk.inapp.domain.models.GeoTargeting
6+
import cloud.mindbox.mobile_sdk.inapp.mapper.InAppMessageMapper
7+
import cloud.mindbox.mobile_sdk.managers.DbManager
8+
import cloud.mindbox.mobile_sdk.managers.GatewayManager
9+
import cloud.mindbox.mobile_sdk.repository.MindboxPreferences
10+
import cloud.mindbox.mobile_sdk.utils.LoggingExceptionHandler
11+
import com.google.gson.Gson
12+
import kotlinx.coroutines.flow.first
13+
import kotlinx.coroutines.flow.map
14+
15+
internal class InAppGeoRepositoryImpl(
16+
private val context: Context,
17+
private val inAppMessageMapper: InAppMessageMapper,
18+
private val gson: Gson,
19+
) : InAppGeoRepository {
20+
override suspend fun fetchGeo() {
21+
val configuration = DbManager.listenConfigurations().first()
22+
val geoTargeting = inAppMessageMapper.mapGeoTargetingDtoToGeoTargeting(
23+
geoTargetingDto = GatewayManager.checkGeoTargeting(
24+
context = context,
25+
configuration = configuration
26+
)
27+
)
28+
MindboxPreferences.inAppGeo = gson.toJson(geoTargeting)
29+
}
30+
31+
override fun geoGeo(): GeoTargeting {
32+
return LoggingExceptionHandler.runCatching(GeoTargeting("", "", "")) {
33+
if (MindboxPreferences.inAppGeo.isNotBlank()) {
34+
gson.fromJson(MindboxPreferences.inAppGeo, GeoTargeting::class.java)
35+
} else {
36+
GeoTargeting("", "", "")
37+
}
38+
}
39+
}
40+
}

sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/InAppRepositoryImpl.kt

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ package cloud.mindbox.mobile_sdk.inapp.data
33
import android.content.Context
44
import cloud.mindbox.mobile_sdk.MindboxConfiguration
55
import cloud.mindbox.mobile_sdk.inapp.domain.InAppRepository
6+
import cloud.mindbox.mobile_sdk.inapp.domain.InAppValidator
67
import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppConfig
78
import cloud.mindbox.mobile_sdk.inapp.domain.models.SegmentationCheckInApp
89
import cloud.mindbox.mobile_sdk.inapp.mapper.InAppMessageMapper
910
import cloud.mindbox.mobile_sdk.inapp.presentation.InAppMessageManagerImpl
1011
import cloud.mindbox.mobile_sdk.logger.MindboxLoggerImpl
12+
import cloud.mindbox.mobile_sdk.managers.DbManager
1113
import cloud.mindbox.mobile_sdk.managers.GatewayManager
1214
import cloud.mindbox.mobile_sdk.managers.MindboxEventManager
1315
import cloud.mindbox.mobile_sdk.models.InAppEventType
16+
import cloud.mindbox.mobile_sdk.models.TreeTargetingDto
1417
import cloud.mindbox.mobile_sdk.models.operation.request.InAppHandleRequest
1518
import cloud.mindbox.mobile_sdk.models.operation.response.FormDto
1619
import cloud.mindbox.mobile_sdk.models.operation.response.InAppConfigResponse
@@ -20,15 +23,16 @@ import cloud.mindbox.mobile_sdk.utils.LoggingExceptionHandler
2023
import com.google.gson.Gson
2124
import com.google.gson.JsonObject
2225
import com.google.gson.reflect.TypeToken
23-
import kotlinx.coroutines.flow.Flow
24-
import kotlinx.coroutines.flow.map
26+
import kotlinx.coroutines.flow.*
2527

2628
internal class InAppRepositoryImpl(
2729
private val inAppMapper: InAppMessageMapper,
2830
private val gson: Gson,
2931
private val context: Context,
32+
private val inAppValidator: InAppValidator,
3033
) : InAppRepository {
3134

35+
3236
override fun getShownInApps(): HashSet<String> {
3337
return LoggingExceptionHandler.runCatching(HashSet()) {
3438
if (MindboxPreferences.shownInAppIds.isBlank()) {
@@ -43,30 +47,36 @@ internal class InAppRepositoryImpl(
4347

4448
override fun sendInAppShown(inAppId: String) {
4549
MindboxEventManager.inAppShown(context,
46-
IN_APP_OPERATION_VIEW_TYPE,
4750
gson.toJson(InAppHandleRequest(inAppId), InAppHandleRequest::class.java))
4851
}
4952

5053
override fun sendInAppClicked(inAppId: String) {
5154
MindboxEventManager.inAppClicked(context,
52-
IN_APP_OPERATION_CLICK_TYPE,
55+
gson.toJson(InAppHandleRequest(inAppId), InAppHandleRequest::class.java))
56+
}
57+
58+
override fun sendInAppTargetingHit(inAppId: String) {
59+
MindboxEventManager.inAppTargetingHit(context,
5360
gson.toJson(InAppHandleRequest(inAppId), InAppHandleRequest::class.java))
5461
}
5562

5663

57-
override suspend fun fetchInAppConfig(configuration: MindboxConfiguration) {
58-
MindboxPreferences.inAppConfig =
59-
GatewayManager.fetchInAppConfig(context,
60-
configuration)
64+
override suspend fun fetchInAppConfig() {
65+
val configuration = DbManager.listenConfigurations().first()
66+
MindboxPreferences.inAppConfig = GatewayManager.fetchInAppConfig(
67+
context = context,
68+
configuration = configuration
69+
)
6170
}
6271

63-
override suspend fun fetchSegmentations(
64-
configuration: MindboxConfiguration,
65-
config: InAppConfig,
66-
): SegmentationCheckInApp {
67-
return inAppMapper.mapToSegmentationCheck(
68-
GatewayManager.checkSegmentation(context,
69-
configuration, inAppMapper.mapToSegmentationCheckRequest(config)))
72+
override suspend fun fetchSegmentations(config: InAppConfig): SegmentationCheckInApp {
73+
val configuration = DbManager.listenConfigurations().first()
74+
val response = GatewayManager.checkSegmentation(
75+
context = context,
76+
configuration = configuration,
77+
segmentationCheckRequest = inAppMapper.mapToSegmentationCheckRequest(config)
78+
)
79+
return inAppMapper.mapToSegmentationCheck(response)
7080
}
7181

7282
override fun listenInAppEvents(): Flow<InAppEventType> {
@@ -87,25 +97,47 @@ internal class InAppRepositoryImpl(
8797
)
8898
val configBlank = deserializeToConfigDtoBlank(inAppConfigString)
8999
val filteredInApps = configBlank?.inApps
90-
?.filter { validateInAppVersion(it) }
100+
?.filter { inAppDtoBlank ->
101+
validateInAppVersion(inAppDtoBlank)
102+
}
91103
?.map { inAppDtoBlank ->
92104
inAppMapper.mapToInAppDto(
93105
inAppDtoBlank = inAppDtoBlank,
94106
formDto = deserializeToInAppFormDto(inAppDtoBlank.form),
107+
targetingDto = deserializeToInAppTargetingDto(inAppDtoBlank.targeting)
95108
)
109+
}?.filter { inAppDto ->
110+
inAppValidator.validateInApp(inAppDto)
96111
}
97112
val filteredConfig = InAppConfigResponse(
98113
inApps = filteredInApps
99114
)
100-
return@map inAppMapper.mapToInAppConfig(filteredConfig).also { inAppConfig ->
101-
MindboxLoggerImpl.d(
102-
parent = this@InAppRepositoryImpl,
103-
message = "Providing config: $inAppConfig"
104-
)
105-
}
115+
116+
return@map inAppMapper.mapToInAppConfig(filteredConfig)
117+
.also { inAppConfig ->
118+
MindboxLoggerImpl.d(
119+
parent = this@InAppRepositoryImpl,
120+
message = "Providing config: $inAppConfig"
121+
)
122+
}
106123
}
107124
}
108125

126+
private fun deserializeToInAppTargetingDto(inAppTreeTargeting: JsonObject?): TreeTargetingDto? {
127+
val result = runCatching {
128+
gson.fromJson(inAppTreeTargeting, TreeTargetingDto::class.java)
129+
}
130+
result.exceptionOrNull()?.let { error ->
131+
MindboxLoggerImpl.e(
132+
parent = this@InAppRepositoryImpl,
133+
message = "Failed to parse JsonObject: $inAppTreeTargeting",
134+
exception = error
135+
)
136+
}
137+
return result.getOrNull()
138+
139+
}
140+
109141
private fun deserializeToConfigDtoBlank(inAppConfig: String): InAppConfigResponseBlank? {
110142
val result = runCatching {
111143
gson.fromJson(inAppConfig, InAppConfigResponseBlank::class.java)
@@ -145,14 +177,26 @@ internal class InAppRepositoryImpl(
145177
return minVersionValid && maxVersionValid
146178
}
147179

180+
148181
companion object {
149182
const val TYPE_JSON_NAME = "\$type"
150-
private const val IN_APP_OPERATION_VIEW_TYPE = "Inapp.Show"
151-
private const val IN_APP_OPERATION_CLICK_TYPE = "Inapp.Click"
152183

153184
/**
154-
* Типы картинок
185+
* Тargeting types
186+
**/
187+
const val TRUE_JSON_NAME = "true"
188+
const val AND_JSON_NAME = "and"
189+
const val OR_JSON_NAME = "or"
190+
const val SEGMENT_JSON_NAME = "segment"
191+
const val COUNTRY_JSON_NAME = "country"
192+
const val CITY_JSON_NAME = "city"
193+
const val REGION_JSON_NAME = "region"
194+
195+
/**
196+
* In-app types
155197
**/
156198
const val SIMPLE_IMAGE_JSON_NAME = "simpleImage"
199+
200+
157201
}
158202
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package cloud.mindbox.mobile_sdk.inapp.data
2+
3+
import cloud.mindbox.mobile_sdk.inapp.domain.InAppValidator
4+
import cloud.mindbox.mobile_sdk.logger.MindboxLoggerImpl
5+
import cloud.mindbox.mobile_sdk.models.TreeTargetingDto
6+
import cloud.mindbox.mobile_sdk.models.operation.response.InAppDto
7+
import cloud.mindbox.mobile_sdk.models.operation.response.PayloadDto
8+
9+
internal class InAppValidatorImpl : InAppValidator {
10+
11+
private fun validateInAppTargeting(id: String, targeting: TreeTargetingDto?): Boolean {
12+
return when (targeting) {
13+
null -> {
14+
MindboxLoggerImpl.d(InAppRepositoryImpl, "targeting is null for in-app with $id")
15+
false
16+
}
17+
is TreeTargetingDto.UnionNodeDto -> {
18+
19+
if (targeting.nodes.isNullOrEmpty()) {
20+
MindboxLoggerImpl.d(InAppRepositoryImpl,
21+
"nodes is ${targeting.nodes.toString()} for in-app with id $id")
22+
return false
23+
}
24+
var isValid = true
25+
for (internalTargeting in targeting.nodes) {
26+
if (!validateInAppTargeting(id, internalTargeting)) {
27+
isValid = false
28+
}
29+
}
30+
isValid
31+
}
32+
is TreeTargetingDto.IntersectionNodeDto -> {
33+
if (targeting.nodes.isNullOrEmpty()) {
34+
MindboxLoggerImpl.d(InAppRepositoryImpl,
35+
"nodes is ${targeting.nodes.toString()} for in-app with id $id")
36+
return false
37+
}
38+
var isValid = true
39+
for (internalTargeting in targeting.nodes) {
40+
if (!validateInAppTargeting(id, internalTargeting)) {
41+
isValid = false
42+
}
43+
}
44+
isValid
45+
}
46+
is TreeTargetingDto.SegmentNodeDto -> {
47+
val rez = targeting.segmentExternalId != null
48+
&& targeting.segmentationInternalId != null
49+
&& (targeting.kind.equals(POSITIVE) || targeting.kind.equals(NEGATIVE))
50+
&& targeting.segmentationExternalId != null
51+
&& targeting.type != null
52+
if (!rez) {
53+
MindboxLoggerImpl.d(InAppRepositoryImpl,
54+
"some segment properties are corrupted")
55+
}
56+
rez
57+
}
58+
is TreeTargetingDto.TrueNodeDto -> {
59+
targeting.type != null
60+
}
61+
is TreeTargetingDto.CityNodeDto -> {
62+
targeting.type != null
63+
&& targeting.ids.isNullOrEmpty().not()
64+
&& (targeting.kind.equals(POSITIVE) || targeting.kind.equals(NEGATIVE))
65+
}
66+
is TreeTargetingDto.CountryNodeDto -> {
67+
targeting.type != null
68+
&& targeting.ids.isNullOrEmpty().not()
69+
&& (targeting.kind.equals(POSITIVE) || targeting.kind.equals(NEGATIVE))
70+
}
71+
is TreeTargetingDto.RegionNodeDto -> {
72+
targeting.type != null
73+
&& targeting.ids.isNullOrEmpty().not()
74+
&& (targeting.kind.equals(POSITIVE) || targeting.kind.equals(NEGATIVE))
75+
}
76+
}
77+
}
78+
79+
private fun validateFormDto(inApp: InAppDto): Boolean {
80+
if (inApp.form?.variants.isNullOrEmpty()) return false
81+
var isValid = true
82+
inApp.form?.variants?.iterator()?.forEach { payloadDto ->
83+
when {
84+
(payloadDto == null) -> {
85+
MindboxLoggerImpl.d(InAppRepositoryImpl,
86+
"payload is null for in-app with id ${inApp.id}")
87+
isValid = false
88+
}
89+
(payloadDto is PayloadDto.SimpleImage) -> {
90+
if ((payloadDto.type == null) or (payloadDto.imageUrl == null)) {
91+
MindboxLoggerImpl.d(InAppRepositoryImpl,
92+
"some properties of in-app with id ${inApp.id} are null. type: ${payloadDto.type}, imageUrl: ${payloadDto.imageUrl}")
93+
isValid = false
94+
}
95+
96+
}
97+
}
98+
}
99+
return isValid
100+
}
101+
102+
override fun validateInApp(inApp: InAppDto): Boolean {
103+
return validateInAppTargeting(inApp.id, inApp.targeting) and validateFormDto(inApp)
104+
}
105+
106+
companion object {
107+
/**
108+
* KIND VALUES
109+
* **/
110+
private const val POSITIVE = "positive"
111+
private const val NEGATIVE = "negative"
112+
}
113+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package cloud.mindbox.mobile_sdk.inapp.data.dto
2+
3+
import com.google.gson.annotations.SerializedName
4+
5+
data class GeoTargetingDto(
6+
@SerializedName("city_id")
7+
val cityId: String?,
8+
@SerializedName("region_id")
9+
val regionId: String?,
10+
@SerializedName("country_id")
11+
val countryId: String?,
12+
)

0 commit comments

Comments
 (0)