diff --git a/messaginginapp/api/messaginginapp.api b/messaginginapp/api/messaginginapp.api index 7847bdf2b..d94b03e3c 100644 --- a/messaginginapp/api/messaginginapp.api +++ b/messaginginapp/api/messaginginapp.api @@ -1,11 +1,14 @@ public final class io/customer/messaginginapp/MessagingInAppModuleConfig : io/customer/sdk/module/CustomerIOModuleConfig { public static final field Companion Lio/customer/messaginginapp/MessagingInAppModuleConfig$Companion; + public synthetic fun (Lio/customer/messaginginapp/type/InAppEventListener;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getEventListener ()Lio/customer/messaginginapp/type/InAppEventListener; } public final class io/customer/messaginginapp/MessagingInAppModuleConfig$Builder : io/customer/sdk/module/CustomerIOModuleConfig$Builder { public fun ()V public fun build ()Lio/customer/messaginginapp/MessagingInAppModuleConfig; public synthetic fun build ()Lio/customer/sdk/module/CustomerIOModuleConfig; + public final fun setEventListener (Lio/customer/messaginginapp/type/InAppEventListener;)Lio/customer/messaginginapp/MessagingInAppModuleConfig$Builder; } public final class io/customer/messaginginapp/MessagingInAppModuleConfig$Companion { @@ -21,3 +24,31 @@ public final class io/customer/messaginginapp/ModuleMessagingInApp : io/customer public fun initialize ()V } +public abstract interface class io/customer/messaginginapp/type/InAppEventListener { + public abstract fun errorWithMessage (Lio/customer/messaginginapp/type/InAppMessage;)V + public abstract fun messageActionTaken (Lio/customer/messaginginapp/type/InAppMessage;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public abstract fun messageDismissed (Lio/customer/messaginginapp/type/InAppMessage;)V + public abstract fun messageShown (Lio/customer/messaginginapp/type/InAppMessage;)V +} + +public final class io/customer/messaginginapp/type/InAppMessage { + public static final field Companion Lio/customer/messaginginapp/type/InAppMessage$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/customer/messaginginapp/type/InAppMessage; + public static synthetic fun copy$default (Lio/customer/messaginginapp/type/InAppMessage;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/customer/messaginginapp/type/InAppMessage; + public fun equals (Ljava/lang/Object;)Z + public final fun getDeliveryId ()Ljava/lang/String; + public final fun getMessageId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/customer/messaginginapp/type/InAppMessage$Companion { +} + +public final class io/customer/messaginginapp/type/InAppMessageKt { + public static final fun getMessage (Lio/customer/messaginginapp/type/InAppMessage;)Lbuild/gist/data/model/Message; +} + diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/MessagingInAppModuleConfig.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/MessagingInAppModuleConfig.kt index 75ba624ef..0c1aa610c 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/MessagingInAppModuleConfig.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/MessagingInAppModuleConfig.kt @@ -1,15 +1,27 @@ package io.customer.messaginginapp +import io.customer.messaginginapp.type.InAppEventListener import io.customer.sdk.module.CustomerIOModuleConfig /** * In app messaging module configurations that can be used to customize app * experience based on the provided configurations */ -class MessagingInAppModuleConfig private constructor() : CustomerIOModuleConfig { +class MessagingInAppModuleConfig private constructor( + val eventListener: InAppEventListener? +) : CustomerIOModuleConfig { class Builder : CustomerIOModuleConfig.Builder { + private var eventListener: InAppEventListener? = null + + fun setEventListener(eventListener: InAppEventListener): Builder { + this.eventListener = eventListener + return this + } + override fun build(): MessagingInAppModuleConfig { - return MessagingInAppModuleConfig() + return MessagingInAppModuleConfig( + eventListener = eventListener + ) } } diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/ModuleMessagingInApp.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/ModuleMessagingInApp.kt index 660292b5a..dc15c06fe 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/ModuleMessagingInApp.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/ModuleMessagingInApp.kt @@ -45,9 +45,16 @@ class ModuleMessagingInApp internal constructor( override fun initialize() { initializeGist(organizationId) setupHooks() + configureSdkModule(moduleConfig) setupGistCallbacks() } + private fun configureSdkModule(moduleConfig: MessagingInAppModuleConfig) { + moduleConfig.eventListener?.let { eventListener -> + gistProvider.setListener(eventListener) + } + } + private fun setupGistCallbacks() { gistProvider.subscribeToEvents( onMessageShown = { deliveryID -> diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/provider/GistApiProvider.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/provider/GistApiProvider.kt index 8b711a7a3..7a67d3932 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/provider/GistApiProvider.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/provider/GistApiProvider.kt @@ -14,6 +14,7 @@ internal interface GistApi { fun setUserToken(userToken: String) fun setCurrentRoute(route: String) fun clearUserToken() + fun addListener(listener: GistListener) fun subscribeToEvents( onMessageShown: (deliveryId: String) -> Unit, onAction: (deliveryId: String?, currentRoute: String, action: String, name: String) -> Unit, @@ -41,6 +42,10 @@ internal class GistApiProvider : GistApi { GistSdk.clearUserToken() } + override fun addListener(listener: GistListener) { + GistSdk.addListener(listener) + } + override fun subscribeToEvents( onMessageShown: (String) -> Unit, onAction: (deliveryId: String?, currentRoute: String, action: String, name: String) -> Unit, diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/provider/GistInAppMessagesProvider.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/provider/GistInAppMessagesProvider.kt index 4e4b7172a..1eea8ee49 100644 --- a/messaginginapp/src/main/java/io/customer/messaginginapp/provider/GistInAppMessagesProvider.kt +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/provider/GistInAppMessagesProvider.kt @@ -1,12 +1,17 @@ package io.customer.messaginginapp.provider import android.app.Application +import build.gist.data.model.Message +import build.gist.presentation.GistListener +import io.customer.messaginginapp.type.InAppEventListener +import io.customer.messaginginapp.type.InAppMessage internal interface InAppMessagesProvider { fun initProvider(application: Application, organizationId: String) fun setUserToken(userToken: String) fun setCurrentRoute(route: String) fun clearUserToken() + fun setListener(listener: InAppEventListener) fun subscribeToEvents( onMessageShown: (deliveryId: String) -> Unit, onAction: (deliveryId: String, currentRoute: String, action: String, name: String) -> Unit, @@ -18,7 +23,13 @@ internal interface InAppMessagesProvider { * Wrapper around Gist SDK */ internal class GistInAppMessagesProvider(private val provider: GistApi) : - InAppMessagesProvider { + InAppMessagesProvider, GistListener { + + private var listener: InAppEventListener? = null + + init { + provider.addListener(this) + } override fun initProvider(application: Application, organizationId: String) { provider.initProvider(application, organizationId) @@ -36,6 +47,10 @@ internal class GistInAppMessagesProvider(private val provider: GistApi) : provider.clearUserToken() } + override fun setListener(listener: InAppEventListener) { + this.listener = listener + } + override fun subscribeToEvents( onMessageShown: (String) -> Unit, onAction: (deliveryId: String, currentRoute: String, action: String, name: String) -> Unit, @@ -55,4 +70,22 @@ internal class GistInAppMessagesProvider(private val provider: GistApi) : } ) } + + override fun embedMessage(message: Message, elementId: String) {} + + override fun onAction(message: Message, currentRoute: String, action: String, name: String) { + listener?.messageActionTaken(InAppMessage.getFromGistMessage(message), currentRoute = currentRoute, action = action, name = name) + } + + override fun onError(message: Message) { + listener?.errorWithMessage(InAppMessage.getFromGistMessage(message)) + } + + override fun onMessageDismissed(message: Message) { + listener?.messageDismissed(InAppMessage.getFromGistMessage(message)) + } + + override fun onMessageShown(message: Message) { + listener?.messageShown(InAppMessage.getFromGistMessage(message)) + } } diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/type/InAppEventListener.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/type/InAppEventListener.kt new file mode 100644 index 000000000..30dd8c296 --- /dev/null +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/type/InAppEventListener.kt @@ -0,0 +1,8 @@ +package io.customer.messaginginapp.type + +interface InAppEventListener { + fun messageShown(message: InAppMessage) + fun messageDismissed(message: InAppMessage) + fun errorWithMessage(message: InAppMessage) + fun messageActionTaken(message: InAppMessage, currentRoute: String, action: String, name: String) +} diff --git a/messaginginapp/src/main/java/io/customer/messaginginapp/type/InAppMessage.kt b/messaginginapp/src/main/java/io/customer/messaginginapp/type/InAppMessage.kt new file mode 100644 index 000000000..162159460 --- /dev/null +++ b/messaginginapp/src/main/java/io/customer/messaginginapp/type/InAppMessage.kt @@ -0,0 +1,33 @@ +package io.customer.messaginginapp.type + +import build.gist.data.model.GistMessageProperties +import build.gist.data.model.Message + +data class InAppMessage( + val messageId: String, + val deliveryId: String? // (Currently taken from Gist's campaignId property). Can be null when sending test in-app messages +) { + companion object { + internal fun getFromGistMessage(gistMessage: Message): InAppMessage { + val gistProperties = GistMessageProperties.getGistProperties(gistMessage) + val campaignId = gistProperties.campaignId + + return InAppMessage( + messageId = gistMessage.messageId, + deliveryId = campaignId + ) + } + } +} + +fun InAppMessage.getMessage(): Message = Message( + messageId = this.messageId, + properties = mapOf( + Pair( + "gist", + mapOf( + Pair("campaignId", this.deliveryId) + ) + ) + ) +) diff --git a/messaginginapp/src/sharedTest/java/io/customer/messaginginapp/InAppMessagesProviderTest.kt b/messaginginapp/src/sharedTest/java/io/customer/messaginginapp/InAppMessagesProviderTest.kt index c0119f16f..eaeac3712 100644 --- a/messaginginapp/src/sharedTest/java/io/customer/messaginginapp/InAppMessagesProviderTest.kt +++ b/messaginginapp/src/sharedTest/java/io/customer/messaginginapp/InAppMessagesProviderTest.kt @@ -1,10 +1,14 @@ package io.customer.messaginginapp import androidx.test.ext.junit.runners.AndroidJUnit4 +import build.gist.data.model.Message import io.customer.commontest.BaseTest import io.customer.messaginginapp.provider.GistApi import io.customer.messaginginapp.provider.GistInAppMessagesProvider -import io.customer.messaginginapp.provider.InAppMessagesProvider +import io.customer.messaginginapp.testutils.extension.getNewRandom +import io.customer.messaginginapp.type.InAppEventListener +import io.customer.messaginginapp.type.InAppMessage +import io.customer.sdk.extensions.random import org.amshove.kluent.internal.assertEquals import org.amshove.kluent.shouldBeEqualTo import org.junit.Before @@ -12,13 +16,16 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) internal class InAppMessagesProviderTest : BaseTest() { - private lateinit var gistInAppMessagesProvider: InAppMessagesProvider + private lateinit var gistInAppMessagesProvider: GistInAppMessagesProvider private val gistApiProvider: GistApi = mock() + private val eventListenerMock: InAppEventListener = mock() @Before override fun setup() { @@ -150,4 +157,44 @@ internal class InAppMessagesProviderTest : BaseTest() { wasOnActionCalled shouldBeEqualTo false wasOnErrorCalled shouldBeEqualTo true } + + @Test + fun eventListener_givenEventListenerSet_expectCallEventListenerOnGistEvents() { + val givenMessage = Message().getNewRandom() + val expectedInAppMessage = InAppMessage.getFromGistMessage(givenMessage) + + gistInAppMessagesProvider.setListener(eventListenerMock) + verifyNoInteractions(eventListenerMock) + + gistInAppMessagesProvider.onMessageShown(givenMessage) + verify(eventListenerMock).messageShown(expectedInAppMessage) + + gistInAppMessagesProvider.onError(givenMessage) + verify(eventListenerMock).errorWithMessage(expectedInAppMessage) + + gistInAppMessagesProvider.onMessageDismissed(givenMessage) + verify(eventListenerMock).messageDismissed(expectedInAppMessage) + + val givenCurrentRoute = String.random + val givenAction = String.random + val givenName = String.random + gistInAppMessagesProvider.onAction(givenMessage, givenCurrentRoute, givenAction, givenName) + verify(eventListenerMock).messageActionTaken(expectedInAppMessage, currentRoute = givenCurrentRoute, action = givenAction, name = givenName) + } + + @Test + fun eventListener_givenEventListenerSet_expectCallEventListenerForEachEvent() { + val givenMessage1 = Message().getNewRandom() + val expectedInAppMessage1 = InAppMessage.getFromGistMessage(givenMessage1) + val givenMessage2 = Message().getNewRandom() + val expectedInAppMessage2 = InAppMessage.getFromGistMessage(givenMessage2) + + gistInAppMessagesProvider.setListener(eventListenerMock) + verifyNoInteractions(eventListenerMock) + + gistInAppMessagesProvider.onMessageShown(givenMessage1) + verify(eventListenerMock).messageShown(expectedInAppMessage1) + gistInAppMessagesProvider.onMessageShown(givenMessage2) + verify(eventListenerMock).messageShown(expectedInAppMessage2) + } } diff --git a/messaginginapp/src/sharedTest/java/io/customer/messaginginapp/ModuleMessagingInAppTest.kt b/messaginginapp/src/sharedTest/java/io/customer/messaginginapp/ModuleMessagingInAppTest.kt index ef74dada4..b835938c4 100644 --- a/messaginginapp/src/sharedTest/java/io/customer/messaginginapp/ModuleMessagingInAppTest.kt +++ b/messaginginapp/src/sharedTest/java/io/customer/messaginginapp/ModuleMessagingInAppTest.kt @@ -3,6 +3,7 @@ package io.customer.messaginginapp import androidx.test.ext.junit.runners.AndroidJUnit4 import io.customer.commontest.BaseTest import io.customer.messaginginapp.provider.InAppMessagesProvider +import io.customer.messaginginapp.type.InAppEventListener import io.customer.sdk.hooks.HookModule import io.customer.sdk.hooks.HooksManager import org.junit.Before @@ -19,6 +20,7 @@ internal class ModuleMessagingInAppTest : BaseTest() { private lateinit var module: ModuleMessagingInApp private val gistInAppMessagesProvider: InAppMessagesProvider = mock() private val hooksManager: HooksManager = mock() + private val eventListenerMock: InAppEventListener = mock() @Before override fun setup() { @@ -27,7 +29,11 @@ internal class ModuleMessagingInAppTest : BaseTest() { di.overrideDependency(InAppMessagesProvider::class.java, gistInAppMessagesProvider) di.overrideDependency(HooksManager::class.java, hooksManager) - module = ModuleMessagingInApp(overrideDiGraph = di, organizationId = "test") + module = ModuleMessagingInApp( + moduleConfig = MessagingInAppModuleConfig.Builder().setEventListener(eventListenerMock).build(), + overrideDiGraph = di, + organizationId = "test" + ) } @Test @@ -42,5 +48,8 @@ internal class ModuleMessagingInAppTest : BaseTest() { // verify events verify(gistInAppMessagesProvider).subscribeToEvents(any(), any(), any()) + + // verify given event listener gets registered + verify(gistInAppMessagesProvider).setListener(eventListenerMock) } } diff --git a/messaginginapp/src/sharedTest/java/io/customer/messaginginapp/testutils/extension/GistExtensions.kt b/messaginginapp/src/sharedTest/java/io/customer/messaginginapp/testutils/extension/GistExtensions.kt new file mode 100644 index 000000000..325f0df8b --- /dev/null +++ b/messaginginapp/src/sharedTest/java/io/customer/messaginginapp/testutils/extension/GistExtensions.kt @@ -0,0 +1,9 @@ +/* ktlint-disable filename */ // until this extension file contains 2+ functions in it, we will disable this ktlint rule. +package io.customer.messaginginapp.testutils.extension + +import build.gist.data.model.Message +import io.customer.messaginginapp.type.InAppMessage +import io.customer.messaginginapp.type.getMessage +import io.customer.sdk.extensions.random + +fun Message.getNewRandom(): Message = InAppMessage(String.random, String.random).getMessage()